provider.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. """Base implementation of a provider interface."""
  2. import functools
  3. import logging
  4. import os
  5. from os.path import expanduser
  6. try:
  7. from configparser import ConfigParser
  8. except ImportError: # Python 2
  9. from ConfigParser import SafeConfigParser as ConfigParser
  10. import six
  11. from cloudbridge.cloud.interfaces import CloudProvider
  12. from cloudbridge.cloud.interfaces.exceptions import HandlerException
  13. from cloudbridge.cloud.interfaces.exceptions import ProviderConnectionException
  14. from cloudbridge.cloud.interfaces.provider import HandlerType
  15. from cloudbridge.cloud.interfaces.resources import Configuration
  16. log = logging.getLogger(__name__)
  17. DEFAULT_RESULT_LIMIT = 50
  18. DEFAULT_WAIT_TIMEOUT = 600
  19. DEFAULT_WAIT_INTERVAL = 5
  20. # By default, use two locations for CloudBridge configuration
  21. CloudBridgeConfigPath = '/etc/cloudbridge.ini'
  22. CloudBridgeConfigLocations = [CloudBridgeConfigPath]
  23. UserConfigPath = os.path.join(expanduser('~'), '.cloudbridge')
  24. CloudBridgeConfigLocations.append(UserConfigPath)
  25. class BaseConfiguration(Configuration):
  26. def __init__(self, user_config):
  27. self.update(user_config)
  28. @property
  29. def default_result_limit(self):
  30. """
  31. Get the maximum number of results to return for a
  32. list method
  33. :rtype: ``int``
  34. :return: The maximum number of results to return
  35. """
  36. log.debug("Maximum number of results for list methods %s",
  37. DEFAULT_RESULT_LIMIT)
  38. return self.get('default_result_limit', DEFAULT_RESULT_LIMIT)
  39. @property
  40. def default_wait_timeout(self):
  41. """
  42. Gets the default wait timeout for LifeCycleObjects.
  43. """
  44. log.debug("Default wait timeout for LifeCycleObjects %s",
  45. DEFAULT_WAIT_TIMEOUT)
  46. return self.get('default_wait_timeout', DEFAULT_WAIT_TIMEOUT)
  47. @property
  48. def default_wait_interval(self):
  49. """
  50. Gets the default wait interval for LifeCycleObjects.
  51. """
  52. log.debug("Default wait interfal for LifeCycleObjects %s",
  53. DEFAULT_WAIT_INTERVAL)
  54. return self.get('default_wait_interval', DEFAULT_WAIT_INTERVAL)
  55. @property
  56. def debug_mode(self):
  57. """
  58. A flag indicating whether CloudBridge is in debug mode. Setting
  59. this to True will cause the underlying provider's debug
  60. output to be turned on.
  61. The flag can be toggled by sending in the cb_debug value via
  62. the config dictionary, or setting the CB_DEBUG environment variable.
  63. :rtype: ``bool``
  64. :return: Whether debug mode is on.
  65. """
  66. return self.get('cb_debug', os.environ.get('CB_DEBUG', False))
  67. class EventDispatcher(object):
  68. def __init__(self):
  69. self.__events = {}
  70. self.__initialized = {}
  71. def get_handlers(self, event_name):
  72. return self.__events.get(event_name)
  73. def check_initialized(self, event_name):
  74. return self.__initialized.get(event_name) or False
  75. def mark_initialized(self, event_name):
  76. self.__initialized[event_name] = True
  77. def subscribe(self, event_name, priority, callback, result_callback=False):
  78. """
  79. Subscribe a handler by event name to the dispatcher.
  80. :type event_name: str
  81. :param event_name: The name of the event to which you are subscribing
  82. the callback function.
  83. :type priority: int
  84. :param priority: The priority that this handler should be given.
  85. When the event is emitted, all handlers will be run in order of
  86. priority.
  87. :type callback: function
  88. :param callback: The callback function that should be called with
  89. the parameters given at when the even is emitted.
  90. :type result_callback: bool
  91. :param result_callback: Whether the callback function should be using
  92. the last return value from previous functions as an argument. This
  93. function should accept a `callback_result` parameter in addition to
  94. the parameters of the event.
  95. """
  96. if result_callback:
  97. handler = EventHandler(HandlerType.RESULT_SUBSCRIPTION, callback)
  98. else:
  99. handler = EventHandler(HandlerType.SUBSCRIPTION, callback)
  100. if not self.__events.get(event_name):
  101. self.__events[event_name] = list()
  102. self.__events[event_name].append((priority, handler))
  103. def interceptable_call(self, event_name, priority, callback, **kwargs):
  104. handler = EventHandler(HandlerType.SUBSCRIPTION, callback)
  105. if not self.__events.get(event_name):
  106. self.__events[event_name] = list()
  107. self.__events[event_name].append((priority, handler))
  108. try:
  109. ret_obj = self._emit(event_name, **kwargs)
  110. finally:
  111. self.__events[event_name].remove((priority, handler))
  112. return ret_obj
  113. def _emit(self, event_name, **kwargs):
  114. def priority_sort(handler_list):
  115. handler_list.sort(key=lambda x: x[0])
  116. # Make sure all priorities are unique
  117. prev_prio = None
  118. for (priority, handler) in handler_list:
  119. if priority == prev_prio:
  120. message = "Event '{}' has multiple subscribed handlers " \
  121. "at priority '{}'. Each priority must only " \
  122. "have a single corresponding handler."\
  123. .format(event_name, priority)
  124. raise HandlerException(message)
  125. return handler_list
  126. if not self.__events.get(event_name):
  127. message = "Event '{}' has no subscribed handlers.".\
  128. format(event_name)
  129. raise HandlerException(message)
  130. prev_handler = None
  131. first_handler = None
  132. for (priority, handler) in priority_sort(self.__events[event_name]):
  133. if not first_handler:
  134. first_handler = handler
  135. if prev_handler:
  136. prev_handler.next_handler = handler
  137. prev_handler = handler
  138. return first_handler.invoke(kwargs)
  139. class EventHandler(object):
  140. def __init__(self, handler_type, callback, result_callback=None):
  141. self.handler_type = handler_type
  142. self.callback = callback
  143. self.result_callback = result_callback
  144. self._next_handler = None
  145. @property
  146. def next_handler(self):
  147. return self._next_handler
  148. @next_handler.setter
  149. def next_handler(self, new_handler):
  150. self._next_handler = new_handler
  151. def invoke(self, args):
  152. if self.handler_type in [HandlerType.SUBSCRIPTION,
  153. HandlerType.RESULT_SUBSCRIPTION]:
  154. result = self.callback(**args)
  155. next = self.next_handler
  156. if next:
  157. if next.handler_type == HandlerType.RESULT_SUBSCRIPTION:
  158. args['callback_result'] = result
  159. new_result = next.invoke(args)
  160. if new_result:
  161. result = new_result
  162. elif next.handler_type == HandlerType.SUBSCRIPTION:
  163. new_result = next.invoke(args)
  164. if new_result:
  165. result = new_result
  166. return result
  167. class BaseCloudProvider(CloudProvider):
  168. def __init__(self, config):
  169. self._config = BaseConfiguration(config)
  170. self._config_parser = ConfigParser()
  171. self._config_parser.read(CloudBridgeConfigLocations)
  172. self._events = EventDispatcher()
  173. @property
  174. def config(self):
  175. return self._config
  176. @property
  177. def name(self):
  178. return str(self.__class__.__name__)
  179. @property
  180. def events(self):
  181. return self._events
  182. def authenticate(self):
  183. """
  184. A basic implementation which simply runs a low impact command to
  185. check whether cloud credentials work. Providers should override with
  186. more efficient implementations.
  187. """
  188. log.debug("Checking if cloud credential works...")
  189. try:
  190. self.security.key_pairs.list()
  191. return True
  192. except Exception as e:
  193. log.exception("ProviderConnectionException occurred")
  194. raise ProviderConnectionException(
  195. "Authentication with cloud provider failed: %s" % (e,))
  196. def _deepgetattr(self, obj, attr):
  197. """Recurses through an attribute chain to get the ultimate value."""
  198. return functools.reduce(getattr, attr.split('.'), obj)
  199. def has_service(self, service_type):
  200. """
  201. Checks whether this provider supports a given service.
  202. :type service_type: str or :class:``.CloudServiceType``
  203. :param service_type: Type of service to check support for.
  204. :rtype: bool
  205. :return: ``True`` if the service type is supported.
  206. """
  207. log.info("Checking if provider supports %s", service_type)
  208. try:
  209. if self._deepgetattr(self, service_type):
  210. log.info("This provider supports %s",
  211. service_type)
  212. return True
  213. except AttributeError:
  214. pass # Undefined service type
  215. except NotImplementedError:
  216. pass # service not implemented
  217. log.info("This provider doesn't support %s",
  218. service_type)
  219. return False
  220. def _get_config_value(self, key, default_value):
  221. """
  222. A convenience method to extract a configuration value.
  223. :type key: str
  224. :param key: a field to look for in the ``self.config`` field
  225. :type default_value: anything
  226. :param default_value: the default value to return if a value for the
  227. ``key`` is not available
  228. :return: a configuration value for the supplied ``key``
  229. """
  230. log.debug("Getting config key %s, with supplied default value: %s",
  231. key, default_value)
  232. value = default_value
  233. if isinstance(self.config, dict) and self.config.get(key):
  234. value = self.config.get(key, default_value)
  235. elif hasattr(self.config, key) and getattr(self.config, key):
  236. value = getattr(self.config, key)
  237. elif (self._config_parser.has_option(self.PROVIDER_ID, key) and
  238. self._config_parser.get(self.PROVIDER_ID, key)):
  239. value = self._config_parser.get(self.PROVIDER_ID, key)
  240. if isinstance(value, six.string_types) and not isinstance(
  241. value, six.text_type):
  242. return six.u(value)
  243. return value