provider.py 12 KB

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