Merge "Add available params in identity client's comment"
diff --git a/releasenotes/notes/identity-clients-as-library-e663c6132fcac6c2.yaml b/releasenotes/notes/identity-clients-as-library-e663c6132fcac6c2.yaml
new file mode 100644
index 0000000..b7850d0
--- /dev/null
+++ b/releasenotes/notes/identity-clients-as-library-e663c6132fcac6c2.yaml
@@ -0,0 +1,9 @@
+---
+features:
+ - |
+ Define identity service clients as libraries
+ The following identity service clients are defined as library interface,
+ so the other projects can use these modules as stable libraries without
+ any maintenance changes.
+
+ * endpoints_client(v2)
diff --git a/tempest/api/identity/admin/v2/test_endpoints.py b/tempest/api/identity/admin/v2/test_endpoints.py
index df75d0a..4493c8e 100644
--- a/tempest/api/identity/admin/v2/test_endpoints.py
+++ b/tempest/api/identity/admin/v2/test_endpoints.py
@@ -37,8 +37,8 @@
region = data_utils.rand_name('region')
url = data_utils.rand_url()
endpoint = cls.endpoints_client.create_endpoint(
- cls.service_id,
- region,
+ service_id=cls.service_id,
+ region=region,
publicurl=url,
adminurl=url,
internalurl=url)['endpoint']
@@ -70,8 +70,8 @@
region = data_utils.rand_name('region')
url = data_utils.rand_url()
endpoint = self.endpoints_client.create_endpoint(
- self.service_id,
- region,
+ service_id=self.service_id,
+ region=region,
publicurl=url,
adminurl=url,
internalurl=url)['endpoint']
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index 27ff15d..24a7a4e 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -16,6 +16,7 @@
from tempest.api.identity import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest.lib.common.utils import test_utils
from tempest import test
CONF = config.CONF
@@ -23,44 +24,78 @@
class DomainsTestJSON(base.BaseIdentityV3AdminTest):
+ @classmethod
+ def resource_setup(cls):
+ super(DomainsTestJSON, cls).resource_setup()
+ # Create some test domains to be used during tests
+ # One of those domains will be disabled
+ cls.setup_domains = list()
+ for i in range(3):
+ domain = cls.domains_client.create_domain(
+ data_utils.rand_name('domain'),
+ description=data_utils.rand_name('domain-desc'),
+ enabled=i < 2)['domain']
+ cls.setup_domains.append(domain)
+
+ @classmethod
+ def resource_cleanup(cls):
+ for domain in cls.setup_domains:
+ cls._delete_domain(domain['id'])
+ super(DomainsTestJSON, cls).resource_cleanup()
+
+ @classmethod
def _delete_domain(self, domain_id):
# It is necessary to disable the domain before deleting,
# or else it would result in unauthorized error
self.domains_client.update_domain(domain_id, enabled=False)
self.domains_client.delete_domain(domain_id)
- # Asserting that the domain is not found in the list
- # after deletion
- body = self.domains_client.list_domains()['domains']
- domains_list = [d['id'] for d in body]
- self.assertNotIn(domain_id, domains_list)
@test.idempotent_id('8cf516ef-2114-48f1-907b-d32726c734d4')
def test_list_domains(self):
# Test to list domains
- domain_ids = list()
fetched_ids = list()
- for _ in range(3):
- domain = self.domains_client.create_domain(
- data_utils.rand_name('domain'),
- description=data_utils.rand_name('domain-desc'))['domain']
- # Delete the domain at the end of this method
- self.addCleanup(self._delete_domain, domain['id'])
- domain_ids.append(domain['id'])
# List and Verify Domains
body = self.domains_client.list_domains()['domains']
for d in body:
fetched_ids.append(d['id'])
- missing_doms = [d for d in domain_ids if d not in fetched_ids]
+ missing_doms = [d for d in self.setup_domains
+ if d['id'] not in fetched_ids]
self.assertEqual(0, len(missing_doms))
+ @test.idempotent_id('c6aee07b-4981-440c-bb0b-eb598f58ffe9')
+ def test_list_domains_filter_by_name(self):
+ # List domains filtering by name
+ params = {'name': self.setup_domains[0]['name']}
+ fetched_domains = self.domains_client.list_domains(
+ params=params)['domains']
+ # Verify the filtered list is correct, domain names are unique
+ # so exactly one domain should be found with the provided name
+ self.assertEqual(1, len(fetched_domains))
+ self.assertEqual(self.setup_domains[0]['name'],
+ fetched_domains[0]['name'])
+
+ @test.idempotent_id('3fd19840-65c1-43f8-b48c-51bdd066dff9')
+ def test_list_domains_filter_by_enabled(self):
+ # List domains filtering by enabled domains
+ params = {'enabled': True}
+ fetched_domains = self.domains_client.list_domains(
+ params=params)['domains']
+ # Verify the filtered list is correct
+ self.assertIn(self.setup_domains[0], fetched_domains)
+ self.assertIn(self.setup_domains[1], fetched_domains)
+ for domain in fetched_domains:
+ self.assertEqual(True, domain['enabled'])
+
@test.attr(type='smoke')
@test.idempotent_id('f2f5b44a-82e8-4dad-8084-0661ea3b18cf')
def test_create_update_delete_domain(self):
+ # Create domain
d_name = data_utils.rand_name('domain')
d_desc = data_utils.rand_name('domain-desc')
domain = self.domains_client.create_domain(
d_name, description=d_desc)['domain']
- self.addCleanup(self._delete_domain, domain['id'])
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self._delete_domain, domain['id'])
self.assertIn('id', domain)
self.assertIn('description', domain)
self.assertIn('name', domain)
@@ -70,11 +105,12 @@
self.assertEqual(d_name, domain['name'])
self.assertEqual(d_desc, domain['description'])
self.assertEqual(True, domain['enabled'])
+ # Update domain
new_desc = data_utils.rand_name('new-desc')
new_name = data_utils.rand_name('new-name')
-
updated_domain = self.domains_client.update_domain(
- domain['id'], name=new_name, description=new_desc)['domain']
+ domain['id'], name=new_name, description=new_desc,
+ enabled=False)['domain']
self.assertIn('id', updated_domain)
self.assertIn('description', updated_domain)
self.assertIn('name', updated_domain)
@@ -83,13 +119,18 @@
self.assertIsNotNone(updated_domain['id'])
self.assertEqual(new_name, updated_domain['name'])
self.assertEqual(new_desc, updated_domain['description'])
- self.assertEqual(True, updated_domain['enabled'])
-
+ self.assertEqual(False, updated_domain['enabled'])
+ # Show domain
fetched_domain = self.domains_client.show_domain(
domain['id'])['domain']
self.assertEqual(new_name, fetched_domain['name'])
self.assertEqual(new_desc, fetched_domain['description'])
- self.assertEqual(True, fetched_domain['enabled'])
+ self.assertEqual(False, fetched_domain['enabled'])
+ # Delete domain
+ self.domains_client.delete_domain(domain['id'])
+ body = self.domains_client.list_domains()['domains']
+ domains_list = [d['id'] for d in body]
+ self.assertNotIn(domain['id'], domains_list)
@test.idempotent_id('036df86e-bb5d-42c0-a7c2-66b9db3a6046')
def test_create_domain_with_disabled_status(self):
diff --git a/tempest/api/identity/admin/v3/test_list_projects.py b/tempest/api/identity/admin/v3/test_list_projects.py
index 928437c..86f6b12 100644
--- a/tempest/api/identity/admin/v3/test_list_projects.py
+++ b/tempest/api/identity/admin/v3/test_list_projects.py
@@ -37,9 +37,15 @@
cls.p2 = cls.projects_client.create_project(p2_name)['project']
cls.data.projects.append(cls.p2)
cls.project_ids.append(cls.p2['id'])
+ # Create a new project (p3) using p2 as parent project
+ p3_name = data_utils.rand_name('project')
+ cls.p3 = cls.projects_client.create_project(
+ p3_name, parent_id=cls.p2['id'])['project']
+ cls.data.projects.append(cls.p3)
+ cls.project_ids.append(cls.p3['id'])
@test.idempotent_id('1d830662-22ad-427c-8c3e-4ec854b0af44')
- def test_projects_list(self):
+ def test_list_projects(self):
# List projects
list_projects = self.projects_client.list_projects()['projects']
@@ -63,6 +69,16 @@
# List projects with name
self._list_projects_with_params({'name': self.p1_name}, 'name')
+ @test.idempotent_id('6edc66f5-2941-4a17-9526-4073311c1fac')
+ def test_list_projects_with_parent(self):
+ # List projects with parent
+ params = {'parent_id': self.p3['parent_id']}
+ fetched_projects = self.projects_client.list_projects(
+ params)['projects']
+ self.assertNotEmpty(fetched_projects)
+ for project in fetched_projects:
+ self.assertEqual(self.p3['parent_id'], project['parent_id'])
+
def _list_projects_with_params(self, params, key):
body = self.projects_client.list_projects(params)['projects']
self.assertIn(self.p1[key], map(lambda x: x[key], body))
diff --git a/tempest/api/identity/admin/v3/test_regions.py b/tempest/api/identity/admin/v3/test_regions.py
index ece36b9..95894a6 100644
--- a/tempest/api/identity/admin/v3/test_regions.py
+++ b/tempest/api/identity/admin/v3/test_regions.py
@@ -15,7 +15,7 @@
from tempest.api.identity import base
from tempest.common.utils import data_utils
-from tempest.lib import exceptions as lib_exc
+from tempest.lib.common.utils import test_utils
from tempest import test
@@ -42,18 +42,19 @@
cls.client.delete_region(r['id'])
super(RegionsTestJSON, cls).resource_cleanup()
- def _delete_region(self, region_id):
- self.client.delete_region(region_id)
- self.assertRaises(lib_exc.NotFound,
- self.client.show_region, region_id)
-
@test.idempotent_id('56186092-82e4-43f2-b954-91013218ba42')
def test_create_update_get_delete_region(self):
+ # Create region
r_description = data_utils.rand_name('description')
region = self.client.create_region(
description=r_description,
parent_region_id=self.setup_regions[0]['id'])['region']
- self.addCleanup(self._delete_region, region['id'])
+ # This test will delete the region as part of the validation
+ # procedure, so it needs a different cleanup method that
+ # would be useful in case the tests fails at any point before
+ # reaching the deletion part.
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.client.delete_region, region['id'])
self.assertEqual(r_description, region['description'])
self.assertEqual(self.setup_regions[0]['id'],
region['parent_region_id'])
@@ -71,6 +72,11 @@
self.assertEqual(r_alt_description, region['description'])
self.assertEqual(self.setup_regions[1]['id'],
region['parent_region_id'])
+ # Delete the region
+ self.client.delete_region(region['id'])
+ body = self.client.list_regions()['regions']
+ regions_list = [r['id'] for r in body]
+ self.assertNotIn(region['id'], regions_list)
@test.attr(type='smoke')
@test.idempotent_id('2c12c5b5-efcf-4aa5-90c5-bff1ab0cdbe2')
@@ -80,7 +86,7 @@
r_description = data_utils.rand_name('description')
region = self.client.create_region(
region_id=r_region_id, description=r_description)['region']
- self.addCleanup(self._delete_region, region['id'])
+ self.addCleanup(self.client.delete_region, region['id'])
# Asserting Create Region with specific id response body
self.assertEqual(r_region_id, region['id'])
self.assertEqual(r_description, region['description'])
@@ -95,3 +101,20 @@
self.assertEqual(0, len(missing_regions),
"Failed to find region %s in fetched list" %
', '.join(str(e) for e in missing_regions))
+
+ @test.idempotent_id('2d1057cb-bbde-413a-acdf-e2d265284542')
+ def test_list_regions_filter_by_parent_region_id(self):
+ # Add a sub-region to one of the existing test regions
+ r_description = data_utils.rand_name('description')
+ region = self.client.create_region(
+ description=r_description,
+ parent_region_id=self.setup_regions[0]['id'])['region']
+ self.addCleanup(self.client.delete_region, region['id'])
+ # Get the list of regions filtering with the parent_region_id
+ params = {'parent_region_id': self.setup_regions[0]['id']}
+ fetched_regions = self.client.list_regions(params=params)['regions']
+ # Asserting list regions response
+ self.assertIn(region, fetched_regions)
+ for r in fetched_regions:
+ self.assertEqual(self.setup_regions[0]['id'],
+ r['parent_region_id'])
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index 27ccae5..cd24d17 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -13,9 +13,9 @@
# under the License.
import six
-
from tempest.api.volume import base
from tempest.common.utils import data_utils
+from tempest.common import waiters
from tempest import test
QUOTA_KEYS = ['gigabytes', 'snapshots', 'volumes']
@@ -25,10 +25,13 @@
class BaseVolumeQuotasAdminV2TestJSON(base.BaseVolumeAdminTest):
force_tenant_isolation = True
+ credentials = ['primary', 'alt', 'admin']
+
@classmethod
def setup_credentials(cls):
super(BaseVolumeQuotasAdminV2TestJSON, cls).setup_credentials()
cls.demo_tenant_id = cls.os.credentials.tenant_id
+ cls.alt_client = cls.os_alt.volumes_client
def _delete_volume(self, volume_id):
# Delete the specified volume using admin credentials
@@ -121,6 +124,54 @@
['quota_set'])
self.assertEqual(volume_default, quota_set_new['volumes'])
+ @test.idempotent_id('8911036f-9d54-4720-80cc-a1c9796a8805')
+ def test_quota_usage_after_volume_transfer(self):
+ # Create a volume for transfer
+ volume = self.create_volume()
+ self.addCleanup(self._delete_volume, volume['id'])
+
+ # List of tenants quota usage pre-transfer
+ primary_quota = self.admin_quotas_client.show_quota_usage(
+ self.demo_tenant_id)['quota_set']
+
+ alt_quota = self.admin_quotas_client.show_quota_usage(
+ self.alt_client.tenant_id)['quota_set']
+
+ # Creates a volume transfer
+ transfer = self.volumes_client.create_volume_transfer(
+ volume_id=volume['id'])['transfer']
+ transfer_id = transfer['id']
+ auth_key = transfer['auth_key']
+
+ # Accepts a volume transfer
+ self.alt_client.accept_volume_transfer(
+ transfer_id, auth_key=auth_key)['transfer']
+
+ # Verify volume transferred is available
+ waiters.wait_for_volume_status(
+ self.alt_client, volume['id'], 'available')
+
+ # List of tenants quota usage post transfer
+ new_primary_quota = self.admin_quotas_client.show_quota_usage(
+ self.demo_tenant_id)['quota_set']
+
+ new_alt_quota = self.admin_quotas_client.show_quota_usage(
+ self.alt_client.tenant_id)['quota_set']
+
+ # Verify tenants quota usage was updated
+ self.assertEqual(primary_quota['volumes']['in_use'] -
+ new_primary_quota['volumes']['in_use'],
+ new_alt_quota['volumes']['in_use'] -
+ alt_quota['volumes']['in_use'])
+
+ self.assertEqual(alt_quota['gigabytes']['in_use'] +
+ volume['size'],
+ new_alt_quota['gigabytes']['in_use'])
+
+ self.assertEqual(primary_quota['gigabytes']['in_use'] -
+ volume['size'],
+ new_primary_quota['gigabytes']['in_use'])
+
class VolumeQuotasAdminV1TestJSON(BaseVolumeQuotasAdminV2TestJSON):
_api_version = 1
diff --git a/tempest/clients.py b/tempest/clients.py
index 31883fd..ccbec4e 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -73,6 +73,7 @@
from tempest.lib.services.compute.versions_client import VersionsClient
from tempest.lib.services.compute.volumes_client import \
VolumesClient as ComputeVolumesClient
+from tempest.lib.services.identity.v2.endpoints_client import EndpointsClient
from tempest.lib.services.identity.v2.token_client import TokenClient
from tempest.lib.services.identity.v3.token_client import V3TokenClient
from tempest.lib.services.image.v2.image_members_client import \
@@ -114,7 +115,6 @@
DatabaseLimitsClient
from tempest.services.database.json.versions_client import \
DatabaseVersionsClient
-from tempest.services.identity.v2.json.endpoints_client import EndpointsClient
from tempest.services.identity.v2.json.identity_client import IdentityClient
from tempest.services.identity.v2.json.roles_client import RolesClient
from tempest.services.identity.v2.json.services_client import \
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index 0a5a41b..db323de 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -234,7 +234,7 @@
parser.add_argument('-r', '--concurrency',
default=1,
type=int,
- required=True,
+ required=False,
dest='concurrency',
help='Concurrency count')
parser.add_argument('--with-admin',
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 8e4eca1..0d31ac7 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -315,7 +315,11 @@
return self.action(server_id, 'os-start', **kwargs)
def attach_volume(self, server_id, **kwargs):
- """Attaches a volume to a server instance."""
+ """Attaches a volume to a server instance.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-compute-v2.1.html#attachVolume
+ """
post_body = json.dumps({'volumeAttachment': kwargs})
resp, body = self.post('servers/%s/os-volume_attachments' % server_id,
post_body)
diff --git a/tempest/services/identity/v2/json/endpoints_client.py b/tempest/lib/services/identity/v2/endpoints_client.py
similarity index 77%
rename from tempest/services/identity/v2/json/endpoints_client.py
rename to tempest/lib/services/identity/v2/endpoints_client.py
index ba9f867..f7b265d 100644
--- a/tempest/services/identity/v2/json/endpoints_client.py
+++ b/tempest/lib/services/identity/v2/endpoints_client.py
@@ -20,16 +20,14 @@
class EndpointsClient(rest_client.RestClient):
api_version = "v2.0"
- def create_endpoint(self, service_id, region_id, **kwargs):
- """Create an endpoint for service."""
- post_body = {
- 'service_id': service_id,
- 'region': region_id,
- 'publicurl': kwargs.get('publicurl'),
- 'adminurl': kwargs.get('adminurl'),
- 'internalurl': kwargs.get('internalurl')
- }
- post_body = json.dumps({'endpoint': post_body})
+ def create_endpoint(self, **kwargs):
+ """Create an endpoint for service.
+
+ Available params: http://developer.openstack.org/
+ api-ref-identity-v2-ext.html#createEndpoint
+ """
+
+ post_body = json.dumps({'endpoint': kwargs})
resp, body = self.post('/endpoints', post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
diff --git a/tempest/tests/lib/services/identity/v2/test_endpoints_client.py b/tempest/tests/lib/services/identity/v2/test_endpoints_client.py
new file mode 100644
index 0000000..7d2cac2
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v2/test_endpoints_client.py
@@ -0,0 +1,99 @@
+# Copyright 2016 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.
+
+from tempest.lib.services.identity.v2 import endpoints_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestEndpointsClient(base.BaseServiceTest):
+ FAKE_CREATE_ENDPOINT = {
+ "endpoint": {
+ "id": 1,
+ "tenantId": 1,
+ "region": "North",
+ "type": "compute",
+ "publicURL": "https://compute.north.public.com/v1",
+ "internalURL": "https://compute.north.internal.com/v1",
+ "adminURL": "https://compute.north.internal.com/v1"
+ }
+ }
+
+ FAKE_LIST_ENDPOINTS = {
+ "endpoints": [
+ {
+ "id": 1,
+ "tenantId": "1",
+ "region": "North",
+ "type": "compute",
+ "publicURL": "https://compute.north.public.com/v1",
+ "internalURL": "https://compute.north.internal.com/v1",
+ "adminURL": "https://compute.north.internal.com/v1"
+ },
+ {
+ "id": 2,
+ "tenantId": "1",
+ "region": "South",
+ "type": "compute",
+ "publicURL": "https://compute.north.public.com/v1",
+ "internalURL": "https://compute.north.internal.com/v1",
+ "adminURL": "https://compute.north.internal.com/v1"
+ }
+ ]
+ }
+
+ def setUp(self):
+ super(TestEndpointsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = endpoints_client.EndpointsClient(fake_auth,
+ 'identity', 'regionOne')
+
+ def _test_create_endpoint(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_endpoint,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_ENDPOINT,
+ bytes_body,
+ service_id="b344506af7644f6794d9cb316600b020",
+ region="region-demo",
+ publicurl="https://compute.north.public.com/v1",
+ adminurl="https://compute.north.internal.com/v1",
+ internalurl="https://compute.north.internal.com/v1")
+
+ def _test_list_endpoints(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_endpoints,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ENDPOINTS,
+ bytes_body)
+
+ def test_create_endpoint_with_str_body(self):
+ self._test_create_endpoint()
+
+ def test_create_endpoint_with_bytes_body(self):
+ self._test_create_endpoint(bytes_body=True)
+
+ def test_list_endpoints_with_str_body(self):
+ self._test_list_endpoints()
+
+ def test_list_endpoints_with_bytes_body(self):
+ self._test_list_endpoints(bytes_body=True)
+
+ def test_delete_endpoint(self):
+ self.check_service_client_function(
+ self.client.delete_endpoint,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ endpoint_id="b344506af7644f6794d9cb316600b020",
+ status=204)
diff --git a/tempest/tests/lib/services/identity/v2/test_token_client.py b/tempest/tests/lib/services/identity/v2/test_token_client.py
index 7925152..dfce9b3 100644
--- a/tempest/tests/lib/services/identity/v2/test_token_client.py
+++ b/tempest/tests/lib/services/identity/v2/test_token_client.py
@@ -25,9 +25,6 @@
class TestTokenClientV2(base.TestCase):
- def setUp(self):
- super(TestTokenClientV2, self).setUp()
-
def test_init_without_authurl(self):
self.assertRaises(exceptions.IdentityError,
token_client.TokenClient, None)
diff --git a/tempest/tests/services/image/__init__.py b/tempest/tests/services/image/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/tests/services/image/__init__.py
+++ /dev/null