]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Allow Cinder to call Nova client
authorAvishay Traeger <avishay@il.ibm.com>
Sun, 11 Aug 2013 16:40:10 +0000 (19:40 +0300)
committerAvishay Traeger <avishay@il.ibm.com>
Mon, 19 Aug 2013 10:38:56 +0000 (13:38 +0300)
This code allows Cinder to call Nova client functions. This will be used
for online migration and guest-assisted snapshots, which are both in
progress.

Change-Id: I9c94917bddcf250b880ca730df463a8402fe6b1d

cinder/api/middleware/auth.py
cinder/compute/__init__.py
cinder/compute/nova.py [new file with mode: 0644]
cinder/context.py
cinder/tests/compute/test_nova.py [new file with mode: 0644]

index a953b490fb56921164fcd9e1b3c9ebf03964a198..fadeb8d64b59a1ab688d902f8bc99fd09e302cf3 100644 (file)
@@ -28,6 +28,7 @@ import webob.exc
 
 from cinder.api.openstack import wsgi
 from cinder import context
+from cinder.openstack.common import jsonutils
 from cinder.openstack.common import log as logging
 from cinder import wsgi as base_wsgi
 
@@ -98,6 +99,16 @@ class CinderKeystoneContext(base_wsgi.Middleware):
 
         # Build a context, including the auth_token...
         remote_address = req.remote_addr
+
+        service_catalog = None
+        if req.headers.get('X_SERVICE_CATALOG') is not None:
+            try:
+                catalog_header = req.headers.get('X_SERVICE_CATALOG')
+                service_catalog = jsonutils.loads(catalog_header)
+            except ValueError:
+                raise webob.exc.HTTPInternalServerError(
+                    _('Invalid service catalog json.'))
+
         if CONF.use_forwarded_for:
             remote_address = req.headers.get('X-Forwarded-For', remote_address)
         ctx = context.RequestContext(user_id,
@@ -105,7 +116,8 @@ class CinderKeystoneContext(base_wsgi.Middleware):
                                      project_name=project_name,
                                      roles=roles,
                                      auth_token=auth_token,
-                                     remote_address=remote_address)
+                                     remote_address=remote_address,
+                                     service_catalog=service_catalog)
 
         req.environ['cinder.context'] = ctx
         return self.application
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c70d14109994e19c81e064e31aa2a91d7a28fe7a 100644 (file)
@@ -0,0 +1,34 @@
+# Copyright 2013 OpenStack Foundation.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import oslo.config.cfg
+
+import cinder.openstack.common.importutils
+
+_compute_opts = [
+    oslo.config.cfg.StrOpt('compute_api_class',
+                           default='cinder.compute.nova.API',
+                           help='The full class name of the '
+                                'compute API class to use'),
+]
+
+oslo.config.cfg.CONF.register_opts(_compute_opts)
+
+
+def API():
+    importutils = cinder.openstack.common.importutils
+    compute_api_class = oslo.config.cfg.CONF.compute_api_class
+    cls = importutils.import_class(compute_api_class)
+    return cls()
diff --git a/cinder/compute/nova.py b/cinder/compute/nova.py
new file mode 100644 (file)
index 0000000..9130f25
--- /dev/null
@@ -0,0 +1,99 @@
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Handles all requests to Nova.
+"""
+
+
+from novaclient import service_catalog
+from novaclient.v1_1 import client as nova_client
+from oslo.config import cfg
+
+from cinder.db import base
+from cinder.openstack.common import log as logging
+
+nova_opts = [
+    cfg.StrOpt('nova_catalog_info',
+               default='compute:nova:publicURL',
+               help='Info to match when looking for nova in the service '
+                    'catalog. Format is : separated values of the form: '
+                    '<service_type>:<service_name>:<endpoint_type>'),
+    cfg.StrOpt('nova_endpoint_template',
+               default=None,
+               help='Override service catalog lookup with template for nova '
+                    'endpoint e.g. http://localhost:8774/v2/%(tenant_id)s'),
+    cfg.StrOpt('os_region_name',
+               default=None,
+               help='region name of this node'),
+    cfg.StrOpt('nova_ca_certificates_file',
+               default=None,
+               help='Location of ca certicates file to use for nova client '
+                    'requests.'),
+    cfg.BoolOpt('nova_api_insecure',
+                default=False,
+                help='Allow to perform insecure SSL requests to nova'),
+]
+
+CONF = cfg.CONF
+CONF.register_opts(nova_opts)
+
+LOG = logging.getLogger(__name__)
+
+
+def novaclient(context):
+
+    # FIXME: the novaclient ServiceCatalog object is mis-named.
+    #        It actually contains the entire access blob.
+    # Only needed parts of the service catalog are passed in, see
+    # nova/context.py.
+    compat_catalog = {
+        'access': {'serviceCatalog': context.service_catalog or []}
+    }
+    sc = service_catalog.ServiceCatalog(compat_catalog)
+    if CONF.nova_endpoint_template:
+        url = CONF.nova_endpoint_template % context.to_dict()
+    else:
+        info = CONF.nova_catalog_info
+        service_type, service_name, endpoint_type = info.split(':')
+        # extract the region if set in configuration
+        if CONF.os_region_name:
+            attr = 'region'
+            filter_value = CONF.os_region_name
+        else:
+            attr = None
+            filter_value = None
+        url = sc.url_for(attr=attr,
+                         filter_value=filter_value,
+                         service_type=service_type,
+                         service_name=service_name,
+                         endpoint_type=endpoint_type)
+
+    LOG.debug(_('Novaclient connection created using URL: %s') % url)
+
+    c = nova_client.Client(context.user_id,
+                           context.auth_token,
+                           context.project_id,
+                           auth_url=url,
+                           insecure=CONF.nova_api_insecure,
+                           cacert=CONF.nova_ca_certificates_file)
+    # noauth extracts user_id:project_id from auth_token
+    c.client.auth_token = context.auth_token or '%s:%s' % (context.user_id,
+                                                           context.project_id)
+    c.client.management_url = url
+    return c
+
+
+class API(base.Base):
+    """API for interacting with novaclient."""
index faf97fb72068693271b31ef8adbc84c742008326..797bfc71bc817f3ba2d02fe87f4d105081d85d88 100644 (file)
@@ -45,7 +45,8 @@ class RequestContext(object):
     def __init__(self, user_id, project_id, is_admin=None, read_deleted="no",
                  roles=None, project_name=None, remote_address=None,
                  timestamp=None, request_id=None, auth_token=None,
-                 overwrite=True, quota_class=None, **kwargs):
+                 overwrite=True, quota_class=None, service_catalog=None,
+                 **kwargs):
         """
         :param read_deleted: 'no' indicates deleted records are hidden, 'yes'
             indicates deleted records are visible, 'only' indicates that
@@ -85,6 +86,14 @@ class RequestContext(object):
         if overwrite or not hasattr(local.store, 'context'):
             self.update_store()
 
+        if service_catalog:
+            # Only include required parts of service_catalog
+            self.service_catalog = [s for s in service_catalog
+                                    if s.get('type') in ('compute')]
+        else:
+            # if list is empty or none
+            self.service_catalog = []
+
     def _get_read_deleted(self):
         return self._read_deleted
 
@@ -115,6 +124,7 @@ class RequestContext(object):
                 'request_id': self.request_id,
                 'auth_token': self.auth_token,
                 'quota_class': self.quota_class,
+                'service_catalog': self.service_catalog,
                 'tenant': self.tenant,
                 'user': self.user}
 
diff --git a/cinder/tests/compute/test_nova.py b/cinder/tests/compute/test_nova.py
new file mode 100644 (file)
index 0000000..62633cf
--- /dev/null
@@ -0,0 +1,35 @@
+#    Copyright 2013 IBM Corp.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from cinderclient import exceptions as cinder_exception
+
+from cinder.compute import nova
+from cinder import context
+from cinder import exception
+from cinder import test
+
+
+class FakeNovaClient(object):
+    def __init__(self):
+        pass
+
+
+class NovaApiTestCase(test.TestCase):
+    def setUp(self):
+        super(NovaApiTestCase, self).setUp()
+
+        self.api = nova.API()
+        self.novaclient = FakeNovaClient()
+        self.ctx = context.get_admin_context()
+        self.mox.StubOutWithMock(nova, 'novaclient')