Add flag cinder_endpoint_template to volume.cinder
Add optional flag to allow cinder endpoint to be defined by flag rather than extracted from the request context's service catalog. No change to default behavior. This flag allows deployers to use a seperate management endpoint for cinder which compute can use exclusively to send messages to cinder, or just use an auth system that doesn't add/forward the catalog along with the request (e.g. noauth). Add python-cinderclient to tools/test-requires fix lp bug #1048798 Change-Id: Icb416bf4df2a6e37024f1fbc866006d46d30bcf2
This commit is contained in:
parent
608b75e125
commit
0fa231f718
|
@ -1752,6 +1752,18 @@
|
|||
#### resides
|
||||
|
||||
|
||||
######## defined in nova.volume.cinder ########
|
||||
|
||||
# cinder_catalog_info=volume:cinder:publicURL
|
||||
#### (StrOpt) Info to match when looking for cinder in the service catalog.
|
||||
#### Format is : separated values of the form:
|
||||
#### <service_type>:<service_name>:<endpoint_type>
|
||||
|
||||
# cinder_endpoint_template=None
|
||||
#### (StrOpt) Override service catalog lookup with template for cinder
|
||||
#### endpoint e.g. http://localhost:8776/v1/%(project_id)s
|
||||
|
||||
|
||||
######## defined in nova.volume.driver ########
|
||||
|
||||
# volume_group=nova-volumes
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 OpenStack LLC
|
||||
#
|
||||
# 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 httplib2
|
||||
import urlparse
|
||||
|
||||
from nova import context
|
||||
from nova.volume import cinder
|
||||
|
||||
from nova import test
|
||||
|
||||
|
||||
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",
|
||||
}
|
||||
volume.update(kwargs)
|
||||
return volume
|
||||
|
||||
|
||||
class FakeHTTPClient(cinder.cinder_client.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 httplib2.Response(status), body
|
||||
else:
|
||||
return httplib2.Response({"status": status}), body
|
||||
|
||||
def get_volumes_1234(self, **kw):
|
||||
volume = {'volume': _stub_volume(id='1234')}
|
||||
return (200, volume)
|
||||
|
||||
|
||||
class FakeCinderClient(cinder.cinder_client.Client):
|
||||
|
||||
def __init__(self, username, password, project_id=None, auth_url=None):
|
||||
super(FakeCinderClient, self).__init__(username, password,
|
||||
project_id=project_id,
|
||||
auth_url=auth_url)
|
||||
self.client = FakeHTTPClient(username, password, project_id, auth_url)
|
||||
# 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 CinderTestCase(test.TestCase):
|
||||
"""Test case for cinder volume api."""
|
||||
|
||||
def setUp(self):
|
||||
super(CinderTestCase, self).setUp()
|
||||
self.fake_client_factory = FakeClientFactory()
|
||||
self.stubs.Set(cinder.cinder_client, "Client",
|
||||
self.fake_client_factory)
|
||||
self.flags(
|
||||
volume_api_class='nova.volume.cinder.API',
|
||||
)
|
||||
self.api = cinder.API()
|
||||
catalog = [{
|
||||
"type": "volume",
|
||||
"name": "cinder",
|
||||
"endpoints": [{"publicURL": "http://localhost:8776/v1/project_id"}]
|
||||
}]
|
||||
self.context = context.RequestContext('username', 'project_id',
|
||||
service_catalog=catalog)
|
||||
|
||||
def assert_called(self, *args, **kwargs):
|
||||
self.fake_client_factory.assert_called(*args, **kwargs)
|
||||
|
||||
def test_context_with_catalog(self):
|
||||
volume = self.api.get(self.context, '1234')
|
||||
self.assert_called('GET', '/volumes/1234')
|
||||
self.assertEquals(
|
||||
self.fake_client_factory.client.client.management_url,
|
||||
'http://localhost:8776/v1/project_id')
|
||||
|
||||
def test_cinder_endpoint_template(self):
|
||||
self.flags(
|
||||
cinder_endpoint_template='http://other_host:8776/v1/%(project_id)s'
|
||||
)
|
||||
volume = self.api.get(self.context, '1234')
|
||||
self.assert_called('GET', '/volumes/1234')
|
||||
self.assertEquals(
|
||||
self.fake_client_factory.client.client.management_url,
|
||||
'http://other_host:8776/v1/project_id')
|
|
@ -36,6 +36,10 @@ cinder_opts = [
|
|||
help='Info to match when looking for cinder in the service '
|
||||
'catalog. Format is : separated values of the form: '
|
||||
'<service_type>:<service_name>:<endpoint_type>'),
|
||||
cfg.StrOpt('cinder_endpoint_template',
|
||||
default=None,
|
||||
help='Override service catalog lookup with template for cinder '
|
||||
'endpoint e.g. http://localhost:8776/v1/%(project_id)s'),
|
||||
]
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
@ -49,14 +53,17 @@ def cinderclient(context):
|
|||
# FIXME: the cinderclient ServiceCatalog object is mis-named.
|
||||
# It actually contains the entire access blob.
|
||||
compat_catalog = {
|
||||
'access': {'serviceCatalog': context.service_catalog}
|
||||
'access': {'serviceCatalog': context.service_catalog or {}}
|
||||
}
|
||||
sc = service_catalog.ServiceCatalog(compat_catalog)
|
||||
info = FLAGS.cinder_catalog_info
|
||||
service_type, service_name, endpoint_type = info.split(':')
|
||||
url = sc.url_for(service_type=service_type,
|
||||
service_name=service_name,
|
||||
endpoint_type=endpoint_type)
|
||||
if FLAGS.cinder_endpoint_template:
|
||||
url = FLAGS.cinder_endpoint_template % context.to_dict()
|
||||
else:
|
||||
info = FLAGS.cinder_catalog_info
|
||||
service_type, service_name, endpoint_type = info.split(':')
|
||||
url = sc.url_for(service_type=service_type,
|
||||
service_name=service_name,
|
||||
endpoint_type=endpoint_type)
|
||||
|
||||
LOG.debug(_('Cinderclient connection created using URL: %s') % url)
|
||||
|
||||
|
@ -64,7 +71,9 @@ def cinderclient(context):
|
|||
context.auth_token,
|
||||
project_id=context.project_id,
|
||||
auth_url=url)
|
||||
c.client.auth_token = context.auth_token
|
||||
# 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
|
||||
|
||||
|
|
|
@ -10,3 +10,4 @@ pep8==1.1
|
|||
pylint==0.25.2
|
||||
sphinx>=1.1.2
|
||||
feedparser
|
||||
python-cinderclient
|
||||
|
|
Loading…
Reference in New Issue