factory.py 7.7 KB

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