Handle Ambiguous Endpoints Correctly
- Added --service_name argument to allow selecting endpoints by service name - Renamed endpoint_name argument to endpoint_type (this breaks compatibility) - Return AmbiguousEndpoints error if more than one endpoint matches filter - Also addresses bug 924052 Use case: $ nova --projectid xxx --version 1.1 --password xxx --username xxx --url https://identity.openstackcloud.com/ image-list Found more than one valid endpoint. Use a more restrictive filter AmbiguousEndpoints: [ {'serviceName': 'New Cloud', 'region': 'Test', 'publicURL': 'https://test.openstackcloud.com/v1.1/tttt', 'tenantId': 'tttt'}, {'serviceName': 'Old Cloud', 'publicURL': 'https://servers.openstackcloud.com/v1.0/tttt', 'tenantId': 'tttt'}] $ nova --projectid tttt --version 1.1 --password xxx --username xxx --url https://identity.openstackcloud.com/ --service_name 'New Cloud' image-list +--------------------------------------+-----------------------------+--------+--------+ | ID | Name | Status | Server | +--------------------------------------+-----------------------------+--------+--------+ | 346f4039-a81e-4444-9223-4a3d13592a07 | Debian Squeeze (6.0) | ACTIVE | | | ac8985ea-c09e-4544-82af-eb459a02a6b2 | Fedora 15 | ACTIVE | | | ddddc02e-92fa-4f44-b36f-55b39bf66a67 | CentOS 5.6 | ACTIVE | | +--------------------------------------+-----------------------------+--------+--------+ Change-Id: I9a10b9ad5e5b9cf6e762659013496a93a79774db
This commit is contained in:
parent
c3a0c702ee
commit
38bc7ea570
@ -36,7 +36,7 @@ class HTTPClient(httplib2.Http):
|
||||
|
||||
def __init__(self, user, password, projectid, auth_url, insecure=False,
|
||||
timeout=None, token=None, region_name=None,
|
||||
endpoint_name='publicURL'):
|
||||
endpoint_type='publicURL', service_name=None):
|
||||
super(HTTPClient, self).__init__(timeout=timeout)
|
||||
self.user = user
|
||||
self.password = password
|
||||
@ -44,7 +44,8 @@ class HTTPClient(httplib2.Http):
|
||||
self.auth_url = auth_url
|
||||
self.version = 'v1.1'
|
||||
self.region_name = region_name
|
||||
self.endpoint_name = endpoint_name
|
||||
self.endpoint_type = endpoint_type
|
||||
self.service_name = service_name
|
||||
|
||||
self.management_url = None
|
||||
self.auth_token = None
|
||||
@ -154,8 +155,13 @@ class HTTPClient(httplib2.Http):
|
||||
self.management_url = self.service_catalog.url_for(
|
||||
attr='region',
|
||||
filter_value=self.region_name,
|
||||
endpoint_type=self.endpoint_name)
|
||||
endpoint_type=self.endpoint_type,
|
||||
service_name=self.service_name)
|
||||
return None
|
||||
except exceptions.AmbiguousEndpoints, exc:
|
||||
print "Found more than one valid endpoint. Use a more " \
|
||||
"restrictive filter"
|
||||
raise
|
||||
except KeyError:
|
||||
raise exceptions.AuthorizationFailure()
|
||||
except exceptions.EndpointNotFound:
|
||||
|
@ -29,6 +29,15 @@ class EndpointNotFound(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AmbiguousEndpoints(Exception):
|
||||
"""Found more than one matching endpoint in Service Catalog."""
|
||||
def __init__(self, endpoints=None):
|
||||
self.endpoints = endpoints
|
||||
|
||||
def __str__(self):
|
||||
return "AmbiguousEndpoints: %s" % repr(self.endpoints)
|
||||
|
||||
|
||||
class ClientException(Exception):
|
||||
"""
|
||||
The base exception class for all exceptions this library raises.
|
||||
|
@ -29,16 +29,19 @@ class ServiceCatalog(object):
|
||||
return self.catalog['access']['token']['id']
|
||||
|
||||
def url_for(self, attr=None, filter_value=None,
|
||||
service_type='compute', endpoint_type='publicURL'):
|
||||
service_type='compute', endpoint_type='publicURL',
|
||||
service_name=None):
|
||||
"""Fetch the public URL from the Compute service for
|
||||
a particular endpoint attribute. If none given, return
|
||||
the first. See tests for sample service catalog."""
|
||||
matching_endpoints = []
|
||||
if 'endpoints' in self.catalog:
|
||||
# We have a bastardized service catalog. Treat it special. :/
|
||||
for endpoint in self.catalog['endpoints']:
|
||||
if not filter_value or endpoint[attr] == filter_value:
|
||||
return endpoint[endpoint_type]
|
||||
raise novaclient.exceptions.EndpointNotFound()
|
||||
matching_endpoints.append(endpoint)
|
||||
if not matching_endpoints:
|
||||
raise novaclient.exceptions.EndpointNotFound()
|
||||
|
||||
# We don't always get a service catalog back ...
|
||||
if not 'serviceCatalog' in self.catalog['access']:
|
||||
@ -51,9 +54,19 @@ class ServiceCatalog(object):
|
||||
if service['type'] != service_type:
|
||||
continue
|
||||
|
||||
if service_name and service.get('name') != service_name:
|
||||
continue
|
||||
|
||||
endpoints = service['endpoints']
|
||||
for endpoint in endpoints:
|
||||
if not filter_value or endpoint[attr] == filter_value:
|
||||
return endpoint[endpoint_type]
|
||||
endpoint["serviceName"] = service.get("name")
|
||||
matching_endpoints.append(endpoint)
|
||||
|
||||
raise novaclient.exceptions.EndpointNotFound()
|
||||
if not matching_endpoints:
|
||||
raise novaclient.exceptions.EndpointNotFound()
|
||||
elif len(matching_endpoints) > 1:
|
||||
raise novaclient.exceptions.AmbiguousEndpoints(
|
||||
endpoints=matching_endpoints)
|
||||
else:
|
||||
return matching_endpoints[0][endpoint_type]
|
||||
|
@ -111,9 +111,13 @@ class OpenStackComputeShell(object):
|
||||
default=env('NOVA_REGION_NAME'),
|
||||
help='Defaults to env[NOVA_REGION_NAME].')
|
||||
|
||||
parser.add_argument('--endpoint_name',
|
||||
default=env('NOVA_ENDPOINT_NAME'),
|
||||
help='Defaults to env[NOVA_ENDPOINT_NAME] or "publicURL".')
|
||||
parser.add_argument('--service_name',
|
||||
default=env('NOVA_SERVICE_NAME'),
|
||||
help='Defaults to env[NOVA_SERVICE_NAME]')
|
||||
|
||||
parser.add_argument('--endpoint_type',
|
||||
default=env('NOVA_ENDPOINT_TYPE'),
|
||||
help='Defaults to env[NOVA_ENDPOINT_TYPE] or "publicURL".')
|
||||
|
||||
parser.add_argument('--version',
|
||||
default=env('NOVA_VERSION'),
|
||||
@ -247,12 +251,13 @@ class OpenStackComputeShell(object):
|
||||
return 0
|
||||
|
||||
(user, apikey, password, projectid, url, region_name,
|
||||
endpoint_name, insecure) = (args.username, args.apikey,
|
||||
args.password, args.projectid, args.url,
|
||||
args.region_name, args.endpoint_name, args.insecure)
|
||||
endpoint_type, insecure, service_name) = (args.username,
|
||||
args.apikey, args.password, args.projectid, args.url,
|
||||
args.region_name, args.endpoint_type, args.insecure,
|
||||
args.service_name)
|
||||
|
||||
if not endpoint_name:
|
||||
endpoint_name = 'publicURL'
|
||||
if not endpoint_type:
|
||||
endpoint_type = 'publicURL'
|
||||
|
||||
#FIXME(usrleon): Here should be restrict for project id same as
|
||||
# for username or password but for compatibility it is not.
|
||||
@ -294,8 +299,9 @@ class OpenStackComputeShell(object):
|
||||
self.cs = client.Client(options.version, user, password,
|
||||
projectid, url, insecure,
|
||||
region_name=region_name,
|
||||
endpoint_name=endpoint_name,
|
||||
extensions=self.extensions)
|
||||
endpoint_type=endpoint_type,
|
||||
extensions=self.extensions,
|
||||
service_name=service_name)
|
||||
|
||||
try:
|
||||
if not utils.isunauthenticated(args.func):
|
||||
|
@ -39,7 +39,8 @@ class Client(object):
|
||||
# FIXME(jesse): project_id isn't required to authenticate
|
||||
def __init__(self, username, api_key, project_id, auth_url,
|
||||
insecure=False, timeout=None, token=None, region_name=None,
|
||||
endpoint_name='publicURL', extensions=None):
|
||||
endpoint_type='publicURL', extensions=None,
|
||||
service_name=None):
|
||||
# FIXME(comstud): Rename the api_key argument above when we
|
||||
# know it's not being used as keyword argument
|
||||
password = api_key
|
||||
@ -82,7 +83,8 @@ class Client(object):
|
||||
timeout=timeout,
|
||||
token=token,
|
||||
region_name=region_name,
|
||||
endpoint_name=endpoint_name)
|
||||
endpoint_type=endpoint_type,
|
||||
service_name=service_name)
|
||||
|
||||
def authenticate(self):
|
||||
"""
|
||||
|
@ -103,8 +103,7 @@ class ServiceCatalogTest(utils.TestCase):
|
||||
def test_building_a_service_catalog(self):
|
||||
sc = service_catalog.ServiceCatalog(SERVICE_CATALOG)
|
||||
|
||||
self.assertEquals(sc.url_for(),
|
||||
"https://compute1.host/v1/1234")
|
||||
self.assertRaises(exceptions.AmbiguousEndpoints, sc.url_for)
|
||||
self.assertEquals(sc.url_for('tenantId', '1'),
|
||||
"https://compute1.host/v1/1234")
|
||||
self.assertEquals(sc.url_for('tenantId', '2'),
|
||||
|
@ -28,11 +28,11 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
|
||||
},
|
||||
"serviceCatalog": [
|
||||
{
|
||||
"adminURL": "http://localhost:8774/v1.1",
|
||||
"type": "compute",
|
||||
"endpoints": [
|
||||
{
|
||||
"region": "RegionOne",
|
||||
"adminURL": "http://localhost:8774/v1.1",
|
||||
"internalURL": "http://localhost:8774/v1.1",
|
||||
"publicURL": "http://localhost:8774/v1.1/",
|
||||
},
|
||||
@ -177,6 +177,57 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
|
||||
|
||||
test_auth_call()
|
||||
|
||||
def test_ambiguous_endpoints(self):
|
||||
cs = client.Client("username", "password", "project_id",
|
||||
"auth_url/v2.0")
|
||||
resp = {
|
||||
"access": {
|
||||
"token": {
|
||||
"expires": "12345",
|
||||
"id": "FAKE_ID",
|
||||
},
|
||||
"serviceCatalog": [
|
||||
{
|
||||
"adminURL": "http://localhost:8774/v1.1",
|
||||
"type": "compute",
|
||||
"name": "Compute CLoud",
|
||||
"endpoints": [
|
||||
{
|
||||
"region": "RegionOne",
|
||||
"internalURL": "http://localhost:8774/v1.1",
|
||||
"publicURL": "http://localhost:8774/v1.1/",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"adminURL": "http://localhost:8774/v1.1",
|
||||
"type": "compute",
|
||||
"name": "Hyper-compute Cloud",
|
||||
"endpoints": [
|
||||
{
|
||||
"internalURL": "http://localhost:8774/v1.1",
|
||||
"publicURL": "http://localhost:8774/v1.1/",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
auth_response = httplib2.Response({
|
||||
"status": 200,
|
||||
"body": json.dumps(resp),
|
||||
})
|
||||
|
||||
mock_request = mock.Mock(return_value=(auth_response,
|
||||
json.dumps(resp)))
|
||||
|
||||
@mock.patch.object(httplib2.Http, "request", mock_request)
|
||||
def test_auth_call():
|
||||
self.assertRaises(exceptions.AmbiguousEndpoints,
|
||||
cs.client.authenticate)
|
||||
|
||||
test_auth_call()
|
||||
|
||||
|
||||
class AuthenticationTests(utils.TestCase):
|
||||
def test_authenticate_success(self):
|
||||
|
Loading…
Reference in New Issue
Block a user