provider.py 12 KB

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