PageRenderTime 42ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/xivo_sysconf/request_handlers/request.py

https://gitlab.com/xivo.solutions/xivo-sysconfd
Python | 284 lines | 202 code | 59 blank | 23 comment | 29 complexity | bffc8cf50aab6118b11ab2eb29a45a2c MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. # Copyright (C) 2015-2016 Avencall
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>
  16. import collections
  17. import configparser
  18. import json
  19. import logging
  20. import threading
  21. from kombu import Connection, Exchange, Producer
  22. from xivo.http_json_server import HttpReqError
  23. from xivo_bus.marshaler import Marshaler
  24. from xivo_bus.publisher import Publisher
  25. from xivo_sysconf.request_handlers.agentd import AgentdCommandExecutor, \
  26. AgentdCommandFactory
  27. from xivo_sysconf.request_handlers.asterisk import AsteriskCommandExecutor, \
  28. AsteriskCommandFactory
  29. from xivo_sysconf.request_handlers.auth_keys import AuthKeysCommandExecutor, \
  30. AuthKeysCommandFactory
  31. from xivo_sysconf.request_handlers.ctid import CTIdCommandExecutor, \
  32. CTIdCommandFactory
  33. logger = logging.getLogger(__name__)
  34. class Request(object):
  35. def __init__(self, commands):
  36. self.commands = commands
  37. self.observer = None
  38. def execute(self):
  39. for command in self.commands:
  40. command.execute()
  41. if self.observer is not None:
  42. self.observer.on_request_executed()
  43. class RequestFactory(object):
  44. def __init__(self,
  45. agentd_command_factory,
  46. asterisk_command_factory,
  47. auth_keys_command_factory,
  48. ctid_command_factory):
  49. self._agentd_command_factory = agentd_command_factory
  50. self._asterisk_command_factory = asterisk_command_factory
  51. self._auth_keys_command_factory = auth_keys_command_factory
  52. self._ctid_command_factory = ctid_command_factory
  53. def new_request(self, args):
  54. commands = []
  55. # asterisk commands must be executed first
  56. self._append_commands('ipbx', self._asterisk_command_factory, args, commands)
  57. self._append_commands('agentbus', self._agentd_command_factory, args, commands)
  58. self._append_commands('ctibus', self._ctid_command_factory, args, commands)
  59. self._append_commands('update_keys', self._auth_keys_command_factory, args, commands)
  60. return Request(commands)
  61. def _append_commands(self, key, factory, args, commands):
  62. values = args.get(key)
  63. if not values:
  64. return
  65. for value in values:
  66. try:
  67. command = factory.new_command(value)
  68. except ValueError as e:
  69. logger.warning('Invalid "%s" command %r: %s', key, value, e)
  70. except Exception:
  71. logger.exception('Error while creating "%s" command %r', key, value)
  72. else:
  73. commands.append(command)
  74. class DuplicateRequestOptimizer(object):
  75. def __init__(self, executor):
  76. self._executor = executor
  77. self._cache = set()
  78. def on_request_put(self, request):
  79. for command in request.commands:
  80. if command.executor != self._executor:
  81. continue
  82. if command.value in self._cache:
  83. command.optimized = True
  84. else:
  85. self._cache.add(command.value)
  86. def on_request_get(self, request):
  87. for command in request.commands:
  88. if command.executor != self._executor:
  89. continue
  90. if not command.optimized:
  91. self._cache.remove(command.value)
  92. class RequestQueue(object):
  93. def __init__(self, optimizer):
  94. self._lock = threading.Lock()
  95. self._condition = threading.Condition(self._lock)
  96. self._queue = collections.deque()
  97. self._optimizer = optimizer
  98. def put(self, request):
  99. with self._lock:
  100. self._queue.append(request)
  101. self._optimizer.on_request_put(request)
  102. self._condition.notify()
  103. def get(self):
  104. with self._lock:
  105. while not self._queue:
  106. self._condition.wait()
  107. request = self._queue.popleft()
  108. self._optimizer.on_request_get(request)
  109. return request
  110. class RequestProcessor(object):
  111. def __init__(self, request_queue):
  112. self._request_queue = request_queue
  113. def run(self):
  114. while True:
  115. try:
  116. request = self._request_queue.get()
  117. request.execute()
  118. except Exception:
  119. logger.exception('Unexpected error')
  120. class RequestHandlers(object):
  121. def __init__(self, request_factory, request_queue):
  122. self._request_factory = request_factory
  123. self._request_queue = request_queue
  124. def handle_request(self, args, options):
  125. try:
  126. request = self._request_factory.new_request(args)
  127. except Exception:
  128. logger.exception('Error while creating new request %s', args)
  129. raise HttpReqError(400)
  130. else:
  131. self._queue_request(request)
  132. def _queue_request(self, request):
  133. self._request_queue.put(request)
  134. class SyncRequestObserver(object):
  135. def __init__(self, timeout=30):
  136. self._event = threading.Event()
  137. self._timeout = timeout
  138. def on_request_executed(self):
  139. self._event.set()
  140. def wait(self):
  141. return self._event.wait(self._timeout)
  142. class SyncRequestHandlers(RequestHandlers):
  143. def _queue_request(self, request):
  144. request.observer = SyncRequestObserver()
  145. super(SyncRequestHandlers, self)._queue_request(request)
  146. if not request.observer.wait():
  147. logger.warning('timeout reached on synchronous request')
  148. class LazyBusPublisher(object):
  149. def __init__(self, bus_connection, bus_exchange, marshaler):
  150. self._bus_connection = bus_connection
  151. self._bus_exchange = bus_exchange
  152. self._marshaler = marshaler
  153. self._publisher = None
  154. def publish(self, event):
  155. if self._publisher is None:
  156. self._publisher = self._new_publisher()
  157. return self._publisher.publish(event)
  158. def _new_publisher(self):
  159. bus_producer = Producer(self._bus_connection, self._bus_exchange, auto_declare=True)
  160. return Publisher(bus_producer, self._marshaler)
  161. class RequestHandlersProxy(object):
  162. _SOCKET_CONFFILE = '/etc/xivo/sysconfd/socket.conf'
  163. def __init__(self):
  164. self._request_handlers = None
  165. self._request_processor = None
  166. def safe_init(self, options):
  167. # read config from socket.conf
  168. conf_obj = configparser.RawConfigParser()
  169. with open(self._SOCKET_CONFFILE) as fobj:
  170. conf_obj.readfp(fobj)
  171. ctibus_host = conf_obj.get('ctibus', 'bindaddr')
  172. ctibus_port = conf_obj.getint('ctibus', 'port')
  173. # read config from main configuration file
  174. config = options.configuration
  175. synchronous = config.getboolean('request_handlers', 'synchronous')
  176. username = config.get('bus', 'username')
  177. password = config.get('bus', 'password')
  178. host = config.get('bus', 'host')
  179. port = config.getint('bus', 'port')
  180. exchange_name = config.get('bus', 'exchange_name')
  181. exchange_type = config.get('bus', 'exchange_type')
  182. _exchange_args_string = config.get('bus', 'exchange_args')
  183. exchange_args = json.loads(_exchange_args_string)
  184. exchange_durable = config.getboolean('bus', 'exchange_durable')
  185. # instantiate bus publisher
  186. # should fetch the uuid from the config
  187. uuid = None
  188. url = 'amqp://{username}:{password}@{host}:{port}//'.format(username=username,
  189. password=password,
  190. host=host,
  191. port=port)
  192. bus_connection = Connection(url)
  193. bus_exchange = Exchange(exchange_name,
  194. type=exchange_type,
  195. arguments=exchange_args,
  196. durable=exchange_durable)
  197. bus_publisher = LazyBusPublisher(bus_connection, bus_exchange, Marshaler(uuid))
  198. # instantiate executors
  199. agentd_command_executor = AgentdCommandExecutor(bus_publisher)
  200. asterisk_command_executor = AsteriskCommandExecutor()
  201. auth_keys_command_executor = AuthKeysCommandExecutor()
  202. ctid_command_executor = CTIdCommandExecutor(ctibus_host, ctibus_port)
  203. # instantiate factories
  204. agentd_command_factory = AgentdCommandFactory(agentd_command_executor)
  205. asterisk_command_factory = AsteriskCommandFactory(asterisk_command_executor)
  206. auth_keys_command_factory = AuthKeysCommandFactory(auth_keys_command_executor)
  207. ctid_command_factory = CTIdCommandFactory(ctid_command_executor)
  208. # instantiate other stuff
  209. request_factory = RequestFactory(agentd_command_factory,
  210. asterisk_command_factory,
  211. auth_keys_command_factory,
  212. ctid_command_factory)
  213. request_optimizer = DuplicateRequestOptimizer(asterisk_command_executor)
  214. request_queue = RequestQueue(request_optimizer)
  215. if synchronous:
  216. self._request_handlers = SyncRequestHandlers(request_factory, request_queue)
  217. else:
  218. self._request_handlers = RequestHandlers(request_factory, request_queue)
  219. self._request_processor = RequestProcessor(request_queue)
  220. def at_start(self, options):
  221. t = threading.Thread(target=self._request_processor.run)
  222. t.daemon = True
  223. t.start()
  224. def handle_request(self, args, options):
  225. return self._request_handlers.handle_request(args, options)