Use session in cinderclient

Use the common session loading parameters and the session object for
talking to cinder.

There are some related changes in this patch.

Firstly auth_token middleware now provides an authentication plugin that
can be used along with the session object to make requests under the
user's authentication. This will largely replace the information
required on the context object.

This authentication plugin is not serializable though and so it cannot
be transferred over RPC so we introduce a simple authentication plugin
that reconstructs the required information from the context.

When talking to cinder we now create a global session object (think of
this like keeping open a connection pool object) and use the
authentication plugin to send requests to cinder.

I also condense the cinder tests as they are largely copied and pasted
between v1 and v2 and this solves fixing them in two places.

DocImpact: Renames cinder's timeout, insecure and CA certificates
parameters to the parameters used by the common session object. Adds
options for using client certificates with connection.

Change-Id: I7afe604503b8597c16be61d2a66a10b94269a219
This commit is contained in:
Jamie Lennox 2014-08-29 13:28:06 +10:00
parent a949f64073
commit 4919269542
7 changed files with 304 additions and 455 deletions

View File

@ -139,6 +139,10 @@ class NovaKeystoneContext(wsgi.Middleware):
raise webob.exc.HTTPInternalServerError(
_('Invalid service catalog json.'))
# NOTE(jamielennox): This is a full auth plugin set by auth_token
# middleware in newer versions.
user_auth_plugin = req.environ.get('keystone.token_auth')
ctx = context.RequestContext(user_id,
project_id,
user_name=user_name,
@ -147,7 +151,8 @@ class NovaKeystoneContext(wsgi.Middleware):
auth_token=auth_token,
remote_address=remote_address,
service_catalog=service_catalog,
request_id=req_id)
request_id=req_id,
user_auth_plugin=user_auth_plugin)
req.environ['nova.context'] = ctx
return self.application

View File

@ -38,6 +38,7 @@ from cinderclient import exceptions as cinder_exception
import eventlet.event
from eventlet import greenthread
import eventlet.timeout
from keystoneclient import exceptions as keystone_exception
from oslo.config import cfg
from oslo import messaging
from oslo.serialization import jsonutils
@ -2404,7 +2405,8 @@ class ComputeManager(manager.Manager):
except exception.VolumeNotFound as exc:
LOG.debug('Ignoring VolumeNotFound: %s', exc,
instance=instance)
except cinder_exception.EndpointNotFound as exc:
except (cinder_exception.EndpointNotFound,
keystone_exception.EndpointNotFound) as exc:
LOG.warn(_LW('Ignoring EndpointNotFound: %s'), exc,
instance=instance)

View File

@ -19,6 +19,8 @@
import copy
from keystoneclient import auth
from keystoneclient import service_catalog
from oslo.utils import timeutils
import six
@ -33,6 +35,32 @@ from nova import policy
LOG = logging.getLogger(__name__)
class _ContextAuthPlugin(auth.BaseAuthPlugin):
"""A keystoneclient auth plugin that uses the values from the Context.
Ideally we would use the plugin provided by auth_token middleware however
this plugin isn't serialized yet so we construct one from the serialized
auth data.
"""
def __init__(self, auth_token, sc):
super(_ContextAuthPlugin, self).__init__()
self.auth_token = auth_token
sc = {'serviceCatalog': sc}
self.service_catalog = service_catalog.ServiceCatalogV2(sc)
def get_token(self, *args, **kwargs):
return self.auth_token
def get_endpoint(self, session, service_type=None, interface=None,
region_name=None, service_name=None, **kwargs):
return self.service_catalog.url_for(service_type=service_type,
service_name=service_name,
endpoint_type=interface,
region_name=region_name)
class RequestContext(object):
"""Security context and request information.
@ -44,15 +72,18 @@ class RequestContext(object):
roles=None, remote_address=None, timestamp=None,
request_id=None, auth_token=None, overwrite=True,
quota_class=None, user_name=None, project_name=None,
service_catalog=None, instance_lock_checked=False, **kwargs):
service_catalog=None, instance_lock_checked=False,
user_auth_plugin=None, **kwargs):
""":param read_deleted: 'no' indicates deleted records are hidden,
'yes' indicates deleted records are visible,
'only' indicates that *only* deleted records are visible.
:param overwrite: Set to False to ensure that the greenthread local
copy of the index is not overwritten.
:param user_auth_plugin: The auth plugin for the current request's
authentication data.
:param kwargs: Extra arguments that might be present, but we ignore
because they possibly came in from older rpc messages.
"""
@ -92,11 +123,18 @@ class RequestContext(object):
self.user_name = user_name
self.project_name = project_name
self.is_admin = is_admin
self.user_auth_plugin = user_auth_plugin
if self.is_admin is None:
self.is_admin = policy.check_is_admin(self)
if overwrite or not hasattr(local.store, 'context'):
self.update_store()
def get_auth_plugin(self):
if self.user_auth_plugin:
return self.user_auth_plugin
else:
return _ContextAuthPlugin(self.auth_token, self.service_catalog)
def _get_read_deleted(self):
return self._read_deleted

View File

@ -12,11 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient import exceptions as cinder_exception
from cinderclient.v1 import client as cinder_client_v1
from cinderclient.v2 import client as cinder_client_v2
import mock
import six.moves.urllib.parse as urlparse
from requests_mock.contrib import fixture
from testtools import matchers
from nova import context
from nova import exception
@ -24,382 +23,171 @@ from nova import test
from nova.volume import cinder
def _stub_volume(**kwargs):
volume = {
'display_name': None,
'display_description': None,
"attachments": [],
"availability_zone": "cinder",
"created_at": "2012-09-10T00:00:00.000000",
"id": '00000000-0000-0000-0000-000000000000',
"metadata": {},
"size": 1,
"snapshot_id": None,
"status": "available",
"volume_type": "None",
"bootable": "true"
}
volume.update(kwargs)
return volume
def _stub_volume_v2(**kwargs):
volume_v2 = {
'name': None,
'description': None,
"attachments": [],
"availability_zone": "cinderv2",
"created_at": "2013-08-10T00:00:00.000000",
"id": '00000000-0000-0000-0000-000000000000',
"metadata": {},
"size": 1,
"snapshot_id": None,
"status": "available",
"volume_type": "None",
"bootable": "true"
}
volume_v2.update(kwargs)
return volume_v2
_image_metadata = {
'kernel_id': 'fake',
'ramdisk_id': 'fake'
}
class FakeHTTPClient(cinder.cinder_client.HTTPClient):
def _cs_request(self, url, method, **kwargs):
# Check that certain things are called correctly
if method in ['GET', 'DELETE']:
assert 'body' not in kwargs
elif method == 'PUT':
assert 'body' in kwargs
# Call the method
args = urlparse.parse_qsl(urlparse.urlparse(url)[4])
kwargs.update(args)
munged_url = url.rsplit('?', 1)[0]
munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_')
munged_url = munged_url.replace('-', '_')
callback = "%s_%s" % (method.lower(), munged_url)
if not hasattr(self, callback):
raise AssertionError('Called unknown API method: %s %s, '
'expected fakes method name: %s' %
(method, url, callback))
# Note the call
self.callstack.append((method, url, kwargs.get('body', None)))
status, body = getattr(self, callback)(**kwargs)
if hasattr(status, 'items'):
return status, body
else:
return {"status": status}, body
def get_volumes_1234(self, **kw):
volume = {'volume': _stub_volume(id='1234')}
return (200, volume)
def get_volumes_nonexisting(self, **kw):
raise cinder_exception.NotFound(code=404, message='Resource not found')
def get_volumes_5678(self, **kw):
"""Volume with image metadata."""
volume = {'volume': _stub_volume(id='1234',
volume_image_metadata=_image_metadata)
}
return (200, volume)
class FakeHTTPClientV2(cinder.cinder_client.HTTPClient):
def _cs_request(self, url, method, **kwargs):
# Check that certain things are called correctly
if method in ['GET', 'DELETE']:
assert 'body' not in kwargs
elif method == 'PUT':
assert 'body' in kwargs
# Call the method
args = urlparse.parse_qsl(urlparse.urlparse(url)[4])
kwargs.update(args)
munged_url = url.rsplit('?', 1)[0]
munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_')
munged_url = munged_url.replace('-', '_')
callback = "%s_%s" % (method.lower(), munged_url)
if not hasattr(self, callback):
raise AssertionError('Called unknown API method: %s %s, '
'expected fakes method name: %s' %
(method, url, callback))
# Note the call
self.callstack.append((method, url, kwargs.get('body', None)))
status, body = getattr(self, callback)(**kwargs)
if hasattr(status, 'items'):
return status, body
else:
return {"status": status}, body
def get_volumes_1234(self, **kw):
volume = {'volume': _stub_volume_v2(id='1234')}
return (200, volume)
def get_volumes_nonexisting(self, **kw):
raise cinder_exception.NotFound(code=404, message='Resource not found')
def get_volumes_5678(self, **kw):
"""Volume with image metadata."""
volume = {'volume': _stub_volume_v2(
id='1234',
volume_image_metadata=_image_metadata)
}
return (200, volume)
class FakeCinderClient(cinder_client_v1.Client):
def __init__(self, username, password, project_id=None, auth_url=None,
insecure=False, retries=None, cacert=None, timeout=None):
super(FakeCinderClient, self).__init__(username, password,
project_id=project_id,
auth_url=auth_url,
insecure=insecure,
retries=retries,
cacert=cacert,
timeout=timeout)
self.client = FakeHTTPClient(username, password, project_id, auth_url,
insecure=insecure, retries=retries,
cacert=cacert, timeout=timeout)
# keep a ref to the clients callstack for factory's assert_called
self.callstack = self.client.callstack = []
class FakeCinderClientV2(cinder_client_v2.Client):
def __init__(self, username, password, project_id=None, auth_url=None,
insecure=False, retries=None, cacert=None, timeout=None):
super(FakeCinderClientV2, self).__init__(username, password,
project_id=project_id,
auth_url=auth_url,
insecure=insecure,
retries=retries,
cacert=cacert,
timeout=timeout)
self.client = FakeHTTPClientV2(username, password, project_id,
auth_url, insecure=insecure,
retries=retries, cacert=cacert,
timeout=timeout)
# keep a ref to the clients callstack for factory's assert_called
self.callstack = self.client.callstack = []
class FakeClientFactory(object):
"""Keep a ref to the FakeClient since volume.api.cinder throws it away."""
def __call__(self, *args, **kwargs):
self.client = FakeCinderClient(*args, **kwargs)
return self.client
def assert_called(self, method, url, body=None, pos=-1):
expected = (method, url)
called = self.client.callstack[pos][0:2]
assert self.client.callstack, ("Expected %s %s but no calls "
"were made." % expected)
assert expected == called, 'Expected %s %s; got %s %s' % (expected +
called)
if body is not None:
assert self.client.callstack[pos][2] == body
class FakeClientV2Factory(object):
"""Keep a ref to the FakeClient since volume.api.cinder throws it away."""
def __call__(self, *args, **kwargs):
self.client = FakeCinderClientV2(*args, **kwargs)
return self.client
def assert_called(self, method, url, body=None, pos=-1):
expected = (method, url)
called = self.client.callstack[pos][0:2]
assert self.client.callstack, ("Expected %s %s but no calls "
"were made." % expected)
assert expected == called, 'Expected %s %s; got %s %s' % (expected +
called)
if body is not None:
assert self.client.callstack[pos][2] == body
fake_client_factory = FakeClientFactory()
fake_client_v2_factory = FakeClientV2Factory()
@mock.patch.object(cinder_client_v1, 'Client', fake_client_factory)
class CinderTestCase(test.NoDBTestCase):
"""Test case for cinder volume v1 api."""
class BaseCinderTestCase(object):
def setUp(self):
super(CinderTestCase, self).setUp()
catalog = [{
"type": "volume",
"name": "cinder",
"endpoints": [{"publicURL": "http://localhost:8776/v1/project_id"}]
}]
cinder.CONF.set_override('catalog_info',
'volume:cinder:publicURL', group='cinder')
self.context = context.RequestContext('username', 'project_id',
service_catalog=catalog)
cinder.cinderclient(self.context)
super(BaseCinderTestCase, self).setUp()
cinder.reset_globals()
self.requests = self.useFixture(fixture.Fixture())
self.api = cinder.API()
def assert_called(self, *args, **kwargs):
fake_client_factory.assert_called(*args, **kwargs)
self.context = context.RequestContext('username',
'project_id',
auth_token='token',
service_catalog=self.CATALOG)
def flags(self, *args, **kwargs):
super(BaseCinderTestCase, self).flags(*args, **kwargs)
cinder.reset_globals()
def create_client(self):
return cinder.cinderclient(self.context)
def test_context_with_catalog(self):
self.api.get(self.context, '1234')
self.assert_called('GET', '/volumes/1234')
self.assertEqual(
fake_client_factory.client.client.management_url,
'http://localhost:8776/v1/project_id')
def test_cinder_endpoint_template(self):
self.flags(
endpoint_template='http://other_host:8776/v1/%(project_id)s',
group='cinder'
)
self.api.get(self.context, '1234')
self.assert_called('GET', '/volumes/1234')
self.assertEqual(
fake_client_factory.client.client.management_url,
'http://other_host:8776/v1/project_id')
def test_get_non_existing_volume(self):
self.assertRaises(exception.VolumeNotFound, self.api.get, self.context,
'nonexisting')
def test_volume_with_image_metadata(self):
volume = self.api.get(self.context, '5678')
self.assert_called('GET', '/volumes/5678')
self.assertIn('volume_image_metadata', volume)
self.assertEqual(volume['volume_image_metadata'], _image_metadata)
def test_cinder_api_insecure(self):
# The True/False negation is awkward, but better for the client
# to pass us insecure=True and we check verify_cert == False
self.flags(api_insecure=True, group='cinder')
self.api.get(self.context, '1234')
self.assert_called('GET', '/volumes/1234')
self.assertEqual(
fake_client_factory.client.client.verify_cert, False)
def test_cinder_api_cacert_file(self):
cacert = "/etc/ssl/certs/ca-certificates.crt"
self.flags(ca_certificates_file=cacert, group='cinder')
self.api.get(self.context, '1234')
self.assert_called('GET', '/volumes/1234')
self.assertEqual(
fake_client_factory.client.client.verify_cert, cacert)
self.assertEqual(self.URL, self.create_client().client.get_endpoint())
def test_cinder_http_retries(self):
retries = 42
self.flags(http_retries=retries, group='cinder')
self.api.get(self.context, '1234')
self.assert_called('GET', '/volumes/1234')
self.assertEqual(
fake_client_factory.client.client.retries, retries)
@mock.patch.object(cinder_client_v2, 'Client', fake_client_v2_factory)
class CinderV2TestCase(test.NoDBTestCase):
"""Test case for cinder volume v2 api."""
def setUp(self):
super(CinderV2TestCase, self).setUp()
catalog = [{
"type": "volumev2",
"name": "cinderv2",
"endpoints": [{"publicURL": "http://localhost:8776/v2/project_id"}]
}]
self.context = context.RequestContext('username', 'project_id',
service_catalog=catalog)
cinder.cinderclient(self.context)
self.api = cinder.API()
def tearDown(self):
cinder.CONF.reset()
super(CinderV2TestCase, self).tearDown()
def assert_called(self, *args, **kwargs):
fake_client_v2_factory.assert_called(*args, **kwargs)
def test_context_with_catalog(self):
self.api.get(self.context, '1234')
self.assert_called('GET', '/volumes/1234')
self.assertEqual(
'http://localhost:8776/v2/project_id',
fake_client_v2_factory.client.client.management_url)
def test_cinder_endpoint_template(self):
self.flags(
endpoint_template='http://other_host:8776/v2/%(project_id)s',
group='cinder'
)
self.api.get(self.context, '1234')
self.assert_called('GET', '/volumes/1234')
self.assertEqual(
'http://other_host:8776/v2/project_id',
fake_client_v2_factory.client.client.management_url)
def test_get_non_existing_volume(self):
self.assertRaises(exception.VolumeNotFound, self.api.get, self.context,
'nonexisting')
def test_volume_with_image_metadata(self):
volume = self.api.get(self.context, '5678')
self.assert_called('GET', '/volumes/5678')
self.assertIn('volume_image_metadata', volume)
self.assertEqual(_image_metadata, volume['volume_image_metadata'])
self.assertEqual(retries, self.create_client().client.connect_retries)
def test_cinder_api_insecure(self):
# The True/False negation is awkward, but better for the client
# to pass us insecure=True and we check verify_cert == False
self.flags(api_insecure=True, group='cinder')
self.api.get(self.context, '1234')
self.assert_called('GET', '/volumes/1234')
self.assertFalse(fake_client_v2_factory.client.client.verify_cert)
def test_cinder_api_cacert_file(self):
cacert = "/etc/ssl/certs/ca-certificates.crt"
self.flags(ca_certificates_file=cacert, group='cinder')
self.api.get(self.context, '1234')
self.assert_called('GET', '/volumes/1234')
self.assertEqual(cacert,
fake_client_v2_factory.client.client.verify_cert)
def test_cinder_http_retries(self):
retries = 42
self.flags(http_retries=retries, group='cinder')
self.api.get(self.context, '1234')
self.assert_called('GET', '/volumes/1234')
self.assertEqual(retries, fake_client_v2_factory.client.client.retries)
self.flags(insecure=True, group='cinder')
self.assertFalse(self.create_client().client.session.verify)
def test_cinder_http_timeout(self):
timeout = 123
self.flags(http_timeout=timeout, group='cinder')
self.api.get(self.context, '1234')
self.assertEqual(timeout,
fake_client_v2_factory.client.client.timeout)
self.flags(timeout=timeout, group='cinder')
self.assertEqual(timeout, self.create_client().client.session.timeout)
def test_cinder_api_cacert_file(self):
cacert = "/etc/ssl/certs/ca-certificates.crt"
self.flags(cafile=cacert, group='cinder')
self.assertEqual(self.create_client().client.session.verify, cacert)
class CinderTestCase(BaseCinderTestCase, test.NoDBTestCase):
"""Test case for cinder volume v1 api."""
URL = "http://localhost:8776/v1/project_id"
CATALOG = [{
"type": "volumev2",
"name": "cinderv2",
"endpoints": [{"publicURL": URL}]
}]
def create_client(self):
c = super(CinderTestCase, self).create_client()
self.assertIsInstance(c, cinder_client_v1.Client)
return c
def stub_volume(self, **kwargs):
volume = {
'display_name': None,
'display_description': None,
"attachments": [],
"availability_zone": "cinder",
"created_at": "2012-09-10T00:00:00.000000",
"id": '00000000-0000-0000-0000-000000000000',
"metadata": {},
"size": 1,
"snapshot_id": None,
"status": "available",
"volume_type": "None",
"bootable": "true"
}
volume.update(kwargs)
return volume
def test_cinder_endpoint_template(self):
endpoint = 'http://other_host:8776/v1/%(project_id)s'
self.flags(endpoint_template=endpoint, group='cinder')
self.assertEqual('http://other_host:8776/v1/project_id',
self.create_client().client.endpoint_override)
def test_get_non_existing_volume(self):
self.requests.get(self.URL + '/volumes/nonexisting',
status_code=404)
self.assertRaises(exception.VolumeNotFound, self.api.get, self.context,
'nonexisting')
def test_volume_with_image_metadata(self):
v = self.stub_volume(id='1234', volume_image_metadata=_image_metadata)
m = self.requests.get(self.URL + '/volumes/5678', json={'volume': v})
volume = self.api.get(self.context, '5678')
self.assertThat(m.last_request.path,
matchers.EndsWith('/volumes/5678'))
self.assertIn('volume_image_metadata', volume)
self.assertEqual(volume['volume_image_metadata'], _image_metadata)
class CinderV2TestCase(BaseCinderTestCase, test.NoDBTestCase):
"""Test case for cinder volume v2 api."""
URL = "http://localhost:8776/v2/project_id"
CATALOG = [{
"type": "volumev2",
"name": "cinder",
"endpoints": [{"publicURL": URL}]
}]
def setUp(self):
super(CinderV2TestCase, self).setUp()
cinder.CONF.set_override('catalog_info',
'volumev2:cinder:publicURL', group='cinder')
self.addCleanup(cinder.CONF.reset)
def create_client(self):
c = super(CinderV2TestCase, self).create_client()
self.assertIsInstance(c, cinder_client_v2.Client)
return c
def stub_volume(self, **kwargs):
volume = {
'name': None,
'description': None,
"attachments": [],
"availability_zone": "cinderv2",
"created_at": "2013-08-10T00:00:00.000000",
"id": '00000000-0000-0000-0000-000000000000',
"metadata": {},
"size": 1,
"snapshot_id": None,
"status": "available",
"volume_type": "None",
"bootable": "true"
}
volume.update(kwargs)
return volume
def test_cinder_endpoint_template(self):
endpoint = 'http://other_host:8776/v2/%(project_id)s'
self.flags(endpoint_template=endpoint, group='cinder')
self.assertEqual('http://other_host:8776/v2/project_id',
self.create_client().client.endpoint_override)
def test_get_non_existing_volume(self):
self.requests.get(self.URL + '/volumes/nonexisting',
status_code=404)
self.assertRaises(exception.VolumeNotFound, self.api.get, self.context,
'nonexisting')
def test_volume_with_image_metadata(self):
v = self.stub_volume(id='1234', volume_image_metadata=_image_metadata)
self.requests.get(self.URL + '/volumes/5678', json={'volume': v})
volume = self.api.get(self.context, '5678')
self.assertIn('volume_image_metadata', volume)
self.assertEqual(_image_metadata, volume['volume_image_metadata'])

View File

@ -93,26 +93,22 @@ class CinderApiTestCase(test.NoDBTestCase):
self.api.get, self.ctx, volume_id)
def test_create(self):
cinder.get_cinder_client_version(self.ctx).AndReturn('2')
cinder.cinderclient(self.ctx).AndReturn(self.cinderclient)
cinder._untranslate_volume_summary_view(self.ctx, {'id': 'created_id'})
self.mox.ReplayAll()
self.api.create(self.ctx, 1, '', '')
def test_create_failed(self):
cinder.get_cinder_client_version(self.ctx).AndReturn('2')
cinder.cinderclient(self.ctx).AndRaise(cinder_exception.BadRequest(''))
self.mox.ReplayAll()
@mock.patch('nova.volume.cinder.cinderclient')
def test_create_failed(self, mock_cinderclient):
mock_cinderclient.return_value.volumes.create.side_effect = (
cinder_exception.BadRequest(''))
self.assertRaises(exception.InvalidInput,
self.api.create, self.ctx, 1, '', '')
@mock.patch('nova.volume.cinder.get_cinder_client_version')
@mock.patch('nova.volume.cinder.cinderclient')
def test_create_over_quota_failed(self, mock_cinderclient,
mock_get_version):
mock_get_version.return_value = '2'
def test_create_over_quota_failed(self, mock_cinderclient):
mock_cinderclient.return_value.volumes.create.side_effect = (
cinder_exception.OverLimit(413))
self.assertRaises(exception.OverQuota, self.api.create, self.ctx,

View File

@ -23,7 +23,9 @@ import sys
from cinderclient import client as cinder_client
from cinderclient import exceptions as cinder_exception
from cinderclient import service_catalog
from cinderclient.v1 import client as v1_client
from keystoneclient import exceptions as keystone_exception
from keystoneclient import session
from oslo.config import cfg
from oslo.utils import strutils
import six.moves.urllib.parse as urlparse
@ -45,17 +47,9 @@ cinder_opts = [
'endpoint e.g. http://localhost:8776/v1/%(project_id)s'),
cfg.StrOpt('os_region_name',
help='Region name of this node'),
cfg.StrOpt('ca_certificates_file',
help='Location of ca certificates file to use for cinder '
'client requests.'),
cfg.IntOpt('http_retries',
default=3,
help='Number of cinderclient retries on failed http calls'),
cfg.IntOpt('http_timeout',
help='HTTP inactivity timeout (in seconds)'),
cfg.BoolOpt('api_insecure',
default=False,
help='Allow to perform insecure SSL requests to cinder'),
cfg.BoolOpt('cross_az_attach',
default=True,
help='Allow attach between instance and volume in different '
@ -63,30 +57,81 @@ cinder_opts = [
]
CONF = cfg.CONF
CONF.register_opts(cinder_opts, group='cinder')
CINDER_OPT_GROUP = 'cinder'
# cinder_opts options in the DEFAULT group were deprecated in Juno
CONF.register_opts(cinder_opts, group=CINDER_OPT_GROUP)
deprecated = {'timeout': [cfg.DeprecatedOpt('http_timeout',
group=CINDER_OPT_GROUP)],
'cafile': [cfg.DeprecatedOpt('ca_certificates_file',
group=CINDER_OPT_GROUP)],
'insecure': [cfg.DeprecatedOpt('api_insecure',
group=CINDER_OPT_GROUP)]}
session.Session.register_conf_options(CONF,
CINDER_OPT_GROUP,
deprecated_opts=deprecated)
LOG = logging.getLogger(__name__)
CINDER_URL = None
_SESSION = None
_V1_ERROR_RAISED = False
def reset_globals():
"""Testing method to reset globals.
"""
global _SESSION
_SESSION = None
def cinderclient(context):
global CINDER_URL
version = get_cinder_client_version(context)
c = cinder_client.Client(version,
context.user_id,
context.auth_token,
project_id=context.project_id,
auth_url=CINDER_URL,
insecure=CONF.cinder.api_insecure,
retries=CONF.cinder.http_retries,
timeout=CONF.cinder.http_timeout,
cacert=CONF.cinder.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 = CINDER_URL
return c
global _SESSION
global _V1_ERROR_RAISED
if not _SESSION:
_SESSION = session.Session.load_from_conf_options(CONF,
CINDER_OPT_GROUP)
url = None
endpoint_override = None
version = None
auth = context.get_auth_plugin()
service_type, service_name, interface = CONF.cinder.catalog_info.split(':')
service_parameters = {'service_type': service_type,
'service_name': service_name,
'interface': interface,
'region_name': CONF.cinder.os_region_name}
if CONF.cinder.endpoint_template:
url = CONF.cinder.endpoint_template % context.to_dict()
endpoint_override = url
else:
url = _SESSION.get_endpoint(auth, **service_parameters)
# TODO(jamielennox): This should be using proper version discovery from
# the cinder service rather than just inspecting the URL for certain string
# values.
version = get_cinder_client_version(url)
if version == '1' and not _V1_ERROR_RAISED:
msg = _LW('Cinder V1 API is deprecated as of the Juno '
'release, and Nova is still configured to use it. '
'Enable the V2 API in Cinder and set '
'cinder_catalog_info in nova.conf to use it.')
LOG.warn(msg)
_V1_ERROR_RAISED = True
return cinder_client.Client(version,
session=_SESSION,
auth=auth,
endpoint_override=endpoint_override,
connect_retries=CONF.cinder.http_retries,
**service_parameters)
def _untranslate_volume_summary_view(context, vol):
@ -166,14 +211,18 @@ def translate_volume_exception(method):
def wrapper(self, ctx, volume_id, *args, **kwargs):
try:
res = method(self, ctx, volume_id, *args, **kwargs)
except cinder_exception.ClientException:
except (cinder_exception.ClientException,
keystone_exception.ClientException):
exc_type, exc_value, exc_trace = sys.exc_info()
if isinstance(exc_value, cinder_exception.NotFound):
if isinstance(exc_value, (keystone_exception.NotFound,
cinder_exception.NotFound)):
exc_value = exception.VolumeNotFound(volume_id=volume_id)
elif isinstance(exc_value, cinder_exception.BadRequest):
elif isinstance(exc_value, (keystone_exception.BadRequest,
cinder_exception.BadRequest)):
exc_value = exception.InvalidInput(reason=exc_value.message)
raise exc_value, None, exc_trace
except cinder_exception.ConnectionError:
except (cinder_exception.ConnectionError,
keystone_exception.ConnectionError):
exc_type, exc_value, exc_trace = sys.exc_info()
exc_value = exception.CinderConnectionFailed(
reason=exc_value.message)
@ -189,12 +238,15 @@ def translate_snapshot_exception(method):
def wrapper(self, ctx, snapshot_id, *args, **kwargs):
try:
res = method(self, ctx, snapshot_id, *args, **kwargs)
except cinder_exception.ClientException:
except (cinder_exception.ClientException,
keystone_exception.ClientException):
exc_type, exc_value, exc_trace = sys.exc_info()
if isinstance(exc_value, cinder_exception.NotFound):
if isinstance(exc_value, (keystone_exception.NotFound,
cinder_exception.NotFound)):
exc_value = exception.SnapshotNotFound(snapshot_id=snapshot_id)
raise exc_value, None, exc_trace
except cinder_exception.ConnectionError:
except (cinder_exception.ConnectionError,
keystone_exception.ConnectionError):
exc_type, exc_value, exc_trace = sys.exc_info()
exc_value = exception.CinderConnectionFailed(
reason=exc_value.message)
@ -203,58 +255,24 @@ def translate_snapshot_exception(method):
return wrapper
def get_cinder_client_version(context):
def get_cinder_client_version(url):
"""Parse cinder client version by endpoint url.
:param context: Nova auth context.
:param url: URL for cinder.
:return: str value(1 or 2).
"""
global CINDER_URL
# FIXME: the cinderclient 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.cinder.endpoint_template:
url = CONF.cinder.endpoint_template % context.to_dict()
else:
info = CONF.cinder.catalog_info
service_type, service_name, endpoint_type = info.split(':')
# extract the region if set in configuration
if CONF.cinder.os_region_name:
attr = 'region'
filter_value = CONF.cinder.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('Cinderclient connection created using URL: %s', url)
# FIXME(jamielennox): Use cinder_client.get_volume_api_from_url when
# bug #1386232 is fixed.
valid_versions = ['v1', 'v2']
magic_tuple = urlparse.urlsplit(url)
scheme, netloc, path, query, frag = magic_tuple
scheme, netloc, path, query, frag = urlparse.urlsplit(url)
components = path.split("/")
for version in valid_versions:
if version in components[1]:
version = version[1:]
if version in components:
return version[1:]
if not CINDER_URL and version == '1':
msg = _LW('Cinder V1 API is deprecated as of the Juno '
'release, and Nova is still configured to use it. '
'Enable the V2 API in Cinder and set '
'cinder_catalog_info in nova.conf to use it.')
LOG.warn(msg)
CINDER_URL = url
return version
msg = _("Invalid client version, must be one of: %s") % valid_versions
msg = "Invalid client version '%s'. must be one of: %s" % (
(version, ', '.join(valid_versions)))
raise cinder_exception.UnsupportedVersion(msg)
@ -350,6 +368,7 @@ class API(object):
def create(self, context, size, name, description, snapshot=None,
image_id=None, volume_type=None, metadata=None,
availability_zone=None):
client = cinderclient(context)
if snapshot is not None:
snapshot_id = snapshot['id']
@ -364,20 +383,20 @@ class API(object):
metadata=metadata,
imageRef=image_id)
version = get_cinder_client_version(context)
if version == '1':
if isinstance(client, v1_client.Client):
kwargs['display_name'] = name
kwargs['display_description'] = description
elif version == '2':
else:
kwargs['name'] = name
kwargs['description'] = description
try:
item = cinderclient(context).volumes.create(size, **kwargs)
item = client.volumes.create(size, **kwargs)
return _untranslate_volume_summary_view(context, item)
except cinder_exception.OverLimit:
raise exception.OverQuota(overs='volumes')
except cinder_exception.BadRequest as e:
except (cinder_exception.BadRequest,
keystone_exception.BadRequest) as e:
raise exception.InvalidInput(reason=e)
@translate_volume_exception

View File

@ -15,6 +15,7 @@ psycopg2
pylint>=1.3.0 # GNU GPL v2
python-ironicclient>=0.2.1
python-subunit>=0.0.18
requests-mock>=0.5.1 # Apache-2.0
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
oslosphinx>=2.2.0 # Apache-2.0
oslotest>=1.2.0 # Apache-2.0