Allow the HTTPClient consumer to pass endpoint_type.
Changes the default behavior of the client. It will now choose publicURL by default rather than adminURL. Now allows the consumer to pass adminURL, internalURL or some other endpoint type when constructing a Client to override this default behavior. Adds --endpoint-type option to the shell client. Defaults to the environment variable OS_ENDPOINT_TYPE or publicURL. This was patterned after the same option in the Nova client. Adds a new exception type to handle the case where a suitable endpoint type is not found in the catalog. Without this, the exception encountered is a KeyError that was not clearly reported to the caller of the quantum command line. Change-Id: Iaffcaff291d433a605d8379dc89c1308096d36c2 Fixes: Bug #1176197
This commit is contained in:
parent
d477346627
commit
06375f089b
quantumclient
tests/unit
@ -60,10 +60,10 @@ class ServiceCatalog(object):
|
||||
return token
|
||||
|
||||
def url_for(self, attr=None, filter_value=None,
|
||||
service_type='network', endpoint_type='adminURL'):
|
||||
"""Fetch the admin URL from the Quantum service for
|
||||
a particular endpoint attribute. If none given, return
|
||||
the first. See tests for sample service catalog.
|
||||
service_type='network', endpoint_type='publicURL'):
|
||||
"""Fetch the URL from the Quantum service for
|
||||
a particular endpoint type. If none given, return
|
||||
publicURL.
|
||||
"""
|
||||
|
||||
catalog = self.catalog['access'].get('serviceCatalog', [])
|
||||
@ -82,6 +82,9 @@ class ServiceCatalog(object):
|
||||
elif len(matching_endpoints) > 1:
|
||||
raise exceptions.AmbiguousEndpoints(message=matching_endpoints)
|
||||
else:
|
||||
if endpoint_type not in matching_endpoints[0]:
|
||||
raise exceptions.EndpointTypeNotFound(message=endpoint_type)
|
||||
|
||||
return matching_endpoints[0][endpoint_type]
|
||||
|
||||
|
||||
@ -94,12 +97,14 @@ class HTTPClient(httplib2.Http):
|
||||
password=None, auth_url=None,
|
||||
token=None, region_name=None, timeout=None,
|
||||
endpoint_url=None, insecure=False,
|
||||
endpoint_type='publicURL',
|
||||
auth_strategy='keystone', **kwargs):
|
||||
super(HTTPClient, self).__init__(timeout=timeout)
|
||||
self.username = username
|
||||
self.tenant_name = tenant_name
|
||||
self.password = password
|
||||
self.auth_url = auth_url.rstrip('/') if auth_url else None
|
||||
self.endpoint_type = endpoint_type
|
||||
self.region_name = region_name
|
||||
self.auth_token = token
|
||||
self.content_type = 'application/json'
|
||||
@ -170,7 +175,7 @@ class HTTPClient(httplib2.Http):
|
||||
raise exceptions.Unauthorized()
|
||||
self.endpoint_url = self.service_catalog.url_for(
|
||||
attr='region', filter_value=self.region_name,
|
||||
endpoint_type='adminURL')
|
||||
endpoint_type=self.endpoint_type)
|
||||
|
||||
def authenticate(self):
|
||||
if self.auth_strategy != 'keystone':
|
||||
@ -217,7 +222,10 @@ class HTTPClient(httplib2.Http):
|
||||
for endpoint in body.get('endpoints', []):
|
||||
if (endpoint['type'] == 'network' and
|
||||
endpoint.get('region') == self.region_name):
|
||||
return endpoint['adminURL']
|
||||
if self.endpoint_type not in endpoint:
|
||||
raise exceptions.EndpointTypeNotFound(
|
||||
message=self.endpoint_type)
|
||||
return endpoint[self.endpoint_type]
|
||||
|
||||
raise exceptions.EndpointNotFound()
|
||||
|
||||
|
@ -49,6 +49,7 @@ class ClientManager(object):
|
||||
|
||||
def __init__(self, token=None, url=None,
|
||||
auth_url=None,
|
||||
endpoint_type=None,
|
||||
tenant_name=None, tenant_id=None,
|
||||
username=None, password=None,
|
||||
region_name=None,
|
||||
@ -59,6 +60,7 @@ class ClientManager(object):
|
||||
self._token = token
|
||||
self._url = url
|
||||
self._auth_url = auth_url
|
||||
self._endpoint_type = endpoint_type
|
||||
self._tenant_name = tenant_name
|
||||
self._tenant_id = tenant_id
|
||||
self._username = username
|
||||
@ -77,6 +79,7 @@ class ClientManager(object):
|
||||
password=self._password,
|
||||
region_name=self._region_name,
|
||||
auth_url=self._auth_url,
|
||||
endpoint_type=self._endpoint_type,
|
||||
insecure=self._insecure)
|
||||
httpclient.authenticate()
|
||||
# Populate other password flow attributes
|
||||
|
@ -111,6 +111,14 @@ class EndpointNotFound(QuantumClientException):
|
||||
message = _("Could not find Service or Region in Service Catalog.")
|
||||
|
||||
|
||||
class EndpointTypeNotFound(QuantumClientException):
|
||||
"""Could not find endpoint type in Service Catalog."""
|
||||
|
||||
def __str__(self):
|
||||
msg = "Could not find endpoint type %s in Service Catalog."
|
||||
return msg % repr(self.message)
|
||||
|
||||
|
||||
class AmbiguousEndpoints(QuantumClientException):
|
||||
"""Found more than one matching endpoint in Service Catalog."""
|
||||
|
||||
|
@ -405,6 +405,11 @@ class QuantumShell(App):
|
||||
'--os_token',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument(
|
||||
'--endpoint-type', metavar='<endpoint-type>',
|
||||
default=env('OS_ENDPOINT_TYPE', default='publicURL'),
|
||||
help='Defaults to env[OS_ENDPOINT_TYPE] or publicURL.')
|
||||
|
||||
parser.add_argument(
|
||||
'--os-url', metavar='<url>',
|
||||
default=env('OS_URL'),
|
||||
@ -583,6 +588,7 @@ class QuantumShell(App):
|
||||
region_name=self.options.os_region_name,
|
||||
api_version=self.api_version,
|
||||
auth_strategy=self.options.os_auth_strategy,
|
||||
endpoint_type=self.options.endpoint_type,
|
||||
insecure=self.options.insecure, )
|
||||
return
|
||||
|
||||
|
@ -119,6 +119,9 @@ class Client(object):
|
||||
:param string token: Token for authentication. (optional)
|
||||
:param string tenant_name: Tenant name. (optional)
|
||||
:param string auth_url: Keystone service endpoint for authorization.
|
||||
:param string endpoint_type: Network service endpoint type to pull from the
|
||||
keystone catalog (e.g. 'publicURL',
|
||||
'internalURL', or 'adminURL') (optional)
|
||||
:param string region_name: Name of a region to select when choosing an
|
||||
endpoint from the service catalog.
|
||||
:param string endpoint_url: A user-supplied endpoint URL for the quantum
|
||||
|
@ -15,6 +15,7 @@
|
||||
#
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
import copy
|
||||
import httplib2
|
||||
import json
|
||||
import uuid
|
||||
@ -23,7 +24,9 @@ import mox
|
||||
from mox import ContainsKeyValue, IsA, StrContains
|
||||
import testtools
|
||||
|
||||
from quantumclient.client import exceptions
|
||||
from quantumclient.client import HTTPClient
|
||||
from quantumclient.client import ServiceCatalog
|
||||
|
||||
|
||||
USERNAME = 'testuser'
|
||||
@ -136,6 +139,27 @@ class CLITestAuthKeystone(testtools.TestCase):
|
||||
self.mox.ReplayAll()
|
||||
self.client.do_request('/resource', 'GET')
|
||||
|
||||
def test_get_endpoint_url_other(self):
|
||||
self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME,
|
||||
password=PASSWORD, auth_url=AUTH_URL,
|
||||
region_name=REGION, endpoint_type='otherURL')
|
||||
self.mox.StubOutWithMock(self.client, "request")
|
||||
|
||||
self.client.auth_token = TOKEN
|
||||
|
||||
res200 = self.mox.CreateMock(httplib2.Response)
|
||||
res200.status = 200
|
||||
|
||||
self.client.request(StrContains(AUTH_URL +
|
||||
'/tokens/%s/endpoints' % TOKEN), 'GET',
|
||||
headers=IsA(dict)). \
|
||||
AndReturn((res200, json.dumps(ENDPOINTS_RESULT)))
|
||||
self.mox.ReplayAll()
|
||||
self.assertRaises(exceptions.EndpointTypeNotFound,
|
||||
self.client.do_request,
|
||||
'/resource',
|
||||
'GET')
|
||||
|
||||
def test_get_endpoint_url_failed(self):
|
||||
self.mox.StubOutWithMock(self.client, "request")
|
||||
|
||||
@ -158,3 +182,133 @@ class CLITestAuthKeystone(testtools.TestCase):
|
||||
AndReturn((res200, ''))
|
||||
self.mox.ReplayAll()
|
||||
self.client.do_request('/resource', 'GET')
|
||||
|
||||
def test_url_for(self):
|
||||
resources = copy.deepcopy(KS_TOKEN_RESULT)
|
||||
|
||||
endpoints = resources['access']['serviceCatalog'][0]['endpoints'][0]
|
||||
endpoints['publicURL'] = 'public'
|
||||
endpoints['internalURL'] = 'internal'
|
||||
endpoints['adminURL'] = 'admin'
|
||||
catalog = ServiceCatalog(resources)
|
||||
|
||||
# endpoint_type not specified
|
||||
url = catalog.url_for(attr='region',
|
||||
filter_value=REGION)
|
||||
self.assertEqual('public', url)
|
||||
|
||||
# endpoint type specified (3 cases)
|
||||
url = catalog.url_for(attr='region',
|
||||
filter_value=REGION,
|
||||
endpoint_type='adminURL')
|
||||
self.assertEqual('admin', url)
|
||||
|
||||
url = catalog.url_for(attr='region',
|
||||
filter_value=REGION,
|
||||
endpoint_type='publicURL')
|
||||
self.assertEqual('public', url)
|
||||
|
||||
url = catalog.url_for(attr='region',
|
||||
filter_value=REGION,
|
||||
endpoint_type='internalURL')
|
||||
self.assertEqual('internal', url)
|
||||
|
||||
# endpoint_type requested does not exist.
|
||||
self.assertRaises(exceptions.EndpointTypeNotFound,
|
||||
catalog.url_for,
|
||||
attr='region',
|
||||
filter_value=REGION,
|
||||
endpoint_type='privateURL')
|
||||
|
||||
# Test scenario with url_for when the service catalog only has publicURL.
|
||||
def test_url_for_only_public_url(self):
|
||||
resources = copy.deepcopy(KS_TOKEN_RESULT)
|
||||
catalog = ServiceCatalog(resources)
|
||||
|
||||
# Remove endpoints from the catalog.
|
||||
endpoints = resources['access']['serviceCatalog'][0]['endpoints'][0]
|
||||
del endpoints['internalURL']
|
||||
del endpoints['adminURL']
|
||||
endpoints['publicURL'] = 'public'
|
||||
|
||||
# Use publicURL when specified explicitly.
|
||||
url = catalog.url_for(attr='region',
|
||||
filter_value=REGION,
|
||||
endpoint_type='publicURL')
|
||||
self.assertEqual('public', url)
|
||||
|
||||
# Use publicURL when specified explicitly.
|
||||
url = catalog.url_for(attr='region',
|
||||
filter_value=REGION)
|
||||
self.assertEqual('public', url)
|
||||
|
||||
# Test scenario with url_for when the service catalog only has adminURL.
|
||||
def test_url_for_only_admin_url(self):
|
||||
resources = copy.deepcopy(KS_TOKEN_RESULT)
|
||||
catalog = ServiceCatalog(resources)
|
||||
endpoints = resources['access']['serviceCatalog'][0]['endpoints'][0]
|
||||
del endpoints['internalURL']
|
||||
del endpoints['publicURL']
|
||||
endpoints['adminURL'] = 'admin'
|
||||
|
||||
# Use publicURL when specified explicitly.
|
||||
url = catalog.url_for(attr='region',
|
||||
filter_value=REGION,
|
||||
endpoint_type='adminURL')
|
||||
self.assertEqual('admin', url)
|
||||
|
||||
# But not when nothing is specified.
|
||||
self.assertRaises(exceptions.EndpointTypeNotFound,
|
||||
catalog.url_for,
|
||||
attr='region',
|
||||
filter_value=REGION)
|
||||
|
||||
def test_endpoint_type(self):
|
||||
resources = copy.deepcopy(KS_TOKEN_RESULT)
|
||||
endpoints = resources['access']['serviceCatalog'][0]['endpoints'][0]
|
||||
endpoints['internalURL'] = 'internal'
|
||||
endpoints['adminURL'] = 'admin'
|
||||
endpoints['publicURL'] = 'public'
|
||||
|
||||
# Test default behavior is to choose public.
|
||||
self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME,
|
||||
password=PASSWORD, auth_url=AUTH_URL,
|
||||
region_name=REGION)
|
||||
|
||||
self.client._extract_service_catalog(resources)
|
||||
self.assertEqual(self.client.endpoint_url, 'public')
|
||||
|
||||
# Test admin url
|
||||
self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME,
|
||||
password=PASSWORD, auth_url=AUTH_URL,
|
||||
region_name=REGION, endpoint_type='adminURL')
|
||||
|
||||
self.client._extract_service_catalog(resources)
|
||||
self.assertEqual(self.client.endpoint_url, 'admin')
|
||||
|
||||
# Test public url
|
||||
self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME,
|
||||
password=PASSWORD, auth_url=AUTH_URL,
|
||||
region_name=REGION, endpoint_type='publicURL')
|
||||
|
||||
self.client._extract_service_catalog(resources)
|
||||
self.assertEqual(self.client.endpoint_url, 'public')
|
||||
|
||||
# Test internal url
|
||||
self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME,
|
||||
password=PASSWORD, auth_url=AUTH_URL,
|
||||
region_name=REGION,
|
||||
endpoint_type='internalURL')
|
||||
|
||||
self.client._extract_service_catalog(resources)
|
||||
self.assertEqual(self.client.endpoint_url, 'internal')
|
||||
|
||||
# Test url that isn't found in the service catalog
|
||||
self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME,
|
||||
password=PASSWORD, auth_url=AUTH_URL,
|
||||
region_name=REGION,
|
||||
endpoint_type='privateURL')
|
||||
|
||||
self.assertRaises(exceptions.EndpointTypeNotFound,
|
||||
self.client._extract_service_catalog,
|
||||
resources)
|
||||
|
@ -143,3 +143,31 @@ class ShellTest(testtools.TestCase):
|
||||
self.mox.VerifyAll()
|
||||
self.mox.UnsetStubs()
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
def test_endpoint_option(self):
|
||||
shell = openstack_shell.QuantumShell('2.0')
|
||||
parser = shell.build_option_parser('descr', '2.0')
|
||||
|
||||
# Neither $OS_ENDPOINT_TYPE nor --endpoint-type
|
||||
namespace = parser.parse_args([])
|
||||
self.assertEqual('publicURL', namespace.endpoint_type)
|
||||
|
||||
# --endpoint-type but not $OS_ENDPOINT_TYPE
|
||||
namespace = parser.parse_args(['--endpoint-type=admin'])
|
||||
self.assertEqual('admin', namespace.endpoint_type)
|
||||
|
||||
def test_endpoint_environment_variable(self):
|
||||
fixture = fixtures.EnvironmentVariable("OS_ENDPOINT_TYPE",
|
||||
"public")
|
||||
self.useFixture(fixture)
|
||||
|
||||
shell = openstack_shell.QuantumShell('2.0')
|
||||
parser = shell.build_option_parser('descr', '2.0')
|
||||
|
||||
# $OS_ENDPOINT_TYPE but not --endpoint-type
|
||||
namespace = parser.parse_args([])
|
||||
self.assertEqual("public", namespace.endpoint_type)
|
||||
|
||||
# --endpoint-type and $OS_ENDPOINT_TYPE
|
||||
namespace = parser.parse_args(['--endpoint-type=admin'])
|
||||
self.assertEqual('admin', namespace.endpoint_type)
|
||||
|
Loading…
x
Reference in New Issue
Block a user