provider.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. """Base implementation of a provider interface."""
  2. import ast
  3. import functools
  4. import logging
  5. import os
  6. from os.path import expanduser
  7. try:
  8. from configparser import ConfigParser
  9. except ImportError: # Python 2
  10. from ConfigParser import SafeConfigParser as ConfigParser
  11. from pyeventsystem.middleware import SimpleMiddlewareManager
  12. import six
  13. from ..base.middleware import ExceptionWrappingMiddleware
  14. from ..interfaces import CloudProvider
  15. from ..interfaces.exceptions import ProviderConnectionException
  16. from ..interfaces.resources import Configuration
  17. log = logging.getLogger(__name__)
  18. DEFAULT_RESULT_LIMIT = 50
  19. DEFAULT_WAIT_TIMEOUT = 600
  20. DEFAULT_WAIT_INTERVAL = 5
  21. # By default, use two locations for CloudBridge configuration
  22. CloudBridgeConfigPath = '/etc/cloudbridge.ini'
  23. CloudBridgeConfigLocations = [CloudBridgeConfigPath]
  24. UserConfigPath = os.path.join(expanduser('~'), '.cloudbridge')
  25. CloudBridgeConfigLocations.append(UserConfigPath)
  26. class BaseConfiguration(Configuration):
  27. def __init__(self, user_config):
  28. self.update(user_config)
  29. @property
  30. def default_result_limit(self):
  31. """
  32. Get the maximum number of results to return for a
  33. list method
  34. :rtype: ``int``
  35. :return: The maximum number of results to return
  36. """
  37. log.debug("Maximum number of results for list methods %s",
  38. DEFAULT_RESULT_LIMIT)
  39. return self.get('default_result_limit', DEFAULT_RESULT_LIMIT)
  40. @property
  41. def default_wait_timeout(self):
  42. """
  43. Gets the default wait timeout for LifeCycleObjects.
  44. """
  45. log.debug("Default wait timeout for LifeCycleObjects %s",
  46. DEFAULT_WAIT_TIMEOUT)
  47. return self.get('default_wait_timeout', DEFAULT_WAIT_TIMEOUT)
  48. @property
  49. def default_wait_interval(self):
  50. """
  51. Gets the default wait interval for LifeCycleObjects.
  52. """
  53. log.debug("Default wait interfal for LifeCycleObjects %s",
  54. DEFAULT_WAIT_INTERVAL)
  55. return self.get('default_wait_interval', DEFAULT_WAIT_INTERVAL)
  56. @property
  57. def debug_mode(self):
  58. """
  59. A flag indicating whether CloudBridge is in debug mode. Setting
  60. this to True will cause the underlying provider's debug
  61. output to be turned on.
  62. The flag can be toggled by sending in the cb_debug value via
  63. the config dictionary, or setting the CB_DEBUG environment variable.
  64. :rtype: ``bool``
  65. :return: Whether debug mode is on.
  66. """
  67. return self.get('cb_debug', os.environ.get('CB_DEBUG', False))
  68. class BaseCloudProvider(CloudProvider):
  69. def __init__(self, config):
  70. self._config = BaseConfiguration(config)
  71. self._config_parser = ConfigParser()
  72. self._config_parser.read(CloudBridgeConfigLocations)
  73. self._middleware = SimpleMiddlewareManager()
  74. self.add_required_middleware()
  75. self._region_name = None
  76. self._zone_name = None
  77. @property
  78. def region_name(self):
  79. return self._region_name
  80. @property
  81. def zone_name(self):
  82. if not self._zone_name:
  83. region = self.compute.regions.current
  84. zone = region.default_zone
  85. self._zone_name = zone.name if zone else None
  86. return self._zone_name
  87. else:
  88. try:
  89. zone_dict = ast.literal_eval(self._zone_name)
  90. if isinstance(zone_dict, dict):
  91. return zone_dict
  92. except (ValueError, SyntaxError):
  93. pass
  94. return self._zone_name
  95. @property
  96. def config(self):
  97. return self._config
  98. @property
  99. def name(self):
  100. return str(self.__class__.__name__)
  101. @property
  102. def middleware(self):
  103. return self._middleware
  104. def add_required_middleware(self):
  105. """
  106. Adds common middleware that is essential for cloudbridge to function.
  107. Any other extra middleware can be added through the provider factory.
  108. """
  109. self.middleware.add(ExceptionWrappingMiddleware())
  110. def authenticate(self):
  111. """
  112. A basic implementation which simply runs a low impact command to
  113. check whether cloud credentials work. Providers should override with
  114. more efficient implementations.
  115. """
  116. log.debug("Checking if cloud credential works...")
  117. try:
  118. self.security.key_pairs.list()
  119. return True
  120. except Exception as e:
  121. log.exception("ProviderConnectionException occurred")
  122. raise ProviderConnectionException(
  123. "Authentication with cloud provider failed: %s" % (e,))
  124. def clone(self, zone=None):
  125. cloned_config = self.config.copy()
  126. cloned_provider = self.__class__(cloned_config)
  127. if zone:
  128. # pylint:disable=protected-access
  129. cloned_provider._zone_name = zone.name
  130. return cloned_provider
  131. def _deepgetattr(self, obj, attr):
  132. """Recurses through an attribute chain to get the ultimate value."""
  133. return functools.reduce(getattr, attr.split('.'), obj)
  134. def has_service(self, service_type):
  135. """
  136. Checks whether this provider supports a given service.
  137. :type service_type: str or :class:``.CloudServiceType``
  138. :param service_type: Type of service to check support for.
  139. :rtype: bool
  140. :return: ``True`` if the service type is supported.
  141. """
  142. log.info("Checking if provider supports %s", service_type)
  143. try:
  144. if self._deepgetattr(self, service_type):
  145. log.info("This provider supports %s",
  146. service_type)
  147. return True
  148. except AttributeError:
  149. pass # Undefined service type
  150. except NotImplementedError:
  151. pass # service not implemented
  152. log.info("This provider doesn't support %s",
  153. service_type)
  154. return False
  155. def _get_config_value(self, key, default_value=None):
  156. """
  157. A convenience method to extract a configuration value.
  158. :type key: str
  159. :param key: a field to look for in the ``self.config`` field
  160. :type default_value: anything
  161. :param default_value: the default value to return if a value for the
  162. ``key`` is not available
  163. :return: a configuration value for the supplied ``key``
  164. """
  165. log.debug("Getting config key %s, with supplied default value: %s",
  166. key, default_value)
  167. value = default_value
  168. if isinstance(self.config, dict) and self.config.get(key):
  169. value = self.config.get(key, default_value)
  170. elif hasattr(self.config, key) and getattr(self.config, key):
  171. value = getattr(self.config, key)
  172. elif (self._config_parser.has_option(self.PROVIDER_ID, key) and
  173. self._config_parser.get(self.PROVIDER_ID, key)):
  174. value = self._config_parser.get(self.PROVIDER_ID, key)
  175. if isinstance(value, six.string_types) and not isinstance(
  176. value, six.text_type):
  177. return six.u(value)
  178. return value