Просмотр исходного кода

Add support for using OpenStack Keystone v3

Enis Afgan 10 лет назад
Родитель
Сommit
7b67a97de0

+ 2 - 2
cloudbridge/cloud/base/provider.py

@@ -95,8 +95,8 @@ class BaseCloudProvider(CloudProvider):
         :param key: a field to look for in the ``self.config`` field
 
         :type default_value: anything
-        : param default_value: the default value to return if a value for the
-                               ``key`` is not available
+        :param default_value: the default value to return if a value for the
+                              ``key`` is not available
 
         :return: a configuration value for the supplied ``key``
         """

+ 4 - 3
cloudbridge/cloud/interfaces/services.py

@@ -978,11 +978,12 @@ class RegionService(PageableObjectMixin, CloudService):
     @abstractproperty
     def current(self):
         """
-        Returns the current region that this provider is connected
-        to
+        Returns the current region that this provider is connected to.
+
+        If the current region cannot be discovered, return ``None``.
 
         :rtype: ``object`` of :class:`.Region`
-        :return:  a Region instance
+        :return:  a Region instance or ``None``
         """
         pass
 

+ 124 - 29
cloudbridge/cloud/providers/openstack/provider.py

@@ -8,7 +8,6 @@ import os
 from cinderclient import client as cinder_client
 from keystoneclient import client as keystone_client
 from keystoneclient import session
-from keystoneclient.auth.identity import Password
 from neutronclient.v2_0 import client as neutron_client
 from novaclient import client as nova_client
 from novaclient import shell as nova_shell
@@ -41,6 +40,16 @@ class OpenStackCloudProvider(BaseCloudProvider):
             'os_auth_url', os.environ.get('OS_AUTH_URL', None))
         self.region_name = self._get_config_value(
             'os_region_name', os.environ.get('OS_REGION_NAME', None))
+        self.project_name = self._get_config_value(
+            'os_project_name', os.environ.get('OS_PROJECT_NAME', None))
+        self.project_domain_name = self._get_config_value(
+            'os_project_domain_name',
+            os.environ.get('OS_PROJECT_DOMAIN_NAME', None))
+        self.user_domain_name = self._get_config_value(
+            'os_user_domain_name', os.environ.get('OS_USER_DOMAIN_NAME', None))
+        self.identity_api_version = self._get_config_value(
+            'os_identity_api_version',
+            os.environ.get('OS_IDENTITY_API_VERSION', None))
         # Allow individual service connections to have their own values but
         # default to a the ones defined above.
         self.swift_username = self._get_config_value(
@@ -86,6 +95,46 @@ class OpenStackCloudProvider(BaseCloudProvider):
             self._keystone = self._connect_keystone()
         return self._keystone
 
+    @property
+    def _keystone_version(self):
+        """
+        Return the numberic version of remote server Keystone.
+
+        :rtype: ``int``
+        :return: Keystone version as an int (currently, 2 or 3).
+        """
+        ks_version = keystone_client.Client(auth_url=self.auth_url).version
+        if ks_version == 'v3':
+            return 3
+        return 2
+
+    @property
+    def _keystone_session(self):
+        """
+        Connect to Keystone and return a session object.
+
+        :rtype: :class:`keystoneclient.session.Session`
+        :return: A Keystone session object.
+        """
+        def connect_v2():
+            from keystoneclient.auth.identity import Password as password_v2
+            auth = password_v2(self.auth_url, username=self.username,
+                               password=self.password,
+                               tenant_name=self.tenant_name)
+            return session.Session(auth=auth)
+
+        def connect_v3():
+            from keystoneclient.auth.identity.v3 import Password as password_v3
+            auth = password_v3(auth_url=self.auth_url,
+                               username=self.username,
+                               password=self.password,
+                               user_domain_name=self.user_domain_name,
+                               project_domain_name=self.project_domain_name,
+                               project_name=self.project_name)
+            return session.Session(auth=auth)
+
+        return connect_v3() if self._keystone_version == 3 else connect_v2()
+
 #     @property
 #     def glance(self):
 #         if not self._glance:
@@ -137,6 +186,27 @@ class OpenStackCloudProvider(BaseCloudProvider):
         """
         Get an OpenStack Nova (compute) client object for the given cloud.
         """
+        def connect_pwd():
+            """
+            Connect using username and password parameters.
+            """
+            nova = nova_client.Client(
+                api_version, username=self.username, api_key=self.password,
+                project_id=self.tenant_name, auth_url=self.auth_url,
+                region_name=region_name, service_name=service_name,
+                http_log_debug=True if self.config.debug_mode else False)
+            nova.authenticate()
+            return nova
+
+        def connect_sess():
+            """
+            Connect using a Keystone session object.
+            """
+            return nova_client.Client(
+                api_version, session=self._keystone_session,
+                service_name=service_name,
+                http_log_debug=True if self.config.debug_mode else False)
+
         api_version = self._get_config_value(
             'os_compute_api_version',
             os.environ.get('OS_COMPUTE_API_VERSION', 2))
@@ -147,46 +217,59 @@ class OpenStackCloudProvider(BaseCloudProvider):
         if self.config.debug_mode:
             nova_shell.OpenStackComputeShell().setup_debugging(True)
 
-        nova = nova_client.Client(
-            api_version, username=self.username, api_key=self.password,
-            project_id=self.tenant_name, auth_url=self.auth_url,
-            region_name=region_name, service_name=service_name,
-            http_log_debug=True if self.config.debug_mode else False)
-        nova.authenticate()
-        return nova
+        return connect_sess() if self._keystone_version == 3 else connect_pwd()
 
     def _connect_keystone(self):
         """
         Get an OpenStack Keystone (identity) client object for the given cloud.
         """
-        auth = Password(self.auth_url, username=self.username,
-                        password=self.password, tenant_name=self.tenant_name)
-        # Wow, the internal keystoneV2 implementation is terribly buggy. It
-        # needs both a separate Session object and the username, password again
-        # for things to work correctly. Plus, a manual call to authenticate()
-        # is also required if the service catalogue needs to be queried.
-        keystone = keystone_client.Client(
-            session=session.Session(auth=auth),
-            auth_url=self.auth_url,
-            username=self.username,
-            password=self.password,
-            tenant_name=self.tenant_name,
-            region_name=self.region_name)
-        keystone.authenticate()
-        return keystone
+        def connect_v2():
+            # Wow, the internal keystoneV2 implementation is terribly buggy. It
+            # needs both a separate Session object and the username, password
+            # again for things to work correctly. Plus, a manual call to
+            # authenticate() is also required if the service catalogue needs
+            # to be queried.
+            keystone = keystone_client.Client(
+                session=self._keystone_session,
+                auth_url=self.auth_url,
+                username=self.username,
+                password=self.password,
+                tenant_name=self.tenant_name,
+                region_name=self.region_name)
+            keystone.authenticate()
+            return keystone
+
+        def connect_v3():
+            return keystone_client.Client(session=self._keystone_session,
+                                          auth_url=self.auth_url)
+
+        return connect_v3() if self._keystone_version == 3 else connect_v2()
 
     def _connect_cinder(self):
         """
         Get an OpenStack Cinder (block storage) client object for the given
         cloud.
         """
+        def connect_pwd():
+            """
+            Connect using username and password parameters.
+            """
+            return cinder_client.Client(
+                api_version, username=self.username, api_key=self.password,
+                project_id=self.tenant_name, auth_url=self.auth_url)
+
+        def connect_sess():
+            """
+            Connect using a Keystone session object.
+            """
+            return cinder_client.Client(
+                api_version, session=self._keystone_session)
+
         api_version = self._get_config_value(
             'os_volume_api_version',
             os.environ.get('OS_VOLUME_API_VERSION', 2))
 
-        return cinder_client.Client(
-            api_version, username=self.username, api_key=self.password,
-            project_id=self.tenant_name, auth_url=self.auth_url)
+        return connect_sess() if self._keystone_version == 3 else connect_pwd()
 
 #     def _connect_glance(self):
 #         """
@@ -217,6 +300,18 @@ class OpenStackCloudProvider(BaseCloudProvider):
         Get an OpenStack Neutron (networking) client object for the given
         cloud.
         """
-        return neutron_client.Client(
-            username=self.username, password=self.password,
-            tenant_name=self.tenant_name, auth_url=self.auth_url)
+        def connect_pwd():
+            """
+            Connect using username and password parameters.
+            """
+            return neutron_client.Client(
+                username=self.username, password=self.password,
+                tenant_name=self.tenant_name, auth_url=self.auth_url)
+
+        def connect_sess():
+            """
+            Connect using a Keystone session object.
+            """
+            return neutron_client.Client(session=self._keystone_session)
+
+        return connect_sess() if self._keystone_version == 3 else connect_pwd()

+ 5 - 2
cloudbridge/cloud/providers/openstack/resources.py

@@ -27,6 +27,7 @@ import json
 
 import ipaddress
 
+from keystoneclient.v3.regions import Region
 import novaclient.exceptions as novaex
 import swiftclient.exceptions as swiftex
 
@@ -384,11 +385,13 @@ class OpenStackRegion(BaseRegion):
 
     @property
     def id(self):
-        return self._os_region
+        return (self._os_region.id if type(self._os_region) == Region else
+                self._os_region)
 
     @property
     def name(self):
-        return self._os_region
+        return (self._os_region.id if type(self._os_region) == Region else
+                self._os_region)
 
     @property
     def zones(self):

+ 32 - 20
cloudbridge/cloud/providers/openstack/services.py

@@ -454,29 +454,41 @@ class OpenStackRegionService(BaseRegionService):
         return next(region, None)
 
     def list(self, limit=None, marker=None):
-        # TODO: KeyStone V3 onwards will support directly listing regions
-        # but for now, this convoluted method is necessary
-        regions = (
-            endpoint.get('region') or endpoint.get('region_id')
-            for svc in self.provider.keystone.service_catalog.get_data()
-            for endpoint in svc.get('endpoints', [])
-        )
-        regions = set(region for region in regions if region)
-        os_regions = [OpenStackRegion(self.provider, region)
-                      for region in regions]
-
-        return ClientPagedResultList(self.provider, os_regions,
-                                     limit=limit, marker=marker)
+        def keystone_v2():
+            # Keystone v3 onwards supports directly listing regions
+            # but for v2, this convoluted method is necessary.
+            regions = (
+                endpoint.get('region') or endpoint.get('region_id')
+                for svc in self.provider.keystone.service_catalog.get_data()
+                for endpoint in svc.get('endpoints', [])
+            )
+            regions = set(region for region in regions if region)
+            os_regions = [OpenStackRegion(self.provider, region)
+                          for region in regions]
+
+            return ClientPagedResultList(self.provider, os_regions,
+                                         limit=limit, marker=marker)
+
+        def keystone_v3():
+            os_regions = [OpenStackRegion(self.provider, region)
+                          for region in self.provider.keystone.regions.list()]
+            return ClientPagedResultList(self.provider, os_regions,
+                                         limit=limit, marker=marker)
+
+        return keystone_v3() if self.provider._keystone_version == 3 else \
+            keystone_v2()  # pylint:disable=protected-access
 
     @property
     def current(self):
-        nova_region = [
-            endpoint.get('region') or endpoint.get('region_id')
-            for svc in self.provider.keystone.service_catalog.get_data()
-            for endpoint in svc.get('endpoints', [])
-            if endpoint.get('publicURL', None) ==
-            self.provider.nova.client.management_url]
-        return self.get(nova_region[0])
+        if self.provider.keystone.has_service_catalog():
+            nova_region = [
+                endpoint.get('region') or endpoint.get('region_id')
+                for svc in self.provider.keystone.service_catalog.get_data()
+                for endpoint in svc.get('endpoints', [])
+                if endpoint.get('publicURL', None) ==
+                self.provider.nova.client.management_url]
+            return self.get(nova_region[0])
+        return None
 
 
 class OpenStackComputeService(BaseComputeService):