factory.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import importlib
  2. import inspect
  3. import logging
  4. import pkgutil
  5. from collections import defaultdict
  6. from cloudbridge.cloud import providers
  7. from cloudbridge.cloud.interfaces import CloudProvider
  8. from cloudbridge.cloud.interfaces import TestMockHelperMixin
  9. log = logging.getLogger(__name__)
  10. class ProviderList(object):
  11. AWS = 'aws'
  12. AZURE = 'azure'
  13. GCE = 'gce'
  14. OPENSTACK = 'openstack'
  15. class CloudProviderFactory(object):
  16. """
  17. Get info and handle on the available cloud provider implementations.
  18. """
  19. def __init__(self):
  20. self.provider_list = defaultdict(dict)
  21. log.debug("Providers List: %s", self.provider_list)
  22. def register_provider_class(self, cls):
  23. """
  24. Registers a provider class with the factory. The class must
  25. inherit from cloudbridge.cloud.interfaces.CloudProvider
  26. and also have a class attribute named PROVIDER_ID.
  27. The PROVIDER_ID is a user friendly name for the cloud provider,
  28. such as 'aws'. The PROVIDER_ID must also be included in the
  29. cloudbridge.factory.ProviderList.
  30. :type cls: class
  31. :param cls: A class implementing the CloudProvider interface.
  32. Mock providers must also implement
  33. :py:class:`cloudbridge.cloud.base.helpers.
  34. TestMockHelperMixin`.
  35. """
  36. if isinstance(cls, type) and issubclass(cls, CloudProvider):
  37. if hasattr(cls, "PROVIDER_ID"):
  38. provider_id = getattr(cls, "PROVIDER_ID")
  39. if issubclass(cls, TestMockHelperMixin):
  40. if self.provider_list.get(provider_id, {}).get(
  41. 'mock_class'):
  42. log.warning("Mock provider with id: %s is already "
  43. "registered. Overriding with class: %s",
  44. provider_id, cls)
  45. self.provider_list[provider_id]['mock_class'] = cls
  46. else:
  47. if self.provider_list.get(provider_id, {}).get('class'):
  48. log.warning("Provider with id: %s is already "
  49. "registered. Overriding with class: %s",
  50. provider_id, cls)
  51. self.provider_list[provider_id]['class'] = cls
  52. else:
  53. log.warning("Provider class: %s implements CloudProvider but"
  54. " does not define PROVIDER_ID. Ignoring...", cls)
  55. else:
  56. log.debug("Class: %s does not implement the CloudProvider"
  57. " interface. Ignoring...", cls)
  58. def discover_providers(self):
  59. """
  60. Discover all available providers within the
  61. ``cloudbridge.cloud.providers`` package.
  62. Note that this methods does not guard against a failed import.
  63. """
  64. for _, modname, _ in pkgutil.iter_modules(providers.__path__):
  65. log.debug("Importing provider: %s", modname)
  66. self._import_provider(modname)
  67. def _import_provider(self, module_name):
  68. """
  69. Imports and registers providers from the given module name.
  70. Raises an ImportError if the import does not succeed.
  71. """
  72. log.debug("Importing providers from %s", module_name)
  73. module = importlib.import_module(
  74. "{0}.{1}".format(providers.__name__,
  75. module_name))
  76. classes = inspect.getmembers(module, inspect.isclass)
  77. for _, cls in classes:
  78. log.debug("Registering the provider: %s", cls)
  79. self.register_provider_class(cls)
  80. def list_providers(self):
  81. """
  82. Get a list of available providers.
  83. It uses a simple automatic discovery system by iterating through all
  84. submodules in cloudbridge.cloud.providers.
  85. :rtype: dict
  86. :return: A dict of available providers and their implementations in the
  87. following format::
  88. {'aws': {'class': aws.provider.AWSCloudProvider,
  89. 'mock_class': aws.provider.MockAWSCloudProvider},
  90. 'openstack': {'class': openstack.provider.OpenStackCloudProvi
  91. der}
  92. }
  93. """
  94. if not self.provider_list:
  95. self.discover_providers()
  96. log.debug("List of available providers: %s", self.provider_list)
  97. return self.provider_list
  98. def create_provider(self, name, config):
  99. """
  100. Searches all available providers for a CloudProvider interface with the
  101. given name, and instantiates it based on the given config dictionary,
  102. where the config dictionary is a dictionary understood by that
  103. cloud provider.
  104. :type name: str
  105. :param name: Cloud provider name: one of ``aws``, ``openstack``.
  106. :type config: an object with required fields
  107. :param config: This can be a Bunch or any other object whose fields can
  108. be accessed using dot notation. See specific provider
  109. implementation for the required fields.
  110. :return: a concrete provider instance
  111. :rtype: ``object`` of :class:`.CloudProvider`
  112. """
  113. log.info("Creating '%s' provider", name)
  114. provider_class = self.get_provider_class(name)
  115. if provider_class is None:
  116. log.exception("A provider with the name %s could not "
  117. "be found", name)
  118. raise NotImplementedError(
  119. 'A provider with name {0} could not be'
  120. ' found'.format(name))
  121. log.debug("Created '%s' provider", name)
  122. return provider_class(config)
  123. def get_provider_class(self, name, get_mock=False):
  124. """
  125. Return a class for the requested provider.
  126. :type get_mock: ``bool``
  127. :param get_mock: If True, returns a mock version of the provider
  128. if available, or the real version if not.
  129. :rtype: provider class or ``None``
  130. :return: A class corresponding to the requested provider or ``None``
  131. if the provider was not found.
  132. """
  133. log.debug("Returning a class for the %s provider", name)
  134. impl = self.list_providers().get(name)
  135. if impl:
  136. if get_mock and impl.get("mock_class"):
  137. log.debug("param get_mock set to True, returning "
  138. "a mock version of the provider %s", name)
  139. return impl["mock_class"]
  140. else:
  141. log.debug("Returning the real version of %s", name)
  142. return impl["class"]
  143. else:
  144. log.debug("Provider with the name: %s not found", name)
  145. return None
  146. def get_all_provider_classes(self, get_mock=False):
  147. """
  148. Returns a list of classes for all available provider implementations
  149. :type get_mock: ``bool``
  150. :param get_mock: If True, returns a mock version of the provider
  151. if available, or the real version if not.
  152. :rtype: type ``class`` or ``None``
  153. :return: A list of all available provider classes or an empty list
  154. if none found.
  155. """
  156. all_providers = []
  157. for impl in self.list_providers().values():
  158. if get_mock and impl.get("mock_class"):
  159. log.debug("param get_mock set to True, appending "
  160. "a mock version of the provider %s", impl)
  161. all_providers.append(impl["mock_class"])
  162. else:
  163. all_providers.append(impl["class"])
  164. log.info("List of provider classes: %s", all_providers)
  165. return all_providers