factory.py 6.7 KB

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