provider.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. """Provider implementation based on OpenStack Python clients for OpenStack."""
  2. import inspect
  3. import os
  4. from cinderclient import client as cinder_client
  5. from cloudbridge.cloud.base import BaseCloudProvider
  6. from keystoneauth1 import session
  7. from keystoneclient import client as keystone_client
  8. from neutronclient.v2_0 import client as neutron_client
  9. from novaclient import client as nova_client
  10. from novaclient import shell as nova_shell
  11. from swiftclient import client as swift_client
  12. from .services import OpenStackBlockStoreService
  13. from .services import OpenStackComputeService
  14. from .services import OpenStackNetworkService
  15. from .services import OpenStackObjectStoreService
  16. from .services import OpenStackSecurityService
  17. class OpenStackCloudProvider(BaseCloudProvider):
  18. """OpenStack provider implementation."""
  19. PROVIDER_ID = 'openstack'
  20. def __init__(self, config):
  21. super(OpenStackCloudProvider, self).__init__(config)
  22. self.cloud_type = 'openstack'
  23. # Initialize cloud connection fields
  24. self.username = self._get_config_value(
  25. 'os_username', os.environ.get('OS_USERNAME', None))
  26. self.password = self._get_config_value(
  27. 'os_password', os.environ.get('OS_PASSWORD', None))
  28. self.project_name = self._get_config_value(
  29. 'os_project_name', os.environ.get('OS_PROJECT_NAME', None) or
  30. os.environ.get('OS_TENANT_NAME', None))
  31. self.auth_url = self._get_config_value(
  32. 'os_auth_url', os.environ.get('OS_AUTH_URL', None))
  33. self.region_name = self._get_config_value(
  34. 'os_region_name', os.environ.get('OS_REGION_NAME', None))
  35. self.project_domain_name = self._get_config_value(
  36. 'os_project_domain_name',
  37. os.environ.get('OS_PROJECT_DOMAIN_NAME', None))
  38. self.user_domain_name = self._get_config_value(
  39. 'os_user_domain_name', os.environ.get('OS_USER_DOMAIN_NAME', None))
  40. # Service connections, lazily initialized
  41. self._nova = None
  42. self._keystone = None
  43. self._glance = None
  44. self._cinder = None
  45. self._swift = None
  46. self._neutron = None
  47. # Additional cached variables
  48. self._cached_keystone_session = None
  49. # Initialize provider services
  50. self._compute = OpenStackComputeService(self)
  51. self._network = OpenStackNetworkService(self)
  52. self._security = OpenStackSecurityService(self)
  53. self._block_store = OpenStackBlockStoreService(self)
  54. self._object_store = OpenStackObjectStoreService(self)
  55. @property
  56. def nova(self):
  57. if not self._nova:
  58. self._nova = self._connect_nova()
  59. return self._nova
  60. @property
  61. def keystone(self):
  62. if not self._keystone:
  63. self._keystone = self._connect_keystone()
  64. return self._keystone
  65. @property
  66. def _keystone_version(self):
  67. """
  68. Return the numeric version of remote Keystone server.
  69. :rtype: ``int``
  70. :return: Keystone version as an int (currently, 2 or 3).
  71. """
  72. ks_version = keystone_client.Client(auth_url=self.auth_url).version
  73. if ks_version == 'v3':
  74. return 3
  75. return 2
  76. @property
  77. def _keystone_session(self):
  78. """
  79. Connect to Keystone and return a session object.
  80. :rtype: :class:`keystoneauth1.session.Session`
  81. :return: A Keystone session object.
  82. """
  83. if self._cached_keystone_session:
  84. return self._cached_keystone_session
  85. if self._keystone_version == 3:
  86. from keystoneauth1.identity.v3 import Password as Password_v3
  87. auth = Password_v3(auth_url=self.auth_url,
  88. username=self.username,
  89. password=self.password,
  90. user_domain_name=self.user_domain_name,
  91. project_domain_name=self.project_domain_name,
  92. project_name=self.project_name)
  93. self._cached_keystone_session = session.Session(auth=auth)
  94. else:
  95. from keystoneauth1.identity.v2 import Password as Password_v2
  96. auth = Password_v2(self.auth_url, username=self.username,
  97. password=self.password,
  98. tenant_name=self.project_name)
  99. self._cached_keystone_session = session.Session(auth=auth)
  100. return self._cached_keystone_session
  101. # @property
  102. # def glance(self):
  103. # if not self._glance:
  104. # self._glance = self._connect_glance()
  105. # return self._glance
  106. @property
  107. def cinder(self):
  108. if not self._cinder:
  109. self._cinder = self._connect_cinder()
  110. return self._cinder
  111. @property
  112. def swift(self):
  113. if not self._swift:
  114. self._swift = self._connect_swift()
  115. return self._swift
  116. @property
  117. def neutron(self):
  118. if not self._neutron:
  119. self._neutron = self._connect_neutron()
  120. return self._neutron
  121. @property
  122. def compute(self):
  123. return self._compute
  124. @property
  125. def network(self):
  126. return self._network
  127. @property
  128. def security(self):
  129. return self._security
  130. @property
  131. def block_store(self):
  132. return self._block_store
  133. @property
  134. def object_store(self):
  135. return self._object_store
  136. def _connect_nova(self):
  137. return self._connect_nova_region(self.region_name)
  138. def _connect_nova_region(self, region_name):
  139. """Get an OpenStack Nova (compute) client object."""
  140. # Force reauthentication with Keystone
  141. self._cached_keystone_session = None
  142. api_version = self._get_config_value(
  143. 'os_compute_api_version',
  144. os.environ.get('OS_COMPUTE_API_VERSION', 2))
  145. service_name = self._get_config_value(
  146. 'nova_service_name',
  147. os.environ.get('NOVA_SERVICE_NAME', None))
  148. if self.config.debug_mode:
  149. nova_shell.OpenStackComputeShell().setup_debugging(True)
  150. nova = nova_client.Client(
  151. api_version, session=self._keystone_session,
  152. auth_url=self.auth_url,
  153. region_name=region_name,
  154. service_name=service_name,
  155. http_log_debug=True if self.config.debug_mode else False)
  156. return nova
  157. def _connect_keystone(self):
  158. """Get an OpenStack Keystone (identity) client object."""
  159. if self._keystone_version == 3:
  160. return keystone_client.Client(session=self._keystone_session,
  161. auth_url=self.auth_url)
  162. else:
  163. # Wow, the internal keystoneV2 implementation is terribly buggy. It
  164. # needs both a separate Session object and the username, password
  165. # again for things to work correctly. Plus, a manual call to
  166. # authenticate() is also required if the service catalog needs
  167. # to be queried.
  168. keystone = keystone_client.Client(
  169. session=self._keystone_session,
  170. auth_url=self.auth_url,
  171. username=self.username,
  172. password=self.password,
  173. project_name=self.project_name,
  174. region_name=self.region_name)
  175. keystone.authenticate()
  176. return keystone
  177. def _connect_cinder(self):
  178. """Get an OpenStack Cinder (block storage) client object."""
  179. api_version = self._get_config_value(
  180. 'os_volume_api_version',
  181. os.environ.get('OS_VOLUME_API_VERSION', 2))
  182. return cinder_client.Client(api_version,
  183. auth_url=self.auth_url,
  184. session=self._keystone_session,
  185. region_name=self.region_name)
  186. # def _connect_glance(self):
  187. # """
  188. # Get an OpenStack Glance (VM images) client object for the given
  189. # cloud.
  190. # """
  191. # api_version = self._get_config_value(
  192. # 'os_image_api_version',
  193. # os.environ.get('OS_IMAGE_API_VERSION', 1))
  194. #
  195. # return glance_client.Client(version=api_version,
  196. # session=self.keystone.session)
  197. @staticmethod
  198. def _clean_options(options, method_to_match):
  199. """
  200. Returns a **copy** of the source options with all keys that are not in
  201. the ``method_to_match`` parameter list removed.
  202. .. note:: If ``options`` has the ``os_options`` key it will have
  203. both the key and its value removed. This is because any entries
  204. in this dictionary value will override our settings. This
  205. situation is only going to happen when the `_connect_swift`
  206. method is called by the SwiftService to manufacture new
  207. connections.
  208. .. seealso::
  209. https://docs.openstack.org/developer/python-swiftclient/swiftclient.html#module-swiftclient.client
  210. :param options: The source options.
  211. :type options: ``dict``
  212. :param method_to_match: The method whose signature is to be matched
  213. :type method_to_match: A callable
  214. :return: A copy of the source options with all keys that are not in the
  215. ``method_to_match`` parameter list removed. If options is ``None``
  216. then this will be an empty dictionary
  217. :rtype: ``dict``
  218. """
  219. result = {}
  220. if options:
  221. try:
  222. method_signature = inspect.signature(method_to_match)
  223. parameters = set(method_signature.parameters.keys())
  224. except AttributeError:
  225. parameters = set(inspect.getargspec(method_to_match).args)
  226. result = {key: val for key, val in options.items() if
  227. key in parameters}
  228. # Don't allow the options to override our authentication
  229. result.pop('os_options', None)
  230. return result
  231. def _connect_swift(self, options=None):
  232. """
  233. Get an OpenStack Swift (object store) client connection.
  234. :param options: A dictionary of options from which values will be
  235. passed to the connection.
  236. :return: A Swift client connection using the auth credentials held by
  237. the OpenStackCloudProvider instance
  238. """
  239. clean_options = self._clean_options(options,
  240. swift_client.Connection.__init__)
  241. storage_url = self._get_config_value(
  242. 'os_storage_url', os.environ.get('OS_STORAGE_URL', None))
  243. auth_token = self._get_config_value(
  244. 'os_auth_token', os.environ.get('OS_AUTH_TOKEN', None))
  245. if storage_url and auth_token:
  246. clean_options['preauthurl'] = storage_url
  247. clean_options['preauthtoken'] = auth_token
  248. else:
  249. clean_options['authurl'] = self.auth_url
  250. clean_options['session'] = self._keystone_session
  251. return swift_client.Connection(**clean_options)
  252. def _connect_neutron(self):
  253. """Get an OpenStack Neutron (networking) client object cloud."""
  254. return neutron_client.Client(auth_url=self.auth_url,
  255. session=self._keystone_session,
  256. region_name=self.region_name)