Merge "identity v3 token"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 3147859..f9f02dc 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -12,6 +12,8 @@
 disable_ssl_certificate_validation = False
 # URL for where to find the OpenStack Identity API endpoint (Keystone)
 uri = http://127.0.0.1:5000/v2.0/
+# URL for where to find the OpenStack V3 Identity API endpoint (Keystone)
+uri_v3 = http://127.0.0.1:5000/v3/
 # Should typically be left as keystone unless you have a non-Keystone
 # authentication API service
 strategy = keystone
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index b19b9d9..9883c00 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -73,6 +73,8 @@
         cls.flavor_ref_alt = cls.config.compute.flavor_ref_alt
         cls.servers = []
 
+        cls.servers_client_v3_auth = os.servers_client_v3_auth
+
     @classmethod
     def _get_identity_admin_client(cls):
         """
diff --git a/tempest/clients.py b/tempest/clients.py
index d78ce61..e85809f 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -264,14 +264,26 @@
             raise exceptions.InvalidConfiguration(msg)
 
         self.auth_url = self.config.identity.uri
+        self.auth_url_v3 = self.config.identity.uri_v3
 
         if self.config.identity.strategy == 'keystone':
             client_args = (self.config, self.username, self.password,
                            self.auth_url, self.tenant_name)
+
+            if self.auth_url_v3:
+                auth_version = 'v3'
+                client_args_v3_auth = (self.config, self.username,
+                                       self.password, self.auth_url_v3,
+                                       self.tenant_name, auth_version)
+            else:
+                client_args_v3_auth = None
+
         else:
             client_args = (self.config, self.username, self.password,
                            self.auth_url)
 
+            client_args_v3_auth = None
+
         try:
             self.servers_client = SERVERS_CLIENTS[interface](*client_args)
             self.limits_client = LIMITS_CLIENTS[interface](*client_args)
@@ -305,6 +317,13 @@
             self.tenant_usages_client = \
                 TENANT_USAGES_CLIENT[interface](*client_args)
             self.policy_client = POLICY_CLIENT[interface](*client_args)
+
+            if client_args_v3_auth:
+                self.servers_client_v3_auth = SERVERS_CLIENTS[interface](
+                    *client_args_v3_auth)
+            else:
+                self.servers_client_v3_auth = None
+
         except KeyError:
             msg = "Unsupported interface type `%s'" % interface
             raise exceptions.InvalidConfiguration(msg)
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index d81af83..baa3c03 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -1,6 +1,7 @@
 # vim: tabstop=4 shiftwidth=4 softtabstop=4
 
 # Copyright 2012 OpenStack, LLC
+# Copyright 2013 IBM Corp.
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -36,12 +37,14 @@
     TYPE = "json"
     LOG = logging.getLogger(__name__)
 
-    def __init__(self, config, user, password, auth_url, tenant_name=None):
+    def __init__(self, config, user, password, auth_url, tenant_name=None,
+                 auth_version='v2'):
         self.config = config
         self.user = user
         self.password = password
         self.auth_url = auth_url
         self.tenant_name = tenant_name
+        self.auth_version = auth_version
 
         self.service = None
         self.token = None
@@ -70,11 +73,16 @@
         """
 
         if self.strategy == 'keystone':
-            self.token, self.base_url = self.keystone_auth(self.user,
-                                                           self.password,
-                                                           self.auth_url,
-                                                           self.service,
-                                                           self.tenant_name)
+
+            if self.auth_version == 'v3':
+                auth_func = self.identity_auth_v3
+            else:
+                auth_func = self.keystone_auth
+
+            self.token, self.base_url = (
+                auth_func(self.user, self.password, self.auth_url,
+                          self.service, self.tenant_name))
+
         else:
             self.token, self.base_url = self.basic_auth(self.user,
                                                         self.password,
@@ -116,7 +124,7 @@
 
     def keystone_auth(self, user, password, auth_url, service, tenant_name):
         """
-        Provides authentication via Keystone.
+        Provides authentication via Keystone using v2 identity API.
         """
 
         # Normalize URI to ensure /tokens is in it.
@@ -170,6 +178,90 @@
         raise exceptions.IdentityError('Unexpected status code {0}'.format(
             resp.status))
 
+    def identity_auth_v3(self, user, password, auth_url, service,
+                         project_name, domain_id='default'):
+        """Provides authentication using Identity API v3."""
+
+        req_url = auth_url.rstrip('/') + '/auth/tokens'
+
+        creds = {
+            "auth": {
+                "identity": {
+                    "methods": ["password"],
+                    "password": {
+                        "user": {
+                            "name": user, "password": password,
+                            "domain": {"id": domain_id}
+                        }
+                    }
+                },
+                "scope": {
+                    "project": {
+                        "domain": {"id": domain_id},
+                        "name": project_name
+                    }
+                }
+            }
+        }
+
+        headers = {'Content-Type': 'application/json'}
+        body = json.dumps(creds)
+        resp, body = self.http_obj.request(req_url, 'POST',
+                                           headers=headers, body=body)
+
+        if resp.status == 201:
+            try:
+                token = resp['x-subject-token']
+            except Exception:
+                self.LOG.exception("Failed to obtain token using V3"
+                                   " authentication (auth URL is '%s')" %
+                                   req_url)
+                raise
+
+            catalog = json.loads(body)['token']['catalog']
+
+            mgmt_url = None
+            for service_info in catalog:
+                if service_info['type'] != service:
+                    continue  # this isn't the entry for us.
+
+                endpoints = service_info['endpoints']
+
+                # Look for an endpoint in the region if configured.
+                if service in self.region:
+                    region = self.region[service]
+
+                    for ep in endpoints:
+                        if ep['region'] != region:
+                            continue
+
+                        mgmt_url = ep['url']
+                        # FIXME(blk-u): this isn't handling endpoint type
+                        # (public, internal, admin).
+                        break
+
+                if not mgmt_url:
+                    # Didn't find endpoint for region, use the first.
+
+                    ep = endpoints[0]
+                    mgmt_url = ep['url']
+                    # FIXME(blk-u): this isn't handling endpoint type
+                    # (public, internal, admin).
+
+                break
+
+            return token, mgmt_url
+
+        elif resp.status == 401:
+            raise exceptions.AuthenticationFailure(user=user,
+                                                   password=password)
+        else:
+            self.LOG.error("Failed to obtain token using V3 authentication"
+                           " (auth URL is '%s'), the response status is %s" %
+                           (req_url, resp.status))
+            raise exceptions.AuthenticationFailure(user=user,
+                                                   password=password)
+
     def post(self, url, body, headers):
         return self.request('POST', url, headers, body)
 
diff --git a/tempest/config.py b/tempest/config.py
index 8f3e574..7164b95 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -37,7 +37,9 @@
                 help="Set to True if using self-signed SSL certificates."),
     cfg.StrOpt('uri',
                default=None,
-               help="Full URI of the OpenStack Identity API (Keystone)"),
+               help="Full URI of the OpenStack Identity API (Keystone), v2"),
+    cfg.StrOpt('uri_v3',
+               help='Full URI of the OpenStack Identity API (Keystone), v3'),
     cfg.StrOpt('strategy',
                default='keystone',
                help="Which auth method does the environment use? "
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 3569b50..d4822da 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -26,9 +26,11 @@
 
 class ServersClientJSON(RestClient):
 
-    def __init__(self, config, username, password, auth_url, tenant_name=None):
+    def __init__(self, config, username, password, auth_url, tenant_name=None,
+                 auth_version='v2'):
         super(ServersClientJSON, self).__init__(config, username, password,
-                                                auth_url, tenant_name)
+                                                auth_url, tenant_name,
+                                                auth_version=auth_version)
         self.service = self.config.compute.catalog_type
 
     def create_server(self, name, image_ref, flavor_ref, **kwargs):
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index 6d811a5..1ec4df0 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -111,9 +111,11 @@
 
 class ServersClientXML(RestClientXML):
 
-    def __init__(self, config, username, password, auth_url, tenant_name=None):
+    def __init__(self, config, username, password, auth_url, tenant_name=None,
+                 auth_version='v2'):
         super(ServersClientXML, self).__init__(config, username, password,
-                                               auth_url, tenant_name)
+                                               auth_url, tenant_name,
+                                               auth_version=auth_version)
         self.service = self.config.compute.catalog_type
 
     def _parse_key_value(self, node):
diff --git a/tempest/tests/compute/test_auth_token.py b/tempest/tests/compute/test_auth_token.py
new file mode 100644
index 0000000..ca319a1
--- /dev/null
+++ b/tempest/tests/compute/test_auth_token.py
@@ -0,0 +1,52 @@
+# Copyright 2013 IBM Corp
+#
+#   Licensed under the Apache License, Version 2.0 (the "License"); you may
+#   not use this file except in compliance with the License. You may obtain
+#   a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#   License for the specific language governing permissions and limitations
+#   under the License.
+
+
+import testtools
+
+import tempest.config as config
+from tempest.tests.compute import base
+
+
+class AuthTokenTestJSON(base.BaseComputeTest):
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(AuthTokenTestJSON, cls).setUpClass()
+
+        cls.servers_v2 = cls.os.servers_client
+        cls.servers_v3 = cls.os.servers_client_v3_auth
+
+    def test_v2_token(self):
+        # Can get a token using v2 of the identity API and use that to perform
+        # an operation on the compute service.
+
+        # Doesn't matter which compute API is used,
+        # picking list_servers because it's easy.
+        self.servers_v2.list_servers()
+
+    @testtools.skipIf(not config.TempestConfig().identity.uri_v3,
+                      'v3 auth client not configured')
+    def test_v3_token(self):
+        # Can get a token using v3 of the identity API and use that to perform
+        # an operation on the compute service.
+
+        # Doesn't matter which compute API is used,
+        # picking list_servers because it's easy.
+        self.servers_v3.list_servers()
+
+
+class AuthTokenTestXML(AuthTokenTestJSON):
+    _interface = 'xml'