from flask import render_template, g, jsonify from flask.views import MethodView from flask_pydantic import validate from flask_jwt_extended import jwt_required from playhouse.shortcuts import model_to_dict import app.modules.roxywi.auth as roxywi_auth import app.modules.roxywi.common as roxywi_common import app.modules.common.common as common import app.modules.db.udp as udp_sql import app.modules.db.ha_cluster as ha_sql import app.modules.db.server as server_sql import app.modules.service.udp as udp_mod import app.modules.service.installation as service_mod from app.middleware import get_user_params, check_services, page_for_admin, check_group from app.modules.common.common_classes import SupportClass from app.modules.roxywi.class_models import BaseResponse, IdResponse, UdpListenerRequest, GroupQuery class UDPListener(MethodView): method_decorators = ["GET", "POST", "PUT", "DELETE"] decorators = [jwt_required(), get_user_params(), check_services, check_group()] def __init__(self, is_api=True): self.is_api = is_api def get(self, service: str, listener_id: int): """ Get information about a specific UDP listener. --- tags: - UDP listener parameters: - name: service in: path type: string required: true description: 'Can be only "udp"' - name: listener_id in: path type: integer required: true description: The listener's identifier responses: 200: description: Listener configuration returned successfully schema: type: object properties: check_enabled: type: integer cluster_id: type: string config: type: array items: type: object properties: backend_ip: type: string description: The IP address of the backend server port: type: integer description: Port number on which the backend server listens for requests weight: type: integer description: Weight assigned to the backend server delay_before_retry: type: integer delay_loop: type: integer description: type: string group_id: type: integer id: type: integer lb_algo: type: string name: type: string port: type: integer retry: type: integer server_id: type: integer vip: type: string default: description: Unexpected error """ if self.is_api: if listener_id: try: listener_config = udp_mod.get_listener_config(listener_id) listener_config['status'] = udp_mod.check_is_listener_active(listener_id) except Exception as e: return roxywi_common.handler_exceptions_for_json_data(e, 'Listener not found') return jsonify(listener_config) else: if not listener_id: kwargs = { 'listeners': udp_sql.select_listeners(g.user_params['group_id']), 'lang': g.user_params['lang'], 'clusters': ha_sql.select_clusters(g.user_params['group_id']), 'is_needed_tool': common.is_tool('ansible'), 'user_subscription': roxywi_common.return_user_subscription() } return render_template('udp/listeners.html', **kwargs) else: listener = udp_sql.get_listener(listener_id) cluster = dict() server = dict() if listener.cluster_id: cluster = ha_sql.select_cluster(listener.cluster_id) elif listener.server_id: server = server_sql.get_server_by_id(listener.server_id) kwargs = { 'clusters': cluster, 'listener': listener, 'server': server, 'lang': g.user_params['lang'], } return render_template('udp/listener.html', **kwargs) @validate(body=UdpListenerRequest) def post(self, service: str, body: UdpListenerRequest): """ This endpoint allows to create a new UDP listener. --- tags: - UDP listener parameters: - in: 'path' name: 'service' description: 'Can be only "udp"' required: true type: 'string' - in: body name: body required: true schema: required: - config - name - port - lb_algo - vip type: object properties: config: type: array items: type: object properties: backend_ip: type: string description: The IP address of the backend server port: type: integer description: Port number on which the backend server listens for requests weight: type: integer description: Weight assigned to the backend server name: type: string cluster_id: type: integer description: Cluster ID where the UDP listener is located. Must be determined if server_id empty server_id: type: integer description: Standalone mode. Server ID where the UDP listener is located. Must be determined if cluster_id empty group_id: type: string port: type: string lb_algo: type: string description: "'rr': 'Round robin', 'wrr': 'Weighted Round Robin', 'lc': 'Least Connection', 'wlc': 'Weighted Least Connection', 'sh': 'Source Hashing', 'dh': 'Destination Hashing', 'lblc': 'Locality-Based Least Connection'" schema: enum: [rr, wrr, lc, wlc, sh, dh, wlc, lblc] check_enabled: type: integer default: 1 description: Enable backend servers checking delay_before_retry: type: integer default: 10 description: Delay between two successive retries delay_loop: type: integer default: 10 description: Specify in seconds the interval between checks retry: type: integer default: 3 description: Maximum number of retries before mark a backend server as down description: type: string vip: type: string description: IP address of the UDP listener binding, if Standalone mode. VIP address of the UDP listener binding, if HA Cluster mode reconfigure: type: boolean description: If 1, reconfigure UDP listener. If 0, just save UDP listener without configuration on servers responses: 201: description: UDP listener created successfully 400: description: Invalid request data default: description: Unexpected error """ roxywi_auth.page_for_admin(level=3) try: listener_id = udp_sql.insert_listener(**body.model_dump(mode='json', exclude={'reconfigure'})) roxywi_common.logging(listener_id, f'UDP listener {body.name} has been created', keep_history=1, roxywi=1, service='UDP Listener') if body.reconfigure: self._reconfigure(listener_id, 'install') return IdResponse(id=listener_id).model_dump(mode='json') except Exception as e: return roxywi_common.handle_json_exceptions(e, 'Cannot create UDP listener') @validate(body=UdpListenerRequest) def put(self, service: str, listener_id: int, body: UdpListenerRequest): """ This endpoint allows to update a UDP listener. --- tags: - UDP listener parameters: - in: 'path' name: 'service' description: 'Can be only "udp"' required: true type: 'string' - in: 'path' name: 'listener_id' description: 'ID of the UDP listener' required: true type: 'integer' - in: body name: body required: true schema: required: - config - name - port - lb_algo - vip type: object properties: config: type: array items: type: object properties: backend_ip: type: string description: The IP address of the backend server port: type: integer description: Port number on which the backend server listens for requests weight: type: integer description: Weight assigned to the backend server name: type: string cluster_id: type: integer description: Cluster ID where the UDP listener is located. Must be determined if server_id empty server_id: type: integer description: Standalone mode. Server ID where the UDP listener is located. Must be determined if cluster_id empty group_id: type: string port: type: string lb_algo: type: string description: "'rr': 'Round robin', 'wrr': 'Weighted Round Robin', 'lc': 'Least Connection', 'wlc': 'Weighted Least Connection', 'sh': 'Source Hashing', 'dh': 'Destination Hashing', 'lblc': 'Locality-Based Least Connection'" schema: enum: [rr, wrr, lc, wlc, sh, dh, wlc, lblc] check_enabled: type: integer default: 1 description: Enable backend servers checking delay_before_retry: type: integer default: 10 description: Delay between two successive retries delay_loop: type: integer default: 10 description: Specify in seconds the interval between checks retry: type: integer default: 3 description: Maximum number of retries before mark a backend server as down description: type: string vip: type: string description: IP address of the UDP listener binding, if Standalone mode. VIP address of the UDP listener binding, if HA Cluster mode reconfigure: type: boolean description: If 1, reconfigure UDP listener. If 0, just save UDP listener without configuration on servers responses: 201: description: UDP listener created successfully 400: description: Invalid request data default: description: Unexpected error """ roxywi_auth.page_for_admin(level=3) try: udp_sql.update_listener(listener_id, **body.model_dump(mode='json', exclude={'reconfigure'})) roxywi_common.logging(listener_id, f'UDP listener {body.name} has been updated', keep_history=1, roxywi=1, service='UDP Listener') if body.reconfigure: self._reconfigure(listener_id, 'install') return BaseResponse().model_dump(mode='json') except Exception as e: return roxywi_common.handle_json_exceptions(e, 'Cannot update UDP listener') def delete(self, service: str, listener_id: int): """ Delete a UDP listener --- tags: - UDP listener parameters: - in: 'path' name: 'service' description: 'Can be only "udp"' required: true type: 'string' - in: 'path' name: 'listener_id' description: 'ID of the UDP listener' required: true type: 'integer' responses: 204: description: UDP listener deletion successful """ roxywi_auth.page_for_admin(level=3) try: self._reconfigure(listener_id, 'uninstall') roxywi_common.logging(listener_id, f'UDP listener has been deleted {listener_id}', roxywi=1, keep_history=1, login=1, service='UDP listener') except Exception as e: return roxywi_common.handle_json_exceptions(e, f'Cannot create inventory for UDP listener deleting {listener_id}') try: udp_sql.delete_listener(listener_id) return BaseResponse().model_dump(mode='json'), 204 except Exception as e: return roxywi_common.handle_json_exceptions(e, f'Cannot delete UDP listener {listener_id}') @staticmethod def _reconfigure(listener_id, action): try: inv, server_ips = service_mod.generate_udp_inv(listener_id, action) except Exception as e: raise Exception(e) try: output = service_mod.run_ansible(inv, server_ips, 'udp') if len(output['failures']) > 0 or len(output['dark']) > 0: raise Exception(f'Cannot {action} UDP listener. Check Apache error log') except Exception as e: raise Exception(f'Cannot {action} UDP listener: {e}') class UDPListeners(MethodView): method_decorators = ["GET"] decorators = [jwt_required(), get_user_params(), check_services, check_group()] @validate(query=GroupQuery) def get(self, service: str, query: GroupQuery): """ Get information about a specific UDP listener. --- tags: - UDP listener parameters: - name: service in: path type: string required: true description: 'Can be only "udp"' - name: group_id in: query type: integer required: false description: The group's identifier. Only accessible by superAdmin role. responses: 200: description: Listener configuration returned successfully schema: type: array items: type: object properties: check_enabled: type: integer cluster_id: type: string config: type: array items: type: object properties: backend_ip: type: string description: The IP address of the backend server port: type: integer description: Port number on which the backend server listens for requests weight: type: integer description: Weight assigned to the backend server delay_before_retry: type: integer delay_loop: type: integer description: type: string group_id: type: integer id: type: integer lb_algo: type: string name: type: string port: type: integer retry: type: integer server_id: type: integer vip: type: string default: description: Unexpected error """ try: group_id = SupportClass.return_group_id(query) except Exception as e: return roxywi_common.handle_json_exceptions(e, 'Cannot get UDP listeners') try: listeners = udp_sql.select_listeners(group_id) except Exception as e: return roxywi_common.handle_json_exceptions(e, 'Cannot get UDP listeners') return jsonify([model_to_dict(listener, recurse=False) for listener in listeners]) class UDPListenerActionView(MethodView): methods = ['GET'] decorators = [jwt_required(), get_user_params(), check_services, page_for_admin(level=3), check_group()] @staticmethod def get(service: str, listener_id: int, action: str): """ This endpoint performs a specified action on a certain UDP listener. --- tags: - UDP listener parameters: - in: path name: service required: true type: 'string' description: Can be only "udp" - in: path name: listener_id type: 'integer' required: true description: The ID af the UDP listener - in: path name: action type: 'string' required: true description: The action to be performed on the service (start, stop, reload, restart) responses: 200: description: Successful operation default: description: Unexpected error """ try: udp_mod.listener_actions(listener_id, action, g.user_params['group_id']) roxywi_common.logging(listener_id, f'UDP listener {listener_id} has been {action}ed', roxywi=1, keep_history=1, login=1, service='UDP listener') return BaseResponse().model_dump(mode='json') except Exception as e: return roxywi_common.handle_json_exceptions(e, f'Cannot {action} listener')