File add-endpoint-type-option.patch of Package python-quantumclient

From c4d28e27560e59018bc69b65cf31b488c623fa65 Mon Sep 17 00:00:00 2001
From: Carl Baldwin <carl.baldwin@hp.com>
Date: Tue, 30 Apr 2013 15:20:10 -0600
Subject: [PATCH] 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
---
 quantumclient/client.py                |  21 +++--
 quantumclient/common/clientmanager.py  |   3 +
 quantumclient/common/exceptions.py     |   8 ++
 quantumclient/shell.py                 |   6 ++
 quantumclient/tests/unit/test_auth.py  | 154 +++++++++++++++++++++++++++++++++
 quantumclient/tests/unit/test_shell.py |  28 ++++++
 quantumclient/v2_0/client.py           |   3 +
 7 files changed, 217 insertions(+), 6 deletions(-)

diff --git a/quantumclient/client.py b/quantumclient/client.py
index e9d5949..a68b600 100644
--- a/quantumclient/client.py
+++ b/quantumclient/client.py
@@ -60,10 +60,11 @@ 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', [])
         matching_endpoints = []
@@ -81,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]
 
 
@@ -93,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'
@@ -167,7 +173,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':
@@ -214,7 +220,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()
 
diff --git a/quantumclient/common/clientmanager.py b/quantumclient/common/clientmanager.py
index 90aa48e..ab36df9 100644
--- a/quantumclient/common/clientmanager.py
+++ b/quantumclient/common/clientmanager.py
@@ -50,6 +50,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,
@@ -60,6 +61,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
@@ -78,6 +80,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
diff --git a/quantumclient/common/exceptions.py b/quantumclient/common/exceptions.py
index 1df133d..9d2a563 100644
--- a/quantumclient/common/exceptions.py
+++ b/quantumclient/common/exceptions.py
@@ -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."""
 
diff --git a/quantumclient/shell.py b/quantumclient/shell.py
index b44e3d9..ac23dcb 100644
--- a/quantumclient/shell.py
+++ b/quantumclient/shell.py
@@ -405,6 +405,11 @@ class QuantumShell(App):
             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'),
             help='Defaults to env[OS_URL]')
@@ -582,6 +587,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
 
diff --git a/quantumclient/tests/unit/test_auth.py b/quantumclient/tests/unit/test_auth.py
index e8e64b3..4daa4e4 100644
--- a/quantumclient/tests/unit/test_auth.py
+++ b/quantumclient/tests/unit/test_auth.py
@@ -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)
diff --git a/quantumclient/tests/unit/test_shell.py b/quantumclient/tests/unit/test_shell.py
index 8491d02..0d3d202 100644
--- a/quantumclient/tests/unit/test_shell.py
+++ b/quantumclient/tests/unit/test_shell.py
@@ -127,3 +127,31 @@ class ShellTest(testtools.TestCase):
         quant_shell = openstack_shell.QuantumShell('2.0')
         result = quant_shell.build_option_parser('descr', '2.0')
         self.assertEqual(True, isinstance(result, argparse.ArgumentParser))
+
+    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)
diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py
index 3010354..0edddee 100644
--- a/quantumclient/v2_0/client.py
+++ b/quantumclient/v2_0/client.py
@@ -118,6 +118,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
-- 
1.8.1.4

openSUSE Build Service is sponsored by