Merge "Use session in cinderclient"

This commit is contained in:
Jenkins 2014-11-20 12:27:14 +00:00 committed by Gerrit Code Review
commit c51673f384
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
@ -2403,7 +2404,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)
@ -360,6 +378,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']
@ -374,20 +393,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