Allows specifying a name for a particular endpoint.

Previously, if there were multiple endpoints with the same type and region,
or without a region, the first endpoint would be returned. Now,
by specifying the name, a specific one can be used.

Co-Authored-By: Franklin Naval <franklin.naval@gmail.com>

Change-Id: Ife6d435e2aa84153d8717463930d45e5f21272f7
Closes-Bug: #1486834
diff --git a/releasenotes/notes/bug-1486834-7ebca15836ae27a9.yaml b/releasenotes/notes/bug-1486834-7ebca15836ae27a9.yaml
new file mode 100644
index 0000000..b2190f3
--- /dev/null
+++ b/releasenotes/notes/bug-1486834-7ebca15836ae27a9.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Tempest library auth interface now supports
+    filtering with catalog name.  Note that filtering by
+    name is only successful if a known service type is
+    provided.
diff --git a/tempest/lib/auth.py b/tempest/lib/auth.py
index ffcc4fb..42ce0f3 100644
--- a/tempest/lib/auth.py
+++ b/tempest/lib/auth.py
@@ -1,4 +1,5 @@
 # Copyright 2014 Hewlett-Packard Development Company, L.P.
+# Copyright 2016 Rackspace Inc.
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -368,18 +369,24 @@
     def base_url(self, filters, auth_data=None):
         """Base URL from catalog
 
-        Filters can be:
-        - service: compute, image, etc
-        - region: the service region
-        - endpoint_type: adminURL, publicURL, internalURL
-        - api_version: replace catalog version with this
-        - skip_path: take just the base URL
+        :param filters: Used to filter results
+            Filters can be:
+            - service: service type name such as compute, image, etc.
+            - region: service region name
+            - name: service name, only if service exists
+            - endpoint_type: type of endpoint such as
+                adminURL, publicURL, internalURL
+            - api_version: the version of api used to replace catalog version
+            - skip_path: skips the suffix path of the url and uses base URL
+        :rtype string
+        :return url with filters applied
         """
         if auth_data is None:
             auth_data = self.get_auth()
         token, _auth_data = auth_data
         service = filters.get('service')
         region = filters.get('region')
+        name = filters.get('name')
         endpoint_type = filters.get('endpoint_type', 'publicURL')
 
         if service is None:
@@ -388,17 +395,19 @@
         _base_url = None
         for ep in _auth_data['serviceCatalog']:
             if ep["type"] == service:
+                if name is not None and ep["name"] != name:
+                    continue
                 for _ep in ep['endpoints']:
                     if region is not None and _ep['region'] == region:
                         _base_url = _ep.get(endpoint_type)
                 if not _base_url:
-                    # No region matching, use the first
+                    # No region or name matching, use the first
                     _base_url = ep['endpoints'][0].get(endpoint_type)
                 break
         if _base_url is None:
             raise exceptions.EndpointNotFound(
-                "service: %s, region: %s, endpoint_type: %s" %
-                (service, region, endpoint_type))
+                "service: %s, region: %s, endpoint_type: %s, name: %s" %
+                (service, region, endpoint_type, name))
         return apply_url_filters(_base_url, filters)
 
     def is_expired(self, auth_data):
@@ -489,18 +498,24 @@
         the auth_data. In such case, as long as the requested service is
         'identity', we can use the original auth URL to build the base_url.
 
-        Filters can be:
-        - service: compute, image, etc
-        - region: the service region
-        - endpoint_type: adminURL, publicURL, internalURL
-        - api_version: replace catalog version with this
-        - skip_path: take just the base URL
+        :param filters: Used to filter results
+            Filters can be:
+            - service: service type name such as compute, image, etc.
+            - region: service region name
+            - name: service name, only if service exists
+            - endpoint_type: type of endpoint such as
+                adminURL, publicURL, internalURL
+            - api_version: the version of api used to replace catalog version
+            - skip_path: skips the suffix path of the url and uses base URL
+        :rtype string
+        :return url with filters applied
         """
         if auth_data is None:
             auth_data = self.get_auth()
         token, _auth_data = auth_data
         service = filters.get('service')
         region = filters.get('region')
+        name = filters.get('name')
         endpoint_type = filters.get('endpoint_type', 'public')
 
         if service is None:
@@ -513,7 +528,15 @@
         # Select entries with matching service type
         service_catalog = [ep for ep in catalog if ep['type'] == service]
         if len(service_catalog) > 0:
-            service_catalog = service_catalog[0]['endpoints']
+            if name is not None:
+                service_catalog = (
+                    [ep for ep in service_catalog if ep['name'] == name])
+                if len(service_catalog) > 0:
+                    service_catalog = service_catalog[0]['endpoints']
+                else:
+                    raise exceptions.EndpointNotFound(name)
+            else:
+                service_catalog = service_catalog[0]['endpoints']
         else:
             if len(catalog) == 0 and service == 'identity':
                 # NOTE(andreaf) If there's no catalog at all and the service
@@ -533,7 +556,7 @@
         filtered_catalog = [ep for ep in filtered_catalog if
                             ep['region'] == region]
         if len(filtered_catalog) == 0:
-            # No matching region, take the first endpoint
+            # No matching region (or name), take the first endpoint
             filtered_catalog = [service_catalog[0]]
         # There should be only one match. If not take the first.
         _base_url = filtered_catalog[0].get('url', None)
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index 30750de..03ec3c0 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -54,6 +54,8 @@
     :param auth_provider: an auth provider object used to wrap requests in auth
     :param str service: The service name to use for the catalog lookup
     :param str region: The region to use for the catalog lookup
+    :param str name: The endpoint name to use for the catalog lookup; this
+                     returns only if the service exists
     :param str endpoint_type: The endpoint type to use for the catalog lookup
     :param int build_interval: Time in seconds between to status checks in
                                wait loops
@@ -76,10 +78,11 @@
                  endpoint_type='publicURL',
                  build_interval=1, build_timeout=60,
                  disable_ssl_certificate_validation=False, ca_certs=None,
-                 trace_requests=''):
+                 trace_requests='', name=None):
         self.auth_provider = auth_provider
         self.service = service
         self.region = region
+        self.name = name
         self.endpoint_type = endpoint_type
         self.build_interval = build_interval
         self.build_timeout = build_timeout
@@ -191,7 +194,8 @@
         _filters = dict(
             service=self.service,
             endpoint_type=self.endpoint_type,
-            region=self.region
+            region=self.region,
+            name=self.name
         )
         if self.api_version is not None:
             _filters['api_version'] = self.api_version
diff --git a/tempest/tests/lib/fake_identity.py b/tempest/tests/lib/fake_identity.py
index c903e47..831f8b5 100644
--- a/tempest/tests/lib/fake_identity.py
+++ b/tempest/tests/lib/fake_identity.py
@@ -100,7 +100,8 @@
 
     ],
     "type": "compute",
-    "id": "fake_compute_endpoint"
+    "id": "fake_compute_endpoint",
+    "name": "nova"
 }
 
 CATALOG_V3 = [COMPUTE_ENDPOINTS_V3, ]
diff --git a/tempest/tests/lib/test_auth.py b/tempest/tests/lib/test_auth.py
index c253187..c08bf6a 100644
--- a/tempest/tests/lib/test_auth.py
+++ b/tempest/tests/lib/test_auth.py
@@ -360,6 +360,58 @@
         self.assertRaises(exceptions.EndpointNotFound,
                           self._test_base_url_helper, None, self.filters)
 
+    def test_base_url_with_known_name(self):
+        """If name and service is known, return the endpoint."""
+        self.filters = {
+            'service': 'compute',
+            'endpoint_type': 'publicURL',
+            'region': 'FakeRegion',
+            'name': 'nova'
+        }
+        expected = self._get_result_url_from_endpoint(
+            self._endpoints[0]['endpoints'][1])
+        self._test_base_url_helper(expected, self.filters)
+
+    def test_base_url_with_known_name_and_unknown_servce(self):
+        """Test with Known Name and Unknown service
+
+        If the name is known but the service is unknown, raise an exception.
+        """
+        self.filters = {
+            'service': 'AintNoBodyKnowThatService',
+            'endpoint_type': 'publicURL',
+            'region': 'FakeRegion',
+            'name': 'AintNoBodyKnowThatName'
+        }
+        self.assertRaises(exceptions.EndpointNotFound,
+                          self._test_base_url_helper, None, self.filters)
+
+    def test_base_url_with_unknown_name_and_known_service(self):
+        """Test with Unknown Name and Known Service
+
+        If the name is unknown, raise an exception.  Note that filtering by
+        name is only successful service exists.
+        """
+
+        self.filters = {
+            'service': 'compute',
+            'endpoint_type': 'publicURL',
+            'region': 'FakeRegion',
+            'name': 'AintNoBodyKnowThatName'
+        }
+        self.assertRaises(exceptions.EndpointNotFound,
+                          self._test_base_url_helper, None, self.filters)
+
+    def test_base_url_without_name(self):
+        self.filters = {
+            'service': 'compute',
+            'endpoint_type': 'publicURL',
+            'region': 'FakeRegion',
+        }
+        expected = self._get_result_url_from_endpoint(
+            self._endpoints[0]['endpoints'][1])
+        self._test_base_url_helper(expected, self.filters)
+
     def test_base_url_with_api_version_filter(self):
         self.filters = {
             'service': 'compute',
diff --git a/tempest/tests/lib/test_rest_client.py b/tempest/tests/lib/test_rest_client.py
index 2a6fad5..d5f7a55 100644
--- a/tempest/tests/lib/test_rest_client.py
+++ b/tempest/tests/lib/test_rest_client.py
@@ -633,6 +633,7 @@
         expected = {'api_version': 'v1',
                     'endpoint_type': 'publicURL',
                     'region': None,
+                    'name': None,
                     'service': None,
                     'skip_path': True}
         self.rest_client.skip_path()
@@ -643,6 +644,7 @@
         expected = {'api_version': 'v1',
                     'endpoint_type': 'publicURL',
                     'region': None,
+                    'name': None,
                     'service': None}
         self.assertEqual(expected, self.rest_client.filters)