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'