From: Avishay Traeger Date: Sun, 11 Aug 2013 16:40:10 +0000 (+0300) Subject: Allow Cinder to call Nova client X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=dee13e6a3660f94cbbc143151e9d4046882f2032;p=openstack-build%2Fcinder-build.git Allow Cinder to call Nova client 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 --- diff --git a/cinder/api/middleware/auth.py b/cinder/api/middleware/auth.py index a953b490f..fadeb8d64 100644 --- a/cinder/api/middleware/auth.py +++ b/cinder/api/middleware/auth.py @@ -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 diff --git a/cinder/compute/__init__.py b/cinder/compute/__init__.py index e69de29bb..c70d14109 100644 --- a/cinder/compute/__init__.py +++ b/cinder/compute/__init__.py @@ -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 index 000000000..9130f25eb --- /dev/null +++ b/cinder/compute/nova.py @@ -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: ' + '::'), + 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.""" diff --git a/cinder/context.py b/cinder/context.py index faf97fb72..797bfc71b 100644 --- a/cinder/context.py +++ b/cinder/context.py @@ -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 index 000000000..62633cfb2 --- /dev/null +++ b/cinder/tests/compute/test_nova.py @@ -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')