/xivo_sysconf/request_handlers/request.py
Python | 284 lines | 202 code | 59 blank | 23 comment | 29 complexity | bffc8cf50aab6118b11ab2eb29a45a2c MD5 | raw file
- # -*- coding: utf-8 -*-
- # Copyright (C) 2015-2016 Avencall
- #
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>
- import collections
- import configparser
- import json
- import logging
- import threading
- from kombu import Connection, Exchange, Producer
- from xivo.http_json_server import HttpReqError
- from xivo_bus.marshaler import Marshaler
- from xivo_bus.publisher import Publisher
- from xivo_sysconf.request_handlers.agentd import AgentdCommandExecutor, \
- AgentdCommandFactory
- from xivo_sysconf.request_handlers.asterisk import AsteriskCommandExecutor, \
- AsteriskCommandFactory
- from xivo_sysconf.request_handlers.auth_keys import AuthKeysCommandExecutor, \
- AuthKeysCommandFactory
- from xivo_sysconf.request_handlers.ctid import CTIdCommandExecutor, \
- CTIdCommandFactory
- logger = logging.getLogger(__name__)
- class Request(object):
- def __init__(self, commands):
- self.commands = commands
- self.observer = None
- def execute(self):
- for command in self.commands:
- command.execute()
- if self.observer is not None:
- self.observer.on_request_executed()
- class RequestFactory(object):
- def __init__(self,
- agentd_command_factory,
- asterisk_command_factory,
- auth_keys_command_factory,
- ctid_command_factory):
- self._agentd_command_factory = agentd_command_factory
- self._asterisk_command_factory = asterisk_command_factory
- self._auth_keys_command_factory = auth_keys_command_factory
- self._ctid_command_factory = ctid_command_factory
- def new_request(self, args):
- commands = []
- # asterisk commands must be executed first
- self._append_commands('ipbx', self._asterisk_command_factory, args, commands)
- self._append_commands('agentbus', self._agentd_command_factory, args, commands)
- self._append_commands('ctibus', self._ctid_command_factory, args, commands)
- self._append_commands('update_keys', self._auth_keys_command_factory, args, commands)
- return Request(commands)
- def _append_commands(self, key, factory, args, commands):
- values = args.get(key)
- if not values:
- return
- for value in values:
- try:
- command = factory.new_command(value)
- except ValueError as e:
- logger.warning('Invalid "%s" command %r: %s', key, value, e)
- except Exception:
- logger.exception('Error while creating "%s" command %r', key, value)
- else:
- commands.append(command)
- class DuplicateRequestOptimizer(object):
- def __init__(self, executor):
- self._executor = executor
- self._cache = set()
- def on_request_put(self, request):
- for command in request.commands:
- if command.executor != self._executor:
- continue
- if command.value in self._cache:
- command.optimized = True
- else:
- self._cache.add(command.value)
- def on_request_get(self, request):
- for command in request.commands:
- if command.executor != self._executor:
- continue
- if not command.optimized:
- self._cache.remove(command.value)
- class RequestQueue(object):
- def __init__(self, optimizer):
- self._lock = threading.Lock()
- self._condition = threading.Condition(self._lock)
- self._queue = collections.deque()
- self._optimizer = optimizer
- def put(self, request):
- with self._lock:
- self._queue.append(request)
- self._optimizer.on_request_put(request)
- self._condition.notify()
- def get(self):
- with self._lock:
- while not self._queue:
- self._condition.wait()
- request = self._queue.popleft()
- self._optimizer.on_request_get(request)
- return request
- class RequestProcessor(object):
- def __init__(self, request_queue):
- self._request_queue = request_queue
- def run(self):
- while True:
- try:
- request = self._request_queue.get()
- request.execute()
- except Exception:
- logger.exception('Unexpected error')
- class RequestHandlers(object):
- def __init__(self, request_factory, request_queue):
- self._request_factory = request_factory
- self._request_queue = request_queue
- def handle_request(self, args, options):
- try:
- request = self._request_factory.new_request(args)
- except Exception:
- logger.exception('Error while creating new request %s', args)
- raise HttpReqError(400)
- else:
- self._queue_request(request)
- def _queue_request(self, request):
- self._request_queue.put(request)
- class SyncRequestObserver(object):
- def __init__(self, timeout=30):
- self._event = threading.Event()
- self._timeout = timeout
- def on_request_executed(self):
- self._event.set()
- def wait(self):
- return self._event.wait(self._timeout)
- class SyncRequestHandlers(RequestHandlers):
- def _queue_request(self, request):
- request.observer = SyncRequestObserver()
- super(SyncRequestHandlers, self)._queue_request(request)
- if not request.observer.wait():
- logger.warning('timeout reached on synchronous request')
- class LazyBusPublisher(object):
- def __init__(self, bus_connection, bus_exchange, marshaler):
- self._bus_connection = bus_connection
- self._bus_exchange = bus_exchange
- self._marshaler = marshaler
- self._publisher = None
- def publish(self, event):
- if self._publisher is None:
- self._publisher = self._new_publisher()
- return self._publisher.publish(event)
- def _new_publisher(self):
- bus_producer = Producer(self._bus_connection, self._bus_exchange, auto_declare=True)
- return Publisher(bus_producer, self._marshaler)
- class RequestHandlersProxy(object):
- _SOCKET_CONFFILE = '/etc/xivo/sysconfd/socket.conf'
- def __init__(self):
- self._request_handlers = None
- self._request_processor = None
- def safe_init(self, options):
- # read config from socket.conf
- conf_obj = configparser.RawConfigParser()
- with open(self._SOCKET_CONFFILE) as fobj:
- conf_obj.readfp(fobj)
- ctibus_host = conf_obj.get('ctibus', 'bindaddr')
- ctibus_port = conf_obj.getint('ctibus', 'port')
- # read config from main configuration file
- config = options.configuration
- synchronous = config.getboolean('request_handlers', 'synchronous')
- username = config.get('bus', 'username')
- password = config.get('bus', 'password')
- host = config.get('bus', 'host')
- port = config.getint('bus', 'port')
- exchange_name = config.get('bus', 'exchange_name')
- exchange_type = config.get('bus', 'exchange_type')
- _exchange_args_string = config.get('bus', 'exchange_args')
- exchange_args = json.loads(_exchange_args_string)
- exchange_durable = config.getboolean('bus', 'exchange_durable')
- # instantiate bus publisher
- # should fetch the uuid from the config
- uuid = None
- url = 'amqp://{username}:{password}@{host}:{port}//'.format(username=username,
- password=password,
- host=host,
- port=port)
- bus_connection = Connection(url)
- bus_exchange = Exchange(exchange_name,
- type=exchange_type,
- arguments=exchange_args,
- durable=exchange_durable)
- bus_publisher = LazyBusPublisher(bus_connection, bus_exchange, Marshaler(uuid))
- # instantiate executors
- agentd_command_executor = AgentdCommandExecutor(bus_publisher)
- asterisk_command_executor = AsteriskCommandExecutor()
- auth_keys_command_executor = AuthKeysCommandExecutor()
- ctid_command_executor = CTIdCommandExecutor(ctibus_host, ctibus_port)
- # instantiate factories
- agentd_command_factory = AgentdCommandFactory(agentd_command_executor)
- asterisk_command_factory = AsteriskCommandFactory(asterisk_command_executor)
- auth_keys_command_factory = AuthKeysCommandFactory(auth_keys_command_executor)
- ctid_command_factory = CTIdCommandFactory(ctid_command_executor)
- # instantiate other stuff
- request_factory = RequestFactory(agentd_command_factory,
- asterisk_command_factory,
- auth_keys_command_factory,
- ctid_command_factory)
- request_optimizer = DuplicateRequestOptimizer(asterisk_command_executor)
- request_queue = RequestQueue(request_optimizer)
- if synchronous:
- self._request_handlers = SyncRequestHandlers(request_factory, request_queue)
- else:
- self._request_handlers = RequestHandlers(request_factory, request_queue)
- self._request_processor = RequestProcessor(request_queue)
- def at_start(self, options):
- t = threading.Thread(target=self._request_processor.run)
- t.daemon = True
- t.start()
- def handle_request(self, args, options):
- return self._request_handlers.handle_request(args, options)