Initial copy of api tests from tempest
This change is the result of running
tools/copy_api_tests_from_tempest.sh.
Change-Id: Ica02dbe1ed26f1bc9526ea9682756ebc5877cf4a
diff --git a/neutron/tests/tempest/services/__init__.py b/neutron/tests/tempest/services/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron/tests/tempest/services/__init__.py
diff --git a/neutron/tests/tempest/services/botoclients.py b/neutron/tests/tempest/services/botoclients.py
new file mode 100644
index 0000000..e2e080b
--- /dev/null
+++ b/neutron/tests/tempest/services/botoclients.py
@@ -0,0 +1,235 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 ConfigParser
+import contextlib
+from tempest_lib import exceptions as lib_exc
+import types
+import urlparse
+
+from neutron.tests.tempest import config
+from neutron.tests.tempest import exceptions
+
+import boto
+import boto.ec2
+import boto.s3.connection
+
+CONF = config.CONF
+
+
+class BotoClientBase(object):
+
+ ALLOWED_METHODS = set()
+
+ def __init__(self, username=None, password=None,
+ auth_url=None, tenant_name=None,
+ *args, **kwargs):
+ # FIXME(andreaf) replace credentials and auth_url with auth_provider
+
+ insecure_ssl = CONF.identity.disable_ssl_certificate_validation
+ self.ca_cert = CONF.identity.ca_certificates_file
+
+ self.connection_timeout = str(CONF.boto.http_socket_timeout)
+ self.num_retries = str(CONF.boto.num_retries)
+ self.build_timeout = CONF.boto.build_timeout
+ self.ks_cred = {"username": username,
+ "password": password,
+ "auth_url": auth_url,
+ "tenant_name": tenant_name,
+ "insecure": insecure_ssl,
+ "cacert": self.ca_cert}
+
+ def _keystone_aws_get(self):
+ # FIXME(andreaf) Move EC2 credentials to AuthProvider
+ import keystoneclient.v2_0.client
+
+ keystone = keystoneclient.v2_0.client.Client(**self.ks_cred)
+ ec2_cred_list = keystone.ec2.list(keystone.auth_user_id)
+ ec2_cred = None
+ for cred in ec2_cred_list:
+ if cred.tenant_id == keystone.auth_tenant_id:
+ ec2_cred = cred
+ break
+ else:
+ ec2_cred = keystone.ec2.create(keystone.auth_user_id,
+ keystone.auth_tenant_id)
+ if not all((ec2_cred, ec2_cred.access, ec2_cred.secret)):
+ raise lib_exc.NotFound("Unable to get access and secret keys")
+ return ec2_cred
+
+ def _config_boto_timeout(self, timeout, retries):
+ try:
+ boto.config.add_section("Boto")
+ except ConfigParser.DuplicateSectionError:
+ pass
+ boto.config.set("Boto", "http_socket_timeout", timeout)
+ boto.config.set("Boto", "num_retries", retries)
+
+ def _config_boto_ca_certificates_file(self, ca_cert):
+ if ca_cert is None:
+ return
+
+ try:
+ boto.config.add_section("Boto")
+ except ConfigParser.DuplicateSectionError:
+ pass
+ boto.config.set("Boto", "ca_certificates_file", ca_cert)
+
+ def __getattr__(self, name):
+ """Automatically creates methods for the allowed methods set."""
+ if name in self.ALLOWED_METHODS:
+ def func(self, *args, **kwargs):
+ with contextlib.closing(self.get_connection()) as conn:
+ return getattr(conn, name)(*args, **kwargs)
+
+ func.__name__ = name
+ setattr(self, name, types.MethodType(func, self, self.__class__))
+ setattr(self.__class__, name,
+ types.MethodType(func, None, self.__class__))
+ return getattr(self, name)
+ else:
+ raise AttributeError(name)
+
+ def get_connection(self):
+ self._config_boto_timeout(self.connection_timeout, self.num_retries)
+ self._config_boto_ca_certificates_file(self.ca_cert)
+ if not all((self.connection_data["aws_access_key_id"],
+ self.connection_data["aws_secret_access_key"])):
+ if all([self.ks_cred.get('auth_url'),
+ self.ks_cred.get('username'),
+ self.ks_cred.get('tenant_name'),
+ self.ks_cred.get('password')]):
+ ec2_cred = self._keystone_aws_get()
+ self.connection_data["aws_access_key_id"] = \
+ ec2_cred.access
+ self.connection_data["aws_secret_access_key"] = \
+ ec2_cred.secret
+ else:
+ raise exceptions.InvalidConfiguration(
+ "Unable to get access and secret keys")
+ return self.connect_method(**self.connection_data)
+
+
+class APIClientEC2(BotoClientBase):
+
+ def connect_method(self, *args, **kwargs):
+ return boto.connect_ec2(*args, **kwargs)
+
+ def __init__(self, *args, **kwargs):
+ super(APIClientEC2, self).__init__(*args, **kwargs)
+ insecure_ssl = CONF.identity.disable_ssl_certificate_validation
+ aws_access = CONF.boto.aws_access
+ aws_secret = CONF.boto.aws_secret
+ purl = urlparse.urlparse(CONF.boto.ec2_url)
+
+ region_name = CONF.compute.region
+ if not region_name:
+ region_name = CONF.identity.region
+ region = boto.ec2.regioninfo.RegionInfo(name=region_name,
+ endpoint=purl.hostname)
+ port = purl.port
+ if port is None:
+ if purl.scheme is not "https":
+ port = 80
+ else:
+ port = 443
+ else:
+ port = int(port)
+ self.connection_data = {"aws_access_key_id": aws_access,
+ "aws_secret_access_key": aws_secret,
+ "is_secure": purl.scheme == "https",
+ "validate_certs": not insecure_ssl,
+ "region": region,
+ "host": purl.hostname,
+ "port": port,
+ "path": purl.path}
+
+ ALLOWED_METHODS = set(('create_key_pair', 'get_key_pair',
+ 'delete_key_pair', 'import_key_pair',
+ 'get_all_key_pairs',
+ 'get_all_tags',
+ 'create_image', 'get_image',
+ 'register_image', 'deregister_image',
+ 'get_all_images', 'get_image_attribute',
+ 'modify_image_attribute', 'reset_image_attribute',
+ 'get_all_kernels',
+ 'create_volume', 'delete_volume',
+ 'get_all_volume_status', 'get_all_volumes',
+ 'get_volume_attribute', 'modify_volume_attribute'
+ 'bundle_instance', 'cancel_spot_instance_requests',
+ 'confirm_product_instanc',
+ 'get_all_instance_status', 'get_all_instances',
+ 'get_all_reserved_instances',
+ 'get_all_spot_instance_requests',
+ 'get_instance_attribute', 'monitor_instance',
+ 'monitor_instances', 'unmonitor_instance',
+ 'unmonitor_instances',
+ 'purchase_reserved_instance_offering',
+ 'reboot_instances', 'request_spot_instances',
+ 'reset_instance_attribute', 'run_instances',
+ 'start_instances', 'stop_instances',
+ 'terminate_instances',
+ 'attach_network_interface', 'attach_volume',
+ 'detach_network_interface', 'detach_volume',
+ 'get_console_output',
+ 'delete_network_interface', 'create_subnet',
+ 'create_network_interface', 'delete_subnet',
+ 'get_all_network_interfaces',
+ 'allocate_address', 'associate_address',
+ 'disassociate_address', 'get_all_addresses',
+ 'release_address',
+ 'create_snapshot', 'delete_snapshot',
+ 'get_all_snapshots', 'get_snapshot_attribute',
+ 'modify_snapshot_attribute',
+ 'reset_snapshot_attribute', 'trim_snapshots',
+ 'get_all_regions', 'get_all_zones',
+ 'get_all_security_groups', 'create_security_group',
+ 'delete_security_group', 'authorize_security_group',
+ 'authorize_security_group_egress',
+ 'revoke_security_group',
+ 'revoke_security_group_egress'))
+
+
+class ObjectClientS3(BotoClientBase):
+
+ def connect_method(self, *args, **kwargs):
+ return boto.connect_s3(*args, **kwargs)
+
+ def __init__(self, *args, **kwargs):
+ super(ObjectClientS3, self).__init__(*args, **kwargs)
+ insecure_ssl = CONF.identity.disable_ssl_certificate_validation
+ aws_access = CONF.boto.aws_access
+ aws_secret = CONF.boto.aws_secret
+ purl = urlparse.urlparse(CONF.boto.s3_url)
+ port = purl.port
+ if port is None:
+ if purl.scheme is not "https":
+ port = 80
+ else:
+ port = 443
+ else:
+ port = int(port)
+ self.connection_data = {"aws_access_key_id": aws_access,
+ "aws_secret_access_key": aws_secret,
+ "is_secure": purl.scheme == "https",
+ "validate_certs": not insecure_ssl,
+ "host": purl.hostname,
+ "port": port,
+ "calling_format": boto.s3.connection.
+ OrdinaryCallingFormat()}
+
+ ALLOWED_METHODS = set(('create_bucket', 'delete_bucket', 'generate_url',
+ 'get_all_buckets', 'get_bucket', 'delete_key',
+ 'lookup'))
diff --git a/neutron/tests/tempest/services/identity/__init__.py b/neutron/tests/tempest/services/identity/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron/tests/tempest/services/identity/__init__.py
diff --git a/neutron/tests/tempest/services/identity/v2/__init__.py b/neutron/tests/tempest/services/identity/v2/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron/tests/tempest/services/identity/v2/__init__.py
diff --git a/neutron/tests/tempest/services/identity/v2/json/__init__.py b/neutron/tests/tempest/services/identity/v2/json/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron/tests/tempest/services/identity/v2/json/__init__.py
diff --git a/neutron/tests/tempest/services/identity/v2/json/identity_client.py b/neutron/tests/tempest/services/identity/v2/json/identity_client.py
new file mode 100644
index 0000000..e3e7290
--- /dev/null
+++ b/neutron/tests/tempest/services/identity/v2/json/identity_client.py
@@ -0,0 +1,271 @@
+# 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 json
+from tempest_lib import exceptions as lib_exc
+
+from neutron.tests.tempest.common import service_client
+
+
+class IdentityClientJSON(service_client.ServiceClient):
+
+ def has_admin_extensions(self):
+ """
+ Returns True if the KSADM Admin Extensions are supported
+ False otherwise
+ """
+ if hasattr(self, '_has_admin_extensions'):
+ return self._has_admin_extensions
+ # Try something that requires admin
+ try:
+ self.list_roles()
+ self._has_admin_extensions = True
+ except Exception:
+ self._has_admin_extensions = False
+ return self._has_admin_extensions
+
+ def create_role(self, name):
+ """Create a role."""
+ post_body = {
+ 'name': name,
+ }
+ post_body = json.dumps({'role': post_body})
+ resp, body = self.post('OS-KSADM/roles', post_body)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, self._parse_resp(body))
+
+ def get_role(self, role_id):
+ """Get a role by its id."""
+ resp, body = self.get('OS-KSADM/roles/%s' % role_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['role'])
+
+ def create_tenant(self, name, **kwargs):
+ """
+ Create a tenant
+ name (required): New tenant name
+ description: Description of new tenant (default is none)
+ enabled <true|false>: Initial tenant status (default is true)
+ """
+ post_body = {
+ 'name': name,
+ 'description': kwargs.get('description', ''),
+ 'enabled': kwargs.get('enabled', True),
+ }
+ post_body = json.dumps({'tenant': post_body})
+ resp, body = self.post('tenants', post_body)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, self._parse_resp(body))
+
+ def delete_role(self, role_id):
+ """Delete a role."""
+ resp, body = self.delete('OS-KSADM/roles/%s' % str(role_id))
+ self.expected_success(204, resp.status)
+ return resp, body
+
+ def list_user_roles(self, tenant_id, user_id):
+ """Returns a list of roles assigned to a user for a tenant."""
+ url = '/tenants/%s/users/%s/roles' % (tenant_id, user_id)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBodyList(resp, self._parse_resp(body))
+
+ def assign_user_role(self, tenant_id, user_id, role_id):
+ """Add roles to a user on a tenant."""
+ resp, body = self.put('/tenants/%s/users/%s/roles/OS-KSADM/%s' %
+ (tenant_id, user_id, role_id), "")
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, self._parse_resp(body))
+
+ def remove_user_role(self, tenant_id, user_id, role_id):
+ """Removes a role assignment for a user on a tenant."""
+ resp, body = self.delete('/tenants/%s/users/%s/roles/OS-KSADM/%s' %
+ (tenant_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def delete_tenant(self, tenant_id):
+ """Delete a tenant."""
+ resp, body = self.delete('tenants/%s' % str(tenant_id))
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def get_tenant(self, tenant_id):
+ """Get tenant details."""
+ resp, body = self.get('tenants/%s' % str(tenant_id))
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, self._parse_resp(body))
+
+ def list_roles(self):
+ """Returns roles."""
+ resp, body = self.get('OS-KSADM/roles')
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBodyList(resp, self._parse_resp(body))
+
+ def list_tenants(self):
+ """Returns tenants."""
+ resp, body = self.get('tenants')
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['tenants'])
+
+ def get_tenant_by_name(self, tenant_name):
+ tenants = self.list_tenants()
+ for tenant in tenants:
+ if tenant['name'] == tenant_name:
+ return tenant
+ raise lib_exc.NotFound('No such tenant')
+
+ def update_tenant(self, tenant_id, **kwargs):
+ """Updates a tenant."""
+ body = self.get_tenant(tenant_id)
+ name = kwargs.get('name', body['name'])
+ desc = kwargs.get('description', body['description'])
+ en = kwargs.get('enabled', body['enabled'])
+ post_body = {
+ 'id': tenant_id,
+ 'name': name,
+ 'description': desc,
+ 'enabled': en,
+ }
+ post_body = json.dumps({'tenant': post_body})
+ resp, body = self.post('tenants/%s' % tenant_id, post_body)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, self._parse_resp(body))
+
+ def create_user(self, name, password, tenant_id, email, **kwargs):
+ """Create a user."""
+ post_body = {
+ 'name': name,
+ 'password': password,
+ 'email': email
+ }
+ if tenant_id is not None:
+ post_body['tenantId'] = tenant_id
+ if kwargs.get('enabled') is not None:
+ post_body['enabled'] = kwargs.get('enabled')
+ post_body = json.dumps({'user': post_body})
+ resp, body = self.post('users', post_body)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, self._parse_resp(body))
+
+ def update_user(self, user_id, **kwargs):
+ """Updates a user."""
+ put_body = json.dumps({'user': kwargs})
+ resp, body = self.put('users/%s' % user_id, put_body)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, self._parse_resp(body))
+
+ def get_user(self, user_id):
+ """GET a user."""
+ resp, body = self.get("users/%s" % user_id)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, self._parse_resp(body))
+
+ def delete_user(self, user_id):
+ """Delete a user."""
+ resp, body = self.delete("users/%s" % user_id)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def get_users(self):
+ """Get the list of users."""
+ resp, body = self.get("users")
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBodyList(resp, self._parse_resp(body))
+
+ def enable_disable_user(self, user_id, enabled):
+ """Enables or disables a user."""
+ put_body = {
+ 'enabled': enabled
+ }
+ put_body = json.dumps({'user': put_body})
+ resp, body = self.put('users/%s/enabled' % user_id, put_body)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, self._parse_resp(body))
+
+ def get_token(self, token_id):
+ """Get token details."""
+ resp, body = self.get("tokens/%s" % token_id)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, self._parse_resp(body))
+
+ def delete_token(self, token_id):
+ """Delete a token."""
+ resp, body = self.delete("tokens/%s" % token_id)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def list_users_for_tenant(self, tenant_id):
+ """List users for a Tenant."""
+ resp, body = self.get('/tenants/%s/users' % tenant_id)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBodyList(resp, self._parse_resp(body))
+
+ def get_user_by_username(self, tenant_id, username):
+ users = self.list_users_for_tenant(tenant_id)
+ for user in users:
+ if user['name'] == username:
+ return user
+ raise lib_exc.NotFound('No such user')
+
+ def create_service(self, name, type, **kwargs):
+ """Create a service."""
+ post_body = {
+ 'name': name,
+ 'type': type,
+ 'description': kwargs.get('description')
+ }
+ post_body = json.dumps({'OS-KSADM:service': post_body})
+ resp, body = self.post('/OS-KSADM/services', post_body)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, self._parse_resp(body))
+
+ def get_service(self, service_id):
+ """Get Service."""
+ url = '/OS-KSADM/services/%s' % service_id
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, self._parse_resp(body))
+
+ def list_services(self):
+ """List Service - Returns Services."""
+ resp, body = self.get('/OS-KSADM/services')
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBodyList(resp, self._parse_resp(body))
+
+ def delete_service(self, service_id):
+ """Delete Service."""
+ url = '/OS-KSADM/services/%s' % service_id
+ resp, body = self.delete(url)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def update_user_password(self, user_id, new_pass):
+ """Update User Password."""
+ put_body = {
+ 'password': new_pass,
+ 'id': user_id
+ }
+ put_body = json.dumps({'user': put_body})
+ resp, body = self.put('users/%s/OS-KSADM/password' % user_id, put_body)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, self._parse_resp(body))
+
+ def list_extensions(self):
+ """List all the extensions."""
+ resp, body = self.get('/extensions')
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp,
+ body['extensions']['values'])
diff --git a/neutron/tests/tempest/services/identity/v2/json/token_client.py b/neutron/tests/tempest/services/identity/v2/json/token_client.py
new file mode 100644
index 0000000..51d9db0
--- /dev/null
+++ b/neutron/tests/tempest/services/identity/v2/json/token_client.py
@@ -0,0 +1,110 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# 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 json
+from tempest_lib.common import rest_client
+from tempest_lib import exceptions as lib_exc
+
+from neutron.tests.tempest.common import service_client
+from neutron.tests.tempest import exceptions
+
+
+class TokenClientJSON(rest_client.RestClient):
+
+ def __init__(self, auth_url, disable_ssl_certificate_validation=None,
+ ca_certs=None, trace_requests=None):
+ dscv = disable_ssl_certificate_validation
+ super(TokenClientJSON, self).__init__(
+ None, None, None, disable_ssl_certificate_validation=dscv,
+ ca_certs=ca_certs, trace_requests=trace_requests)
+
+ # 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
+
+ def auth(self, user, password, tenant=None):
+ creds = {
+ 'auth': {
+ 'passwordCredentials': {
+ 'username': user,
+ 'password': password,
+ },
+ }
+ }
+
+ if tenant:
+ creds['auth']['tenantName'] = tenant
+
+ body = json.dumps(creds)
+ resp, body = self.post(self.auth_url, body=body)
+ self.expected_success(200, resp.status)
+
+ return service_client.ResponseBody(resp, body['access'])
+
+ def auth_token(self, token_id, tenant=None):
+ creds = {
+ 'auth': {
+ 'token': {
+ 'id': token_id,
+ },
+ }
+ }
+
+ if tenant:
+ creds['auth']['tenantName'] = tenant
+
+ body = json.dumps(creds)
+ resp, body = self.post(self.auth_url, body=body)
+ self.expected_success(200, resp.status)
+
+ return service_client.ResponseBody(resp, body['access'])
+
+ def request(self, method, url, extra_headers=False, headers=None,
+ body=None):
+ """A simple HTTP request interface."""
+ if headers is None:
+ headers = self.get_headers(accept_type="json")
+ elif extra_headers:
+ try:
+ headers.update(self.get_headers(accept_type="json"))
+ except (ValueError, TypeError):
+ headers = self.get_headers(accept_type="json")
+
+ resp, resp_body = self.raw_request(url, method,
+ headers=headers, body=body)
+ self._log_request(method, url, resp)
+
+ if resp.status in [401, 403]:
+ resp_body = json.loads(resp_body)
+ raise lib_exc.Unauthorized(resp_body['error']['message'])
+ elif resp.status not in [200, 201]:
+ raise exceptions.IdentityError(
+ 'Unexpected status code {0}'.format(resp.status))
+
+ if isinstance(resp_body, str):
+ resp_body = json.loads(resp_body)
+ return resp, resp_body
+
+ def get_token(self, user, password, tenant, auth_data=False):
+ """
+ Returns (token id, token data) for supplied credentials
+ """
+ body = self.auth(user, password, tenant)
+
+ if auth_data:
+ return body['token']['id'], body
+ else:
+ return body['token']['id']
diff --git a/neutron/tests/tempest/services/identity/v3/__init__.py b/neutron/tests/tempest/services/identity/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron/tests/tempest/services/identity/v3/__init__.py
diff --git a/neutron/tests/tempest/services/identity/v3/json/__init__.py b/neutron/tests/tempest/services/identity/v3/json/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron/tests/tempest/services/identity/v3/json/__init__.py
diff --git a/neutron/tests/tempest/services/identity/v3/json/credentials_client.py b/neutron/tests/tempest/services/identity/v3/json/credentials_client.py
new file mode 100644
index 0000000..4300c0f
--- /dev/null
+++ b/neutron/tests/tempest/services/identity/v3/json/credentials_client.py
@@ -0,0 +1,83 @@
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 json
+
+from neutron.tests.tempest.common import service_client
+
+
+class CredentialsClientJSON(service_client.ServiceClient):
+ api_version = "v3"
+
+ def create_credential(self, access_key, secret_key, user_id, project_id):
+ """Creates a credential."""
+ blob = "{\"access\": \"%s\", \"secret\": \"%s\"}" % (
+ access_key, secret_key)
+ post_body = {
+ "blob": blob,
+ "project_id": project_id,
+ "type": "ec2",
+ "user_id": user_id
+ }
+ post_body = json.dumps({'credential': post_body})
+ resp, body = self.post('credentials', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ body['credential']['blob'] = json.loads(body['credential']['blob'])
+ return service_client.ResponseBody(resp, body['credential'])
+
+ def update_credential(self, credential_id, **kwargs):
+ """Updates a credential."""
+ body = self.get_credential(credential_id)
+ cred_type = kwargs.get('type', body['type'])
+ access_key = kwargs.get('access_key', body['blob']['access'])
+ secret_key = kwargs.get('secret_key', body['blob']['secret'])
+ project_id = kwargs.get('project_id', body['project_id'])
+ user_id = kwargs.get('user_id', body['user_id'])
+ blob = "{\"access\": \"%s\", \"secret\": \"%s\"}" % (
+ access_key, secret_key)
+ post_body = {
+ "blob": blob,
+ "project_id": project_id,
+ "type": cred_type,
+ "user_id": user_id
+ }
+ post_body = json.dumps({'credential': post_body})
+ resp, body = self.patch('credentials/%s' % credential_id, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ body['credential']['blob'] = json.loads(body['credential']['blob'])
+ return service_client.ResponseBody(resp, body['credential'])
+
+ def get_credential(self, credential_id):
+ """To GET Details of a credential."""
+ resp, body = self.get('credentials/%s' % credential_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ body['credential']['blob'] = json.loads(body['credential']['blob'])
+ return service_client.ResponseBody(resp, body['credential'])
+
+ def list_credentials(self):
+ """Lists out all the available credentials."""
+ resp, body = self.get('credentials')
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['credentials'])
+
+ def delete_credential(self, credential_id):
+ """Deletes a credential."""
+ resp, body = self.delete('credentials/%s' % credential_id)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
diff --git a/neutron/tests/tempest/services/identity/v3/json/endpoints_client.py b/neutron/tests/tempest/services/identity/v3/json/endpoints_client.py
new file mode 100644
index 0000000..b60dd26
--- /dev/null
+++ b/neutron/tests/tempest/services/identity/v3/json/endpoints_client.py
@@ -0,0 +1,87 @@
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 json
+
+from neutron.tests.tempest.common import service_client
+
+
+class EndPointClientJSON(service_client.ServiceClient):
+ api_version = "v3"
+
+ def list_endpoints(self):
+ """GET endpoints."""
+ resp, body = self.get('endpoints')
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['endpoints'])
+
+ def create_endpoint(self, service_id, interface, url, **kwargs):
+ """Create endpoint.
+
+ Normally this function wouldn't allow setting values that are not
+ allowed for 'enabled'. Use `force_enabled` to set a non-boolean.
+
+ """
+ region = kwargs.get('region', None)
+ if 'force_enabled' in kwargs:
+ enabled = kwargs.get('force_enabled', None)
+ else:
+ enabled = kwargs.get('enabled', None)
+ post_body = {
+ 'service_id': service_id,
+ 'interface': interface,
+ 'url': url,
+ 'region': region,
+ 'enabled': enabled
+ }
+ post_body = json.dumps({'endpoint': post_body})
+ resp, body = self.post('endpoints', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['endpoint'])
+
+ def update_endpoint(self, endpoint_id, service_id=None, interface=None,
+ url=None, region=None, enabled=None, **kwargs):
+ """Updates an endpoint with given parameters.
+
+ Normally this function wouldn't allow setting values that are not
+ allowed for 'enabled'. Use `force_enabled` to set a non-boolean.
+
+ """
+ post_body = {}
+ if service_id is not None:
+ post_body['service_id'] = service_id
+ if interface is not None:
+ post_body['interface'] = interface
+ if url is not None:
+ post_body['url'] = url
+ if region is not None:
+ post_body['region'] = region
+ if 'force_enabled' in kwargs:
+ post_body['enabled'] = kwargs['force_enabled']
+ elif enabled is not None:
+ post_body['enabled'] = enabled
+ post_body = json.dumps({'endpoint': post_body})
+ resp, body = self.patch('endpoints/%s' % endpoint_id, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['endpoint'])
+
+ def delete_endpoint(self, endpoint_id):
+ """Delete endpoint."""
+ resp_header, resp_body = self.delete('endpoints/%s' % endpoint_id)
+ self.expected_success(204, resp_header.status)
+ return service_client.ResponseBody(resp_header, resp_body)
diff --git a/neutron/tests/tempest/services/identity/v3/json/identity_client.py b/neutron/tests/tempest/services/identity/v3/json/identity_client.py
new file mode 100644
index 0000000..f8dd4f7
--- /dev/null
+++ b/neutron/tests/tempest/services/identity/v3/json/identity_client.py
@@ -0,0 +1,523 @@
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 json
+import urllib
+
+from neutron.tests.tempest.common import service_client
+
+
+class IdentityV3ClientJSON(service_client.ServiceClient):
+ api_version = "v3"
+
+ def create_user(self, user_name, password=None, project_id=None,
+ email=None, domain_id='default', **kwargs):
+ """Creates a user."""
+ en = kwargs.get('enabled', True)
+ description = kwargs.get('description', None)
+ default_project_id = kwargs.get('default_project_id')
+ post_body = {
+ 'project_id': project_id,
+ 'default_project_id': default_project_id,
+ 'description': description,
+ 'domain_id': domain_id,
+ 'email': email,
+ 'enabled': en,
+ 'name': user_name,
+ 'password': password
+ }
+ post_body = json.dumps({'user': post_body})
+ resp, body = self.post('users', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['user'])
+
+ def update_user(self, user_id, name, **kwargs):
+ """Updates a user."""
+ body = self.get_user(user_id)
+ email = kwargs.get('email', body['email'])
+ en = kwargs.get('enabled', body['enabled'])
+ project_id = kwargs.get('project_id', body['project_id'])
+ if 'default_project_id' in body.keys():
+ default_project_id = kwargs.get('default_project_id',
+ body['default_project_id'])
+ else:
+ default_project_id = kwargs.get('default_project_id')
+ description = kwargs.get('description', body['description'])
+ domain_id = kwargs.get('domain_id', body['domain_id'])
+ post_body = {
+ 'name': name,
+ 'email': email,
+ 'enabled': en,
+ 'project_id': project_id,
+ 'default_project_id': default_project_id,
+ 'id': user_id,
+ 'domain_id': domain_id,
+ 'description': description
+ }
+ post_body = json.dumps({'user': post_body})
+ resp, body = self.patch('users/%s' % user_id, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['user'])
+
+ def update_user_password(self, user_id, password, original_password):
+ """Updates a user password."""
+ update_user = {
+ 'password': password,
+ 'original_password': original_password
+ }
+ update_user = json.dumps({'user': update_user})
+ resp, _ = self.post('users/%s/password' % user_id, update_user)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp)
+
+ def list_user_projects(self, user_id):
+ """Lists the projects on which a user has roles assigned."""
+ resp, body = self.get('users/%s/projects' % user_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['projects'])
+
+ def get_users(self, params=None):
+ """Get the list of users."""
+ url = 'users'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['users'])
+
+ def get_user(self, user_id):
+ """GET a user."""
+ resp, body = self.get("users/%s" % user_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['user'])
+
+ def delete_user(self, user_id):
+ """Deletes a User."""
+ resp, body = self.delete("users/%s" % user_id)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def create_project(self, name, **kwargs):
+ """Creates a project."""
+ description = kwargs.get('description', None)
+ en = kwargs.get('enabled', True)
+ domain_id = kwargs.get('domain_id', 'default')
+ post_body = {
+ 'description': description,
+ 'domain_id': domain_id,
+ 'enabled': en,
+ 'name': name
+ }
+ post_body = json.dumps({'project': post_body})
+ resp, body = self.post('projects', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['project'])
+
+ def list_projects(self, params=None):
+ url = "projects"
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['projects'])
+
+ def update_project(self, project_id, **kwargs):
+ body = self.get_project(project_id)
+ name = kwargs.get('name', body['name'])
+ desc = kwargs.get('description', body['description'])
+ en = kwargs.get('enabled', body['enabled'])
+ domain_id = kwargs.get('domain_id', body['domain_id'])
+ post_body = {
+ 'id': project_id,
+ 'name': name,
+ 'description': desc,
+ 'enabled': en,
+ 'domain_id': domain_id,
+ }
+ post_body = json.dumps({'project': post_body})
+ resp, body = self.patch('projects/%s' % project_id, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['project'])
+
+ def get_project(self, project_id):
+ """GET a Project."""
+ resp, body = self.get("projects/%s" % project_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['project'])
+
+ def delete_project(self, project_id):
+ """Delete a project."""
+ resp, body = self.delete('projects/%s' % str(project_id))
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def create_role(self, name):
+ """Create a Role."""
+ post_body = {
+ 'name': name
+ }
+ post_body = json.dumps({'role': post_body})
+ resp, body = self.post('roles', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['role'])
+
+ def get_role(self, role_id):
+ """GET a Role."""
+ resp, body = self.get('roles/%s' % str(role_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['role'])
+
+ def list_roles(self):
+ """Get the list of Roles."""
+ resp, body = self.get("roles")
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['roles'])
+
+ def update_role(self, name, role_id):
+ """Create a Role."""
+ post_body = {
+ 'name': name
+ }
+ post_body = json.dumps({'role': post_body})
+ resp, body = self.patch('roles/%s' % str(role_id), post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['role'])
+
+ def delete_role(self, role_id):
+ """Delete a role."""
+ resp, body = self.delete('roles/%s' % str(role_id))
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def assign_user_role(self, project_id, user_id, role_id):
+ """Add roles to a user on a project."""
+ resp, body = self.put('projects/%s/users/%s/roles/%s' %
+ (project_id, user_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def create_domain(self, name, **kwargs):
+ """Creates a domain."""
+ description = kwargs.get('description', None)
+ en = kwargs.get('enabled', True)
+ post_body = {
+ 'description': description,
+ 'enabled': en,
+ 'name': name
+ }
+ post_body = json.dumps({'domain': post_body})
+ resp, body = self.post('domains', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['domain'])
+
+ def delete_domain(self, domain_id):
+ """Delete a domain."""
+ resp, body = self.delete('domains/%s' % str(domain_id))
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def list_domains(self):
+ """List Domains."""
+ resp, body = self.get('domains')
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['domains'])
+
+ def update_domain(self, domain_id, **kwargs):
+ """Updates a domain."""
+ body = self.get_domain(domain_id)
+ description = kwargs.get('description', body['description'])
+ en = kwargs.get('enabled', body['enabled'])
+ name = kwargs.get('name', body['name'])
+ post_body = {
+ 'description': description,
+ 'enabled': en,
+ 'name': name
+ }
+ post_body = json.dumps({'domain': post_body})
+ resp, body = self.patch('domains/%s' % domain_id, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['domain'])
+
+ def get_domain(self, domain_id):
+ """Get Domain details."""
+ resp, body = self.get('domains/%s' % domain_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['domain'])
+
+ def get_token(self, resp_token):
+ """Get token details."""
+ headers = {'X-Subject-Token': resp_token}
+ resp, body = self.get("auth/tokens", headers=headers)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['token'])
+
+ def delete_token(self, resp_token):
+ """Deletes token."""
+ headers = {'X-Subject-Token': resp_token}
+ resp, body = self.delete("auth/tokens", headers=headers)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def create_group(self, name, **kwargs):
+ """Creates a group."""
+ description = kwargs.get('description', None)
+ domain_id = kwargs.get('domain_id', 'default')
+ project_id = kwargs.get('project_id', None)
+ post_body = {
+ 'description': description,
+ 'domain_id': domain_id,
+ 'project_id': project_id,
+ 'name': name
+ }
+ post_body = json.dumps({'group': post_body})
+ resp, body = self.post('groups', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['group'])
+
+ def get_group(self, group_id):
+ """Get group details."""
+ resp, body = self.get('groups/%s' % group_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['group'])
+
+ def list_groups(self):
+ """Lists the groups."""
+ resp, body = self.get('groups')
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['groups'])
+
+ def update_group(self, group_id, **kwargs):
+ """Updates a group."""
+ body = self.get_group(group_id)
+ name = kwargs.get('name', body['name'])
+ description = kwargs.get('description', body['description'])
+ post_body = {
+ 'name': name,
+ 'description': description
+ }
+ post_body = json.dumps({'group': post_body})
+ resp, body = self.patch('groups/%s' % group_id, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['group'])
+
+ def delete_group(self, group_id):
+ """Delete a group."""
+ resp, body = self.delete('groups/%s' % str(group_id))
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def add_group_user(self, group_id, user_id):
+ """Add user into group."""
+ resp, body = self.put('groups/%s/users/%s' % (group_id, user_id),
+ None)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def list_group_users(self, group_id):
+ """List users in group."""
+ resp, body = self.get('groups/%s/users' % group_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['users'])
+
+ def list_user_groups(self, user_id):
+ """Lists groups which a user belongs to."""
+ resp, body = self.get('users/%s/groups' % user_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['groups'])
+
+ def delete_group_user(self, group_id, user_id):
+ """Delete user in group."""
+ resp, body = self.delete('groups/%s/users/%s' % (group_id, user_id))
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def assign_user_role_on_project(self, project_id, user_id, role_id):
+ """Add roles to a user on a project."""
+ resp, body = self.put('projects/%s/users/%s/roles/%s' %
+ (project_id, user_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def assign_user_role_on_domain(self, domain_id, user_id, role_id):
+ """Add roles to a user on a domain."""
+ resp, body = self.put('domains/%s/users/%s/roles/%s' %
+ (domain_id, user_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def list_user_roles_on_project(self, project_id, user_id):
+ """list roles of a user on a project."""
+ resp, body = self.get('projects/%s/users/%s/roles' %
+ (project_id, user_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['roles'])
+
+ def list_user_roles_on_domain(self, domain_id, user_id):
+ """list roles of a user on a domain."""
+ resp, body = self.get('domains/%s/users/%s/roles' %
+ (domain_id, user_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['roles'])
+
+ def revoke_role_from_user_on_project(self, project_id, user_id, role_id):
+ """Delete role of a user on a project."""
+ resp, body = self.delete('projects/%s/users/%s/roles/%s' %
+ (project_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def revoke_role_from_user_on_domain(self, domain_id, user_id, role_id):
+ """Delete role of a user on a domain."""
+ resp, body = self.delete('domains/%s/users/%s/roles/%s' %
+ (domain_id, user_id, role_id))
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def assign_group_role_on_project(self, project_id, group_id, role_id):
+ """Add roles to a user on a project."""
+ resp, body = self.put('projects/%s/groups/%s/roles/%s' %
+ (project_id, group_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def assign_group_role_on_domain(self, domain_id, group_id, role_id):
+ """Add roles to a user on a domain."""
+ resp, body = self.put('domains/%s/groups/%s/roles/%s' %
+ (domain_id, group_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def list_group_roles_on_project(self, project_id, group_id):
+ """list roles of a user on a project."""
+ resp, body = self.get('projects/%s/groups/%s/roles' %
+ (project_id, group_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['roles'])
+
+ def list_group_roles_on_domain(self, domain_id, group_id):
+ """list roles of a user on a domain."""
+ resp, body = self.get('domains/%s/groups/%s/roles' %
+ (domain_id, group_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['roles'])
+
+ def revoke_role_from_group_on_project(self, project_id, group_id, role_id):
+ """Delete role of a user on a project."""
+ resp, body = self.delete('projects/%s/groups/%s/roles/%s' %
+ (project_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def revoke_role_from_group_on_domain(self, domain_id, group_id, role_id):
+ """Delete role of a user on a domain."""
+ resp, body = self.delete('domains/%s/groups/%s/roles/%s' %
+ (domain_id, group_id, role_id))
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def create_trust(self, trustor_user_id, trustee_user_id, project_id,
+ role_names, impersonation, expires_at):
+ """Creates a trust."""
+ roles = [{'name': n} for n in role_names]
+ post_body = {
+ 'trustor_user_id': trustor_user_id,
+ 'trustee_user_id': trustee_user_id,
+ 'project_id': project_id,
+ 'impersonation': impersonation,
+ 'roles': roles,
+ 'expires_at': expires_at
+ }
+ post_body = json.dumps({'trust': post_body})
+ resp, body = self.post('OS-TRUST/trusts', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['trust'])
+
+ def delete_trust(self, trust_id):
+ """Deletes a trust."""
+ resp, body = self.delete("OS-TRUST/trusts/%s" % trust_id)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def get_trusts(self, trustor_user_id=None, trustee_user_id=None):
+ """GET trusts."""
+ if trustor_user_id:
+ resp, body = self.get("OS-TRUST/trusts?trustor_user_id=%s"
+ % trustor_user_id)
+ elif trustee_user_id:
+ resp, body = self.get("OS-TRUST/trusts?trustee_user_id=%s"
+ % trustee_user_id)
+ else:
+ resp, body = self.get("OS-TRUST/trusts")
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['trusts'])
+
+ def get_trust(self, trust_id):
+ """GET trust."""
+ resp, body = self.get("OS-TRUST/trusts/%s" % trust_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['trust'])
+
+ def get_trust_roles(self, trust_id):
+ """GET roles delegated by a trust."""
+ resp, body = self.get("OS-TRUST/trusts/%s/roles" % trust_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['roles'])
+
+ def get_trust_role(self, trust_id, role_id):
+ """GET role delegated by a trust."""
+ resp, body = self.get("OS-TRUST/trusts/%s/roles/%s"
+ % (trust_id, role_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['role'])
+
+ def check_trust_role(self, trust_id, role_id):
+ """HEAD Check if role is delegated by a trust."""
+ resp, body = self.head("OS-TRUST/trusts/%s/roles/%s"
+ % (trust_id, role_id))
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, body)
diff --git a/neutron/tests/tempest/services/identity/v3/json/policy_client.py b/neutron/tests/tempest/services/identity/v3/json/policy_client.py
new file mode 100644
index 0000000..2e44185
--- /dev/null
+++ b/neutron/tests/tempest/services/identity/v3/json/policy_client.py
@@ -0,0 +1,69 @@
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 json
+
+from neutron.tests.tempest.common import service_client
+
+
+class PolicyClientJSON(service_client.ServiceClient):
+ api_version = "v3"
+
+ def create_policy(self, blob, type):
+ """Creates a Policy."""
+ post_body = {
+ "blob": blob,
+ "type": type
+ }
+ post_body = json.dumps({'policy': post_body})
+ resp, body = self.post('policies', post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['policy'])
+
+ def list_policies(self):
+ """Lists the policies."""
+ resp, body = self.get('policies')
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['policies'])
+
+ def get_policy(self, policy_id):
+ """Lists out the given policy."""
+ url = 'policies/%s' % policy_id
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['policy'])
+
+ def update_policy(self, policy_id, **kwargs):
+ """Updates a policy."""
+ type = kwargs.get('type')
+ post_body = {
+ 'type': type
+ }
+ post_body = json.dumps({'policy': post_body})
+ url = 'policies/%s' % policy_id
+ resp, body = self.patch(url, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['policy'])
+
+ def delete_policy(self, policy_id):
+ """Deletes the policy."""
+ url = "policies/%s" % policy_id
+ resp, body = self.delete(url)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
diff --git a/neutron/tests/tempest/services/identity/v3/json/region_client.py b/neutron/tests/tempest/services/identity/v3/json/region_client.py
new file mode 100644
index 0000000..e173aa5
--- /dev/null
+++ b/neutron/tests/tempest/services/identity/v3/json/region_client.py
@@ -0,0 +1,77 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P
+# All Rights Reserved.
+#
+# 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 json
+import urllib
+
+from neutron.tests.tempest.common import service_client
+
+
+class RegionClientJSON(service_client.ServiceClient):
+ api_version = "v3"
+
+ def create_region(self, description, **kwargs):
+ """Create region."""
+ req_body = {
+ 'description': description,
+ }
+ if kwargs.get('parent_region_id'):
+ req_body['parent_region_id'] = kwargs.get('parent_region_id')
+ req_body = json.dumps({'region': req_body})
+ if kwargs.get('unique_region_id'):
+ resp, body = self.put(
+ 'regions/%s' % kwargs.get('unique_region_id'), req_body)
+ else:
+ resp, body = self.post('regions', req_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['region'])
+
+ def update_region(self, region_id, **kwargs):
+ """Updates a region."""
+ post_body = {}
+ if 'description' in kwargs:
+ post_body['description'] = kwargs.get('description')
+ if 'parent_region_id' in kwargs:
+ post_body['parent_region_id'] = kwargs.get('parent_region_id')
+ post_body = json.dumps({'region': post_body})
+ resp, body = self.patch('regions/%s' % region_id, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['region'])
+
+ def get_region(self, region_id):
+ """Get region."""
+ url = 'regions/%s' % region_id
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['region'])
+
+ def list_regions(self, params=None):
+ """List regions."""
+ url = 'regions'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['regions'])
+
+ def delete_region(self, region_id):
+ """Delete region."""
+ resp, body = self.delete('regions/%s' % region_id)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
diff --git a/neutron/tests/tempest/services/identity/v3/json/service_client.py b/neutron/tests/tempest/services/identity/v3/json/service_client.py
new file mode 100644
index 0000000..529693e
--- /dev/null
+++ b/neutron/tests/tempest/services/identity/v3/json/service_client.py
@@ -0,0 +1,73 @@
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 json
+
+from neutron.tests.tempest.common import service_client
+
+
+class ServiceClientJSON(service_client.ServiceClient):
+ api_version = "v3"
+
+ def update_service(self, service_id, **kwargs):
+ """Updates a service."""
+ body = self.get_service(service_id)
+ name = kwargs.get('name', body['name'])
+ type = kwargs.get('type', body['type'])
+ desc = kwargs.get('description', body['description'])
+ patch_body = {
+ 'description': desc,
+ 'type': type,
+ 'name': name
+ }
+ patch_body = json.dumps({'service': patch_body})
+ resp, body = self.patch('services/%s' % service_id, patch_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['service'])
+
+ def get_service(self, service_id):
+ """Get Service."""
+ url = 'services/%s' % service_id
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['service'])
+
+ def create_service(self, serv_type, name=None, description=None,
+ enabled=True):
+ body_dict = {
+ 'name': name,
+ 'type': serv_type,
+ 'enabled': enabled,
+ 'description': description,
+ }
+ body = json.dumps({'service': body_dict})
+ resp, body = self.post("services", body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body["service"])
+
+ def delete_service(self, serv_id):
+ url = "services/" + serv_id
+ resp, body = self.delete(url)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def list_services(self):
+ resp, body = self.get('services')
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBodyList(resp, body['services'])
diff --git a/neutron/tests/tempest/services/identity/v3/json/token_client.py b/neutron/tests/tempest/services/identity/v3/json/token_client.py
new file mode 100644
index 0000000..61cbf60
--- /dev/null
+++ b/neutron/tests/tempest/services/identity/v3/json/token_client.py
@@ -0,0 +1,137 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# 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 json
+from tempest_lib.common import rest_client
+from tempest_lib import exceptions as lib_exc
+
+from neutron.tests.tempest.common import service_client
+from neutron.tests.tempest import exceptions
+
+
+class V3TokenClientJSON(rest_client.RestClient):
+
+ def __init__(self, auth_url, disable_ssl_certificate_validation=None,
+ ca_certs=None, trace_requests=None):
+ dscv = disable_ssl_certificate_validation
+ super(V3TokenClientJSON, self).__init__(
+ None, None, None, disable_ssl_certificate_validation=dscv,
+ ca_certs=ca_certs, trace_requests=trace_requests)
+ if not auth_url:
+ raise exceptions.InvalidConfiguration('you must specify a v3 uri '
+ 'if using the v3 identity '
+ 'api')
+ if 'auth/tokens' not in auth_url:
+ auth_url = auth_url.rstrip('/') + '/auth/tokens'
+
+ self.auth_url = auth_url
+
+ def auth(self, user=None, password=None, project=None, user_type='id',
+ user_domain=None, project_domain=None, token=None):
+ """
+ :param user: user id or name, as specified in user_type
+ :param user_domain: the user domain
+ :param project_domain: the project domain
+ :param token: a token to re-scope.
+
+ Accepts different combinations of credentials. Restrictions:
+ - project and domain are only name (no id)
+ Sample sample valid combinations:
+ - token
+ - token, project, project_domain
+ - user_id, password
+ - username, password, user_domain
+ - username, password, project, user_domain, project_domain
+ Validation is left to the server side.
+ """
+ creds = {
+ 'auth': {
+ 'identity': {
+ 'methods': [],
+ }
+ }
+ }
+ id_obj = creds['auth']['identity']
+ if token:
+ id_obj['methods'].append('token')
+ id_obj['token'] = {
+ 'id': token
+ }
+ if user and password:
+ id_obj['methods'].append('password')
+ id_obj['password'] = {
+ 'user': {
+ 'password': password,
+ }
+ }
+ if user_type == 'id':
+ id_obj['password']['user']['id'] = user
+ else:
+ id_obj['password']['user']['name'] = user
+ if user_domain is not None:
+ _domain = dict(name=user_domain)
+ id_obj['password']['user']['domain'] = _domain
+ if project is not None:
+ _domain = dict(name=project_domain)
+ _project = dict(name=project, domain=_domain)
+ scope = dict(project=_project)
+ creds['auth']['scope'] = scope
+
+ body = json.dumps(creds)
+ resp, body = self.post(self.auth_url, body=body)
+ self.expected_success(201, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def request(self, method, url, extra_headers=False, headers=None,
+ body=None):
+ """A simple HTTP request interface."""
+ if headers is None:
+ # Always accept 'json', for xml token client too.
+ # Because XML response is not easily
+ # converted to the corresponding JSON one
+ headers = self.get_headers(accept_type="json")
+ elif extra_headers:
+ try:
+ headers.update(self.get_headers(accept_type="json"))
+ except (ValueError, TypeError):
+ headers = self.get_headers(accept_type="json")
+
+ resp, resp_body = self.raw_request(url, method,
+ headers=headers, body=body)
+ self._log_request(method, url, resp)
+
+ if resp.status in [401, 403]:
+ resp_body = json.loads(resp_body)
+ raise lib_exc.Unauthorized(resp_body['error']['message'])
+ elif resp.status not in [200, 201, 204]:
+ raise exceptions.IdentityError(
+ 'Unexpected status code {0}'.format(resp.status))
+
+ return resp, json.loads(resp_body)
+
+ def get_token(self, user, password, project=None, project_domain='Default',
+ user_domain='Default', auth_data=False):
+ """
+ :param user: username
+ Returns (token id, token data) for supplied credentials
+ """
+ body = self.auth(user, password, project, user_type='name',
+ user_domain=user_domain,
+ project_domain=project_domain)
+
+ token = body.response.get('x-subject-token')
+ if auth_data:
+ return token, body['token']
+ else:
+ return token
diff --git a/neutron/tests/tempest/services/network/__init__.py b/neutron/tests/tempest/services/network/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron/tests/tempest/services/network/__init__.py
diff --git a/neutron/tests/tempest/services/network/json/__init__.py b/neutron/tests/tempest/services/network/json/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron/tests/tempest/services/network/json/__init__.py
diff --git a/neutron/tests/tempest/services/network/json/network_client.py b/neutron/tests/tempest/services/network/json/network_client.py
new file mode 100644
index 0000000..0e37c3d
--- /dev/null
+++ b/neutron/tests/tempest/services/network/json/network_client.py
@@ -0,0 +1,581 @@
+# 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 json
+import time
+import urllib
+
+from tempest_lib import exceptions as lib_exc
+
+from neutron.tests.tempest.common import service_client
+from neutron.tests.tempest.common.utils import misc
+from neutron.tests.tempest import exceptions
+
+
+class NetworkClientJSON(service_client.ServiceClient):
+
+ """
+ Tempest REST client for Neutron. Uses v2 of the Neutron API, since the
+ V1 API has been removed from the code base.
+
+ Implements create, delete, update, list and show for the basic Neutron
+ abstractions (networks, sub-networks, routers, ports and floating IP):
+
+ Implements add/remove interface to router using subnet ID / port ID
+
+ It also implements list, show, update and reset for OpenStack Networking
+ quotas
+ """
+
+ version = '2.0'
+ uri_prefix = "v2.0"
+
+ def get_uri(self, plural_name):
+ # get service prefix from resource name
+
+ # The following list represents resource names that do not require
+ # changing underscore to a hyphen
+ hyphen_exceptions = ["health_monitors", "firewall_rules",
+ "firewall_policies"]
+ # the following map is used to construct proper URI
+ # for the given neutron resource
+ service_resource_prefix_map = {
+ 'networks': '',
+ 'subnets': '',
+ 'ports': '',
+ 'pools': 'lb',
+ 'vips': 'lb',
+ 'health_monitors': 'lb',
+ 'members': 'lb',
+ 'ipsecpolicies': 'vpn',
+ 'vpnservices': 'vpn',
+ 'ikepolicies': 'vpn',
+ 'ipsec-site-connections': 'vpn',
+ 'metering_labels': 'metering',
+ 'metering_label_rules': 'metering',
+ 'firewall_rules': 'fw',
+ 'firewall_policies': 'fw',
+ 'firewalls': 'fw'
+ }
+ service_prefix = service_resource_prefix_map.get(
+ plural_name)
+ if plural_name not in hyphen_exceptions:
+ plural_name = plural_name.replace("_", "-")
+ if service_prefix:
+ uri = '%s/%s/%s' % (self.uri_prefix, service_prefix,
+ plural_name)
+ else:
+ uri = '%s/%s' % (self.uri_prefix, plural_name)
+ return uri
+
+ def pluralize(self, resource_name):
+ # get plural from map or just add 's'
+
+ # map from resource name to a plural name
+ # needed only for those which can't be constructed as name + 's'
+ resource_plural_map = {
+ 'security_groups': 'security_groups',
+ 'security_group_rules': 'security_group_rules',
+ 'ipsecpolicy': 'ipsecpolicies',
+ 'ikepolicy': 'ikepolicies',
+ 'ipsec_site_connection': 'ipsec-site-connections',
+ 'quotas': 'quotas',
+ 'firewall_policy': 'firewall_policies'
+ }
+ return resource_plural_map.get(resource_name, resource_name + 's')
+
+ def _lister(self, plural_name):
+ def _list(**filters):
+ uri = self.get_uri(plural_name)
+ if filters:
+ uri += '?' + urllib.urlencode(filters, doseq=1)
+ resp, body = self.get(uri)
+ result = {plural_name: self.deserialize_list(body)}
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, result)
+
+ return _list
+
+ def _deleter(self, resource_name):
+ def _delete(resource_id):
+ plural = self.pluralize(resource_name)
+ uri = '%s/%s' % (self.get_uri(plural), resource_id)
+ resp, body = self.delete(uri)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ return _delete
+
+ def _shower(self, resource_name):
+ def _show(resource_id, **fields):
+ # fields is a dict which key is 'fields' and value is a
+ # list of field's name. An example:
+ # {'fields': ['id', 'name']}
+ plural = self.pluralize(resource_name)
+ uri = '%s/%s' % (self.get_uri(plural), resource_id)
+ if fields:
+ uri += '?' + urllib.urlencode(fields, doseq=1)
+ resp, body = self.get(uri)
+ body = self.deserialize_single(body)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ return _show
+
+ def _creater(self, resource_name):
+ def _create(**kwargs):
+ plural = self.pluralize(resource_name)
+ uri = self.get_uri(plural)
+ post_data = self.serialize({resource_name: kwargs})
+ resp, body = self.post(uri, post_data)
+ body = self.deserialize_single(body)
+ self.expected_success(201, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ return _create
+
+ def _updater(self, resource_name):
+ def _update(res_id, **kwargs):
+ plural = self.pluralize(resource_name)
+ uri = '%s/%s' % (self.get_uri(plural), res_id)
+ post_data = self.serialize({resource_name: kwargs})
+ resp, body = self.put(uri, post_data)
+ body = self.deserialize_single(body)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ return _update
+
+ def __getattr__(self, name):
+ method_prefixes = ["list_", "delete_", "show_", "create_", "update_"]
+ method_functors = [self._lister,
+ self._deleter,
+ self._shower,
+ self._creater,
+ self._updater]
+ for index, prefix in enumerate(method_prefixes):
+ prefix_len = len(prefix)
+ if name[:prefix_len] == prefix:
+ return method_functors[index](name[prefix_len:])
+ raise AttributeError(name)
+
+ # Common methods that are hard to automate
+ def create_bulk_network(self, names):
+ network_list = [{'name': name} for name in names]
+ post_data = {'networks': network_list}
+ body = self.serialize_list(post_data, "networks", "network")
+ uri = self.get_uri("networks")
+ resp, body = self.post(uri, body)
+ body = {'networks': self.deserialize_list(body)}
+ self.expected_success(201, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def create_bulk_subnet(self, subnet_list):
+ post_data = {'subnets': subnet_list}
+ body = self.serialize_list(post_data, 'subnets', 'subnet')
+ uri = self.get_uri('subnets')
+ resp, body = self.post(uri, body)
+ body = {'subnets': self.deserialize_list(body)}
+ self.expected_success(201, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def create_bulk_port(self, port_list):
+ post_data = {'ports': port_list}
+ body = self.serialize_list(post_data, 'ports', 'port')
+ uri = self.get_uri('ports')
+ resp, body = self.post(uri, body)
+ body = {'ports': self.deserialize_list(body)}
+ self.expected_success(201, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def wait_for_resource_deletion(self, resource_type, id):
+ """Waits for a resource to be deleted."""
+ start_time = int(time.time())
+ while True:
+ if self.is_resource_deleted(resource_type, id):
+ return
+ if int(time.time()) - start_time >= self.build_timeout:
+ raise exceptions.TimeoutException
+ time.sleep(self.build_interval)
+
+ def is_resource_deleted(self, resource_type, id):
+ method = 'show_' + resource_type
+ try:
+ getattr(self, method)(id)
+ except AttributeError:
+ raise Exception("Unknown resource type %s " % resource_type)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ def wait_for_resource_status(self, fetch, status, interval=None,
+ timeout=None):
+ """
+ @summary: Waits for a network resource to reach a status
+ @param fetch: the callable to be used to query the resource status
+ @type fecth: callable that takes no parameters and returns the resource
+ @param status: the status that the resource has to reach
+ @type status: String
+ @param interval: the number of seconds to wait between each status
+ query
+ @type interval: Integer
+ @param timeout: the maximum number of seconds to wait for the resource
+ to reach the desired status
+ @type timeout: Integer
+ """
+ if not interval:
+ interval = self.build_interval
+ if not timeout:
+ timeout = self.build_timeout
+ start_time = time.time()
+
+ while time.time() - start_time <= timeout:
+ resource = fetch()
+ if resource['status'] == status:
+ return
+ time.sleep(interval)
+
+ # At this point, the wait has timed out
+ message = 'Resource %s' % (str(resource))
+ message += ' failed to reach status %s' % status
+ message += ' (current: %s)' % resource['status']
+ message += ' within the required time %s' % timeout
+ caller = misc.find_test_caller()
+ if caller:
+ message = '(%s) %s' % (caller, message)
+ raise exceptions.TimeoutException(message)
+
+ def deserialize_single(self, body):
+ return json.loads(body)
+
+ def deserialize_list(self, body):
+ res = json.loads(body)
+ # expecting response in form
+ # {'resources': [ res1, res2] } => when pagination disabled
+ # {'resources': [..], 'resources_links': {}} => if pagination enabled
+ for k in res.keys():
+ if k.endswith("_links"):
+ continue
+ return res[k]
+
+ def serialize(self, data):
+ return json.dumps(data)
+
+ def serialize_list(self, data, root=None, item=None):
+ return self.serialize(data)
+
+ def update_quotas(self, tenant_id, **kwargs):
+ put_body = {'quota': kwargs}
+ body = json.dumps(put_body)
+ uri = '%s/quotas/%s' % (self.uri_prefix, tenant_id)
+ resp, body = self.put(uri, body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body['quota'])
+
+ def reset_quotas(self, tenant_id):
+ uri = '%s/quotas/%s' % (self.uri_prefix, tenant_id)
+ resp, body = self.delete(uri)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def create_router(self, name, admin_state_up=True, **kwargs):
+ post_body = {'router': kwargs}
+ post_body['router']['name'] = name
+ post_body['router']['admin_state_up'] = admin_state_up
+ body = json.dumps(post_body)
+ uri = '%s/routers' % (self.uri_prefix)
+ resp, body = self.post(uri, body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def _update_router(self, router_id, set_enable_snat, **kwargs):
+ uri = '%s/routers/%s' % (self.uri_prefix, router_id)
+ resp, body = self.get(uri)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ update_body = {}
+ update_body['name'] = kwargs.get('name', body['router']['name'])
+ update_body['admin_state_up'] = kwargs.get(
+ 'admin_state_up', body['router']['admin_state_up'])
+ cur_gw_info = body['router']['external_gateway_info']
+ if cur_gw_info:
+ # TODO(kevinbenton): setting the external gateway info is not
+ # allowed for a regular tenant. If the ability to update is also
+ # merged, a test case for this will need to be added similar to
+ # the SNAT case.
+ cur_gw_info.pop('external_fixed_ips', None)
+ if not set_enable_snat:
+ cur_gw_info.pop('enable_snat', None)
+ update_body['external_gateway_info'] = kwargs.get(
+ 'external_gateway_info', body['router']['external_gateway_info'])
+ if 'distributed' in kwargs:
+ update_body['distributed'] = kwargs['distributed']
+ update_body = dict(router=update_body)
+ update_body = json.dumps(update_body)
+ resp, body = self.put(uri, update_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def update_router(self, router_id, **kwargs):
+ """Update a router leaving enable_snat to its default value."""
+ # If external_gateway_info contains enable_snat the request will fail
+ # with 404 unless executed with admin client, and therefore we instruct
+ # _update_router to not set this attribute
+ # NOTE(salv-orlando): The above applies as long as Neutron's default
+ # policy is to restrict enable_snat usage to admins only.
+ return self._update_router(router_id, set_enable_snat=False, **kwargs)
+
+ def update_router_with_snat_gw_info(self, router_id, **kwargs):
+ """Update a router passing also the enable_snat attribute.
+
+ This method must be execute with admin credentials, otherwise the API
+ call will return a 404 error.
+ """
+ return self._update_router(router_id, set_enable_snat=True, **kwargs)
+
+ def add_router_interface_with_subnet_id(self, router_id, subnet_id):
+ uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix,
+ router_id)
+ update_body = {"subnet_id": subnet_id}
+ update_body = json.dumps(update_body)
+ resp, body = self.put(uri, update_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def add_router_interface_with_port_id(self, router_id, port_id):
+ uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix,
+ router_id)
+ update_body = {"port_id": port_id}
+ update_body = json.dumps(update_body)
+ resp, body = self.put(uri, update_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def remove_router_interface_with_subnet_id(self, router_id, subnet_id):
+ uri = '%s/routers/%s/remove_router_interface' % (self.uri_prefix,
+ router_id)
+ update_body = {"subnet_id": subnet_id}
+ update_body = json.dumps(update_body)
+ resp, body = self.put(uri, update_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def remove_router_interface_with_port_id(self, router_id, port_id):
+ uri = '%s/routers/%s/remove_router_interface' % (self.uri_prefix,
+ router_id)
+ update_body = {"port_id": port_id}
+ update_body = json.dumps(update_body)
+ resp, body = self.put(uri, update_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def associate_health_monitor_with_pool(self, health_monitor_id,
+ pool_id):
+ post_body = {
+ "health_monitor": {
+ "id": health_monitor_id,
+ }
+ }
+ body = json.dumps(post_body)
+ uri = '%s/lb/pools/%s/health_monitors' % (self.uri_prefix,
+ pool_id)
+ resp, body = self.post(uri, body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def disassociate_health_monitor_with_pool(self, health_monitor_id,
+ pool_id):
+ uri = '%s/lb/pools/%s/health_monitors/%s' % (self.uri_prefix, pool_id,
+ health_monitor_id)
+ resp, body = self.delete(uri)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def list_router_interfaces(self, uuid):
+ uri = '%s/ports?device_id=%s' % (self.uri_prefix, uuid)
+ resp, body = self.get(uri)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def update_agent(self, agent_id, agent_info):
+ """
+ :param agent_info: Agent update information.
+ E.g {"admin_state_up": True}
+ """
+ uri = '%s/agents/%s' % (self.uri_prefix, agent_id)
+ agent = {"agent": agent_info}
+ body = json.dumps(agent)
+ resp, body = self.put(uri, body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def list_pools_hosted_by_one_lbaas_agent(self, agent_id):
+ uri = '%s/agents/%s/loadbalancer-pools' % (self.uri_prefix, agent_id)
+ resp, body = self.get(uri)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def show_lbaas_agent_hosting_pool(self, pool_id):
+ uri = ('%s/lb/pools/%s/loadbalancer-agent' %
+ (self.uri_prefix, pool_id))
+ resp, body = self.get(uri)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def list_routers_on_l3_agent(self, agent_id):
+ uri = '%s/agents/%s/l3-routers' % (self.uri_prefix, agent_id)
+ resp, body = self.get(uri)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def list_l3_agents_hosting_router(self, router_id):
+ uri = '%s/routers/%s/l3-agents' % (self.uri_prefix, router_id)
+ resp, body = self.get(uri)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def add_router_to_l3_agent(self, agent_id, router_id):
+ uri = '%s/agents/%s/l3-routers' % (self.uri_prefix, agent_id)
+ post_body = {"router_id": router_id}
+ body = json.dumps(post_body)
+ resp, body = self.post(uri, body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def remove_router_from_l3_agent(self, agent_id, router_id):
+ uri = '%s/agents/%s/l3-routers/%s' % (
+ self.uri_prefix, agent_id, router_id)
+ resp, body = self.delete(uri)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def list_dhcp_agent_hosting_network(self, network_id):
+ uri = '%s/networks/%s/dhcp-agents' % (self.uri_prefix, network_id)
+ resp, body = self.get(uri)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def list_networks_hosted_by_one_dhcp_agent(self, agent_id):
+ uri = '%s/agents/%s/dhcp-networks' % (self.uri_prefix, agent_id)
+ resp, body = self.get(uri)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def remove_network_from_dhcp_agent(self, agent_id, network_id):
+ uri = '%s/agents/%s/dhcp-networks/%s' % (self.uri_prefix, agent_id,
+ network_id)
+ resp, body = self.delete(uri)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
+ def create_ikepolicy(self, name, **kwargs):
+ post_body = {
+ "ikepolicy": {
+ "name": name,
+ }
+ }
+ for key, val in kwargs.items():
+ post_body['ikepolicy'][key] = val
+ body = json.dumps(post_body)
+ uri = '%s/vpn/ikepolicies' % (self.uri_prefix)
+ resp, body = self.post(uri, body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def update_extra_routes(self, router_id, nexthop, destination):
+ uri = '%s/routers/%s' % (self.uri_prefix, router_id)
+ put_body = {
+ 'router': {
+ 'routes': [{'nexthop': nexthop,
+ "destination": destination}]
+ }
+ }
+ body = json.dumps(put_body)
+ resp, body = self.put(uri, body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def delete_extra_routes(self, router_id):
+ uri = '%s/routers/%s' % (self.uri_prefix, router_id)
+ null_routes = None
+ put_body = {
+ 'router': {
+ 'routes': null_routes
+ }
+ }
+ body = json.dumps(put_body)
+ resp, body = self.put(uri, body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def list_lb_pool_stats(self, pool_id):
+ uri = '%s/lb/pools/%s/stats' % (self.uri_prefix, pool_id)
+ resp, body = self.get(uri)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def add_dhcp_agent_to_network(self, agent_id, network_id):
+ post_body = {'network_id': network_id}
+ body = json.dumps(post_body)
+ uri = '%s/agents/%s/dhcp-networks' % (self.uri_prefix, agent_id)
+ resp, body = self.post(uri, body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def insert_firewall_rule_in_policy(self, firewall_policy_id,
+ firewall_rule_id, insert_after="",
+ insert_before=""):
+ uri = '%s/fw/firewall_policies/%s/insert_rule' % (self.uri_prefix,
+ firewall_policy_id)
+ body = {
+ "firewall_rule_id": firewall_rule_id,
+ "insert_after": insert_after,
+ "insert_before": insert_before
+ }
+ body = json.dumps(body)
+ resp, body = self.put(uri, body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def remove_firewall_rule_from_policy(self, firewall_policy_id,
+ firewall_rule_id):
+ uri = '%s/fw/firewall_policies/%s/remove_rule' % (self.uri_prefix,
+ firewall_policy_id)
+ update_body = {"firewall_rule_id": firewall_rule_id}
+ update_body = json.dumps(update_body)
+ resp, body = self.put(uri, update_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
diff --git a/neutron/tests/tempest/services/network/resources.py b/neutron/tests/tempest/services/network/resources.py
new file mode 100644
index 0000000..4d45515
--- /dev/null
+++ b/neutron/tests/tempest/services/network/resources.py
@@ -0,0 +1,189 @@
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# 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 abc
+
+import six
+
+
+class AttributeDict(dict):
+
+ """
+ Provide attribute access (dict.key) to dictionary values.
+ """
+
+ def __getattr__(self, name):
+ """Allow attribute access for all keys in the dict."""
+ if name in self:
+ return self[name]
+ return super(AttributeDict, self).__getattribute__(name)
+
+
+@six.add_metaclass(abc.ABCMeta)
+class DeletableResource(AttributeDict):
+
+ """
+ Support deletion of neutron resources (networks, subnets) via a
+ delete() method, as is supported by keystone and nova resources.
+ """
+
+ def __init__(self, *args, **kwargs):
+ self.client = kwargs.pop('client', None)
+ super(DeletableResource, self).__init__(*args, **kwargs)
+
+ def __str__(self):
+ return '<%s id="%s" name="%s">' % (self.__class__.__name__,
+ self.id, self.name)
+
+ @abc.abstractmethod
+ def delete(self):
+ return
+
+ @abc.abstractmethod
+ def refresh(self):
+ return
+
+ def __hash__(self):
+ return hash(self.id)
+
+ def wait_for_status(self, status):
+ if not hasattr(self, 'status'):
+ return
+
+ def helper_get():
+ self.refresh()
+ return self
+
+ return self.client.wait_for_resource_status(helper_get, status)
+
+
+class DeletableNetwork(DeletableResource):
+
+ def delete(self):
+ self.client.delete_network(self.id)
+
+
+class DeletableSubnet(DeletableResource):
+
+ def __init__(self, *args, **kwargs):
+ super(DeletableSubnet, self).__init__(*args, **kwargs)
+ self._router_ids = set()
+
+ def update(self, *args, **kwargs):
+ result = self.client.update_subnet(self.id,
+ *args,
+ **kwargs)
+ return super(DeletableSubnet, self).update(**result['subnet'])
+
+ def add_to_router(self, router_id):
+ self._router_ids.add(router_id)
+ self.client.add_router_interface_with_subnet_id(router_id,
+ subnet_id=self.id)
+
+ def delete(self):
+ for router_id in self._router_ids.copy():
+ self.client.remove_router_interface_with_subnet_id(
+ router_id,
+ subnet_id=self.id)
+ self._router_ids.remove(router_id)
+ self.client.delete_subnet(self.id)
+
+
+class DeletableRouter(DeletableResource):
+
+ def set_gateway(self, network_id):
+ return self.update(external_gateway_info=dict(network_id=network_id))
+
+ def unset_gateway(self):
+ return self.update(external_gateway_info=dict())
+
+ def update(self, *args, **kwargs):
+ result = self.client.update_router(self.id,
+ *args,
+ **kwargs)
+ return super(DeletableRouter, self).update(**result['router'])
+
+ def delete(self):
+ self.unset_gateway()
+ self.client.delete_router(self.id)
+
+
+class DeletableFloatingIp(DeletableResource):
+
+ def refresh(self, *args, **kwargs):
+ result = self.client.show_floatingip(self.id,
+ *args,
+ **kwargs)
+ super(DeletableFloatingIp, self).update(**result['floatingip'])
+
+ def update(self, *args, **kwargs):
+ result = self.client.update_floatingip(self.id,
+ *args,
+ **kwargs)
+ super(DeletableFloatingIp, self).update(**result['floatingip'])
+
+ def __repr__(self):
+ return '<%s addr="%s">' % (self.__class__.__name__,
+ self.floating_ip_address)
+
+ def __str__(self):
+ return '<"FloatingIP" addr="%s" id="%s">' % (self.floating_ip_address,
+ self.id)
+
+ def delete(self):
+ self.client.delete_floatingip(self.id)
+
+
+class DeletablePort(DeletableResource):
+
+ def delete(self):
+ self.client.delete_port(self.id)
+
+
+class DeletableSecurityGroup(DeletableResource):
+
+ def delete(self):
+ self.client.delete_security_group(self.id)
+
+
+class DeletableSecurityGroupRule(DeletableResource):
+
+ def __repr__(self):
+ return '<%s id="%s">' % (self.__class__.__name__, self.id)
+
+ def delete(self):
+ self.client.delete_security_group_rule(self.id)
+
+
+class DeletablePool(DeletableResource):
+
+ def delete(self):
+ self.client.delete_pool(self.id)
+
+
+class DeletableMember(DeletableResource):
+
+ def delete(self):
+ self.client.delete_member(self.id)
+
+
+class DeletableVip(DeletableResource):
+
+ def delete(self):
+ self.client.delete_vip(self.id)
+
+ def refresh(self):
+ result = self.client.show_vip(self.id)
+ super(DeletableVip, self).update(**result['vip'])