Merge "Allows identity endpoint to be specified as URI"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index d12da44..8429cd0 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -10,18 +10,8 @@
 # Ignore SSL certificate validation failures? Use when in testing
 # environments that have self-signed SSL certs.
 disable_ssl_certificate_validation = False
-# Set to True if your test environment's Keystone authentication service should
-# be accessed over HTTPS
-use_ssl = False
-# This is the main host address of the authentication service API
-host = 127.0.0.1
-# Port that the authentication service API is running on
-port = 5000
-# Version of the authentication service API (a string)
-api_version = v2.0
-# Path to the authentication service tokens resource (do not modify unless you
-# have a custom authentication API and are not using Keystone)
-path = tokens
+# URL for where to find the OpenStack Identity API endpoint (Keystone)
+uri = http://127.0.0.1:5000/v2.0/
 # Should typically be left as keystone unless you have a non-Keystone
 # authentication API service
 strategy = keystone
diff --git a/stress/tools/nova_destroy_all.py b/stress/tools/nova_destroy_all.py
index 21cac11..0070e72 100755
--- a/stress/tools/nova_destroy_all.py
+++ b/stress/tools/nova_destroy_all.py
@@ -24,7 +24,7 @@
 compute = tempest.config.TempestConfig().compute
 
 nt = client.Client(compute.username, compute.password,
-                   compute.tenant_name, identity.auth_url)
+                   compute.tenant_name, identity.uri)
 
 flavor_list = nt.flavors.list()
 server_list = nt.servers.list()
diff --git a/stress/tools/nova_status.py b/stress/tools/nova_status.py
index d413d7a..f9bc707 100755
--- a/stress/tools/nova_status.py
+++ b/stress/tools/nova_status.py
@@ -23,10 +23,10 @@
 identity = tempest.config.TempestConfig().identity
 compute = tempest.config.TempestConfig().compute
 print compute.username, compute.password,\
-    compute.tenant_name, identity.auth_url
+    compute.tenant_name, identity.uri
 
 nt = client.Client(compute.username, compute.password,
-                   compute.tenant_name, identity.auth_url)
+                   compute.tenant_name, identity.uri)
 
 flavor_list = nt.flavors.list()
 server_list = nt.servers.list()
diff --git a/tempest/clients.py b/tempest/clients.py
index 245f852..ac163b6 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -166,7 +166,7 @@
                    "tenant_name: %(tenant_name)s") % locals()
             raise exceptions.InvalidConfiguration(msg)
 
-        self.auth_url = self.config.identity.auth_url
+        self.auth_url = self.config.identity.uri
 
         if self.config.identity.strategy == 'keystone':
             client_args = (self.config, self.username, self.password,
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 7beef3f..4213b10 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -116,6 +116,10 @@
         Provides authentication via Keystone
         """
 
+        # Normalize URI to ensure /tokens is in it.
+        if 'tokens' not in auth_url:
+            auth_url = auth_url.rstrip('/') + '/tokens'
+
         creds = {
             'auth': {
                 'passwordCredentials': {
diff --git a/tempest/config.py b/tempest/config.py
index 45e8bc4..70b1c79 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -34,21 +34,25 @@
     cfg.BoolOpt('disable_ssl_certificate_validation',
                 default=False,
                 help="Set to True if using self-signed SSL certificates."),
+    cfg.StrOpt('uri',
+               default=None,
+               help="Full URI of the OpenStack Identity API (Keystone)"),
     cfg.StrOpt('host',
                default="127.0.0.1",
-               help="Host IP for making Identity API requests."),
+               help="(DEPRECATED, use uri) Host IP for making Identity "
+                    "API requests."),
     cfg.IntOpt('port',
                default=8773,
-               help="Port for the Identity service."),
+               help="(DEPRECATED, use uri) Port for the Identity service."),
     cfg.StrOpt('api_version',
                default="v1.1",
-               help="Version of the Identity API"),
+               help="(DEPRECATED, use uri) Version of the Identity API"),
     cfg.StrOpt('path',
                default='/',
-               help="Path of API request"),
+               help="(IGNORED) Path of API request"),
     cfg.BoolOpt('use_ssl',
                 default=False,
-                help="Specifies if we are using https."),
+                help="(DEPRECATED, use uri) Specifies if we are using https."),
     cfg.StrOpt('strategy',
                default='keystone',
                help="Which auth method does the environment use? "
@@ -64,16 +68,15 @@
     for opt in IdentityGroup:
         conf.register_opt(opt, group='identity')
 
+    # Fall back to piecemeal identity URI for legacy support
     authurl = data_utils.build_url(conf.identity.host,
                                    str(conf.identity.port),
                                    conf.identity.api_version,
-                                   conf.identity.path,
+                                   path='',  # Ignore path...
                                    use_ssl=conf.identity.use_ssl)
 
-    auth_url = cfg.StrOpt('auth_url',
-                          default=authurl,
-                          help="The Identity URL (derived)")
-    conf.register_opt(auth_url, group="identity")
+    if not conf.identity.uri:
+        conf.identity.uri = authurl
 
 
 identity_admin_group = cfg.OptGroup(name='identity-admin',
diff --git a/tempest/manager.py b/tempest/manager.py
index cfe8a5c..8e7cbd1 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -126,8 +126,7 @@
                    "tenant_name: %(tenant_name)s") % locals()
             raise exceptions.InvalidConfiguration(msg)
 
-        # Novaclient adds a /tokens/ part to the auth URL automatically
-        auth_url = self.config.identity.auth_url.rstrip('tokens')
+        auth_url = self.config.identity.uri
         dscv = self.config.identity.disable_ssl_certificate_validation
 
         client_args = (username, password, tenant_name, auth_url)
@@ -166,7 +165,7 @@
                    "tenant_name: %(tenant_name)s") % locals()
             raise exceptions.InvalidConfiguration(msg)
 
-        auth_url = self.config.identity.auth_url.rstrip('tokens')
+        auth_url = self.config.identity.uri
         dscv = self.config.identity.disable_ssl_certificate_validation
 
         return keystoneclient.v2_0.client.Client(username=username,
@@ -192,7 +191,7 @@
                    "tenant_name: %(tenant_name)s") % locals()
             raise exceptions.InvalidConfiguration(msg)
 
-        auth_url = self.config.identity.auth_url.rstrip('tokens')
+        auth_url = self.config.identity.uri
         dscv = self.config.identity.disable_ssl_certificate_validation
 
         return quantumclient.v2_0.client.Client(username=username,
@@ -233,7 +232,11 @@
                    "tenant_name: %(tenant_name)s") % locals()
             raise exceptions.InvalidConfiguration(msg)
 
-        auth_url = self.config.identity.auth_url
+        auth_url = self.config.identity.uri
+
+        # Ensure /tokens is in the URL for Keystone...
+        if 'tokens' not in auth_url:
+            auth_url = auth_url.rstrip('/') + '/tokens'
 
         if self.config.identity.strategy == 'keystone':
             client_args = (self.config, username, password, auth_url,
diff --git a/tempest/services/boto/__init__.py b/tempest/services/boto/__init__.py
index 1365435..83bf1f9 100644
--- a/tempest/services/boto/__init__.py
+++ b/tempest/services/boto/__init__.py
@@ -38,10 +38,6 @@
         self.connection_timeout = str(config.boto.http_socket_timeout)
         self.num_retries = str(config.boto.num_retries)
         self.build_timeout = config.boto.build_timeout
-        # We do not need the "path":  "/token" part
-        if auth_url:
-            auth_url = re.sub("(.*)" + re.escape(config.identity.path) + "$",
-                              "\\1", auth_url)
         self.ks_cred = {"username": username,
                         "password": password,
                         "auth_url": auth_url,
diff --git a/tempest/services/identity/json/admin_client.py b/tempest/services/identity/json/admin_client.py
index a0da4ca..7b1cb4b 100644
--- a/tempest/services/identity/json/admin_client.py
+++ b/tempest/services/identity/json/admin_client.py
@@ -206,7 +206,14 @@
 class TokenClientJSON(RestClient):
 
     def __init__(self, config):
-        self.auth_url = config.identity.auth_url
+        auth_url = config.identity.uri
+
+        # TODO(jaypipes) Why is this all repeated code in here?
+        # Normalize URI to ensure /tokens is in it.
+        if 'tokens' not in auth_url:
+            auth_url = auth_url.rstrip('/') + '/tokens'
+
+        self.auth_url = auth_url
         self.config = config
 
     def auth(self, user, password, tenant):
diff --git a/tempest/services/identity/xml/admin_client.py b/tempest/services/identity/xml/admin_client.py
index 46a1255..1c71d87 100644
--- a/tempest/services/identity/xml/admin_client.py
+++ b/tempest/services/identity/xml/admin_client.py
@@ -242,7 +242,14 @@
 class TokenClientXML(RestClientXML):
 
     def __init__(self, config):
-        self.auth_url = config.identity.auth_url
+        auth_url = config.identity.uri
+
+        # TODO(jaypipes) Why is this all repeated code in here?
+        # Normalize URI to ensure /tokens is in it.
+        if 'tokens' not in auth_url:
+            auth_url = auth_url.rstrip('/') + '/tokens'
+
+        self.auth_url = auth_url
         self.config = config
 
     def auth(self, user, password, tenant):
diff --git a/tempest/services/image/service.py b/tempest/services/image/service.py
index 3ffdd10..cf4ff4d 100644
--- a/tempest/services/image/service.py
+++ b/tempest/services/image/service.py
@@ -42,8 +42,7 @@
             import keystoneclient.v2_0.client
 
             dscv = self.config.identity.disable_ssl_certificate_validation
-
-            auth_url = self.config.identity.auth_url.rstrip('tokens')
+            auth_url = self.config.identity.uri
             keystone = keystoneclient.v2_0.client.Client(
                     username=config.images.username,
                     password=config.images.password,
diff --git a/tempest/tests/compute/admin/test_quotas.py b/tempest/tests/compute/admin/test_quotas.py
index 452de80..eaf245a 100644
--- a/tempest/tests/compute/admin/test_quotas.py
+++ b/tempest/tests/compute/admin/test_quotas.py
@@ -30,7 +30,7 @@
         adm_user = cls.config.compute_admin.username
         adm_pass = cls.config.compute_admin.password
         adm_tenant = cls.config.compute_admin.tenant_name
-        auth_url = cls.config.identity.auth_url
+        auth_url = cls.config.identity.uri
 
         cls.adm_client = adm_quotas.AdminQuotasClient(cls.config, adm_user,
                                                       adm_pass, auth_url,
diff --git a/tempest/tests/compute/base.py b/tempest/tests/compute/base.py
index 8044d01..c44a2f1 100644
--- a/tempest/tests/compute/base.py
+++ b/tempest/tests/compute/base.py
@@ -89,7 +89,7 @@
             cls.config,
             cls.config.identity_admin.username,
             cls.config.identity_admin.password,
-            cls.config.identity.auth_url
+            cls.config.identity.uri
         )
 
     @classmethod
diff --git a/tempest/tests/volume/admin/base.py b/tempest/tests/volume/admin/base.py
index 81c7c78..d35efbc 100644
--- a/tempest/tests/volume/admin/base.py
+++ b/tempest/tests/volume/admin/base.py
@@ -35,7 +35,7 @@
         cls.adm_user = cls.config.compute_admin.username
         cls.adm_pass = cls.config.compute_admin.password
         cls.adm_tenant = cls.config.compute_admin.tenant_name
-        cls.auth_url = cls.config.identity.auth_url
+        cls.auth_url = cls.config.identity.uri
 
         if not cls.adm_user and cls.adm_pass and cls.adm_tenant:
             msg = ("Missing Volume Admin API credentials "
diff --git a/tempest/tests/volume/admin/test_volume_types.py b/tempest/tests/volume/admin/test_volume_types.py
index 65c975a..a907a79 100644
--- a/tempest/tests/volume/admin/test_volume_types.py
+++ b/tempest/tests/volume/admin/test_volume_types.py
@@ -28,7 +28,7 @@
         adm_user = cls.config.compute_admin.username
         adm_pass = cls.config.compute_admin.password
         adm_tenant = cls.config.compute_admin.tenant_name
-        auth_url = cls.config.identity.auth_url
+        auth_url = cls.config.identity.uri
 
         cls.client = volume_types_client.VolumeTypesClientJSON(cls.config,
                                                                adm_user,
diff --git a/tempest/tests/volume/admin/test_volume_types_extra_specs.py b/tempest/tests/volume/admin/test_volume_types_extra_specs.py
index 9734c42..c5a1fa9 100644
--- a/tempest/tests/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/tests/volume/admin/test_volume_types_extra_specs.py
@@ -29,7 +29,7 @@
         adm_user = cls.config.compute_admin.username
         adm_pass = cls.config.compute_admin.password
         adm_tenant = cls.config.compute_admin.tenant_name
-        auth_url = cls.config.identity.auth_url
+        auth_url = cls.config.identity.uri
 
         cls.client = volume_types_client.VolumeTypesClientJSON(cls.config,
                                                                adm_user,
diff --git a/tools/tempest_coverage.py b/tools/tempest_coverage.py
index 73dcfbc..6e7ac04 100755
--- a/tools/tempest_coverage.py
+++ b/tools/tempest_coverage.py
@@ -139,7 +139,7 @@
 def main(argv):
     CLI = parse_opts(argv)
     client_args = (CONF, CONF.compute_admin.username,
-                   CONF.compute_admin.password, CONF.identity.auth_url,
+                   CONF.compute_admin.password, CONF.identity.uri,
                    CONF.compute_admin.tenant_name)
     coverage_client = CoverageClientJSON(*client_args)