Merge "Return complete response from servers_client"
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 1f53f9a..8d68986 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -75,6 +75,7 @@
cls.networks_client = cls.os.networks_client
cls.limits_client = cls.os.limits_client
cls.volumes_extensions_client = cls.os.volumes_extensions_client
+ cls.snapshots_extensions_client = cls.os.snapshots_extensions_client
cls.interfaces_client = cls.os.interfaces_client
cls.fixed_ips_client = cls.os.fixed_ips_client
cls.availability_zone_client = cls.os.availability_zone_client
diff --git a/tempest/api/compute/security_groups/test_security_group_rules.py b/tempest/api/compute/security_groups/test_security_group_rules.py
index 3c22d28..9aa59f7 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules.py
@@ -161,8 +161,8 @@
self.addCleanup(self.client.delete_security_group_rule, rule2_id)
# Get rules of the created Security Group
- rules = (self.client.list_security_group_rules(securitygroup_id)
- ['rules'])
+ rules = self.security_groups_client.show_security_group(
+ securitygroup_id)['security_group']['rules']
self.assertTrue(any([i for i in rules if i['id'] == rule1_id]))
self.assertTrue(any([i for i in rules if i['id'] == rule2_id]))
@@ -187,6 +187,7 @@
# Delete group2
self.security_groups_client.delete_security_group(sg2_id)
# Get rules of the Group1
- rules = self.client.list_security_group_rules(sg1_id)['rules']
+ rules = (self.security_groups_client.show_security_group(sg1_id)
+ ['security_group']['rules'])
# The group1 has no rules because group2 has deleted
self.assertEqual(0, len(rules))
diff --git a/tempest/api/compute/volumes/test_volume_snapshots.py b/tempest/api/compute/volumes/test_volume_snapshots.py
new file mode 100644
index 0000000..a00c0ba
--- /dev/null
+++ b/tempest/api/compute/volumes/test_volume_snapshots.py
@@ -0,0 +1,73 @@
+# Copyright 2015 Fujitsu(fnst) 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.api.compute import base
+from tempest.common.utils import data_utils
+from tempest.common import waiters
+from tempest import config
+from tempest import test
+
+
+CONF = config.CONF
+
+
+class VolumesSnapshotsTestJSON(base.BaseV2ComputeTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(VolumesSnapshotsTestJSON, cls).skip_checks()
+ if not CONF.service_available.cinder:
+ skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ @classmethod
+ def setup_clients(cls):
+ super(VolumesSnapshotsTestJSON, cls).setup_clients()
+ cls.volumes_client = cls.volumes_extensions_client
+ cls.snapshots_client = cls.snapshots_extensions_client
+
+ @test.idempotent_id('cd4ec87d-7825-450d-8040-6e2068f2da8f')
+ def test_volume_snapshot_create_get_list_delete(self):
+ v_name = data_utils.rand_name('Volume')
+ volume = self.volumes_client.create_volume(
+ size=CONF.volume.volume_size,
+ display_name=v_name)['volume']
+ self.addCleanup(self.delete_volume, volume['id'])
+ waiters.wait_for_volume_status(self.volumes_client, volume['id'],
+ 'available')
+ s_name = data_utils.rand_name('Snapshot')
+ # Create snapshot
+ snapshot = self.snapshots_client.create_snapshot(
+ volume['id'],
+ display_name=s_name)['snapshot']
+
+ def delete_snapshot(snapshot_id):
+ waiters.wait_for_snapshot_status(self.snapshots_client,
+ snapshot_id,
+ 'available')
+ # Delete snapshot
+ self.snapshots_client.delete_snapshot(snapshot_id)
+ self.snapshots_client.wait_for_resource_deletion(snapshot_id)
+
+ self.addCleanup(delete_snapshot, snapshot['id'])
+ self.assertEqual(volume['id'], snapshot['volumeId'])
+ # Get snapshot
+ fetched_snapshot = self.snapshots_client.show_snapshot(
+ snapshot['id'])['snapshot']
+ self.assertEqual(s_name, fetched_snapshot['displayName'])
+ self.assertEqual(volume['id'], fetched_snapshot['volumeId'])
+ # Fetch all snapshots
+ snapshots = self.snapshots_client.list_snapshots()['snapshots']
+ self.assertIn(snapshot['id'], map(lambda x: x['id'], snapshots))
diff --git a/tempest/api/database/flavors/test_flavors.py b/tempest/api/database/flavors/test_flavors.py
index c97ddd7..62c1e05 100644
--- a/tempest/api/database/flavors/test_flavors.py
+++ b/tempest/api/database/flavors/test_flavors.py
@@ -28,7 +28,8 @@
@test.idempotent_id('c94b825e-0132-4686-8049-8a4a2bc09525')
def test_get_db_flavor(self):
# The expected flavor details should be returned
- flavor = self.client.get_db_flavor_details(self.db_flavor_ref)
+ flavor = (self.client.get_db_flavor_details(self.db_flavor_ref)
+ ['flavor'])
self.assertEqual(self.db_flavor_ref, str(flavor['id']))
self.assertIn('ram', flavor)
self.assertIn('links', flavor)
@@ -37,9 +38,10 @@
@test.attr(type='smoke')
@test.idempotent_id('685025d6-0cec-4673-8a8d-995cb8e0d3bb')
def test_list_db_flavors(self):
- flavor = self.client.get_db_flavor_details(self.db_flavor_ref)
+ flavor = (self.client.get_db_flavor_details(self.db_flavor_ref)
+ ['flavor'])
# List of all flavors should contain the expected flavor
- flavors = self.client.list_db_flavors()
+ flavors = self.client.list_db_flavors()['flavors']
self.assertIn(flavor, flavors)
def _check_values(self, names, db_flavor, os_flavor, in_db=True):
@@ -57,7 +59,7 @@
@test.idempotent_id('afb2667f-4ec2-4925-bcb7-313fdcffb80d')
@test.services('compute')
def test_compare_db_flavors_with_os(self):
- db_flavors = self.client.list_db_flavors()
+ db_flavors = self.client.list_db_flavors()['flavors']
os_flavors = (self.os_flavors_client.list_flavors(detail=True)
['flavors'])
self.assertEqual(len(os_flavors), len(db_flavors),
@@ -65,7 +67,7 @@
(os_flavors, db_flavors))
for os_flavor in os_flavors:
db_flavor =\
- self.client.get_db_flavor_details(os_flavor['id'])
+ self.client.get_db_flavor_details(os_flavor['id'])['flavor']
self._check_values(['id', 'name', 'ram'], db_flavor, os_flavor)
self._check_values(['disk', 'vcpus', 'swap'], db_flavor, os_flavor,
in_db=False)
diff --git a/tempest/api/volume/admin/test_snapshots_actions.py b/tempest/api/volume/admin/test_snapshots_actions.py
index 784f1b6..aa6bfdf 100644
--- a/tempest/api/volume/admin/test_snapshots_actions.py
+++ b/tempest/api/volume/admin/test_snapshots_actions.py
@@ -15,10 +15,18 @@
from tempest.api.volume import base
from tempest.common.utils import data_utils
+from tempest import config
from tempest import test
+CONF = config.CONF
+
class SnapshotsActionsV2Test(base.BaseVolumeAdminTest):
+ @classmethod
+ def skip_checks(cls):
+ super(SnapshotsActionsV2Test, cls).skip_checks()
+ if not CONF.volume_feature_enabled.snapshot:
+ raise cls.skipException("Cinder snapshot feature disabled")
@classmethod
def setup_clients(cls):
diff --git a/tempest/api/volume/test_snapshot_metadata.py b/tempest/api/volume/test_snapshot_metadata.py
index ce6ba90..e50ca95 100644
--- a/tempest/api/volume/test_snapshot_metadata.py
+++ b/tempest/api/volume/test_snapshot_metadata.py
@@ -16,10 +16,18 @@
from testtools import matchers
from tempest.api.volume import base
+from tempest import config
from tempest import test
+CONF = config.CONF
+
class SnapshotV2MetadataTestJSON(base.BaseVolumeTest):
+ @classmethod
+ def skip_checks(cls):
+ super(SnapshotV2MetadataTestJSON, cls).skip_checks()
+ if not CONF.volume_feature_enabled.snapshot:
+ raise cls.skipException("Cinder snapshot feature disabled")
@classmethod
def setup_clients(cls):
diff --git a/tempest/api_schema/response/compute/v2_1/snapshots.py b/tempest/api_schema/response/compute/v2_1/snapshots.py
new file mode 100644
index 0000000..01a524b
--- /dev/null
+++ b/tempest/api_schema/response/compute/v2_1/snapshots.py
@@ -0,0 +1,61 @@
+# Copyright 2015 Fujitsu(fnst) 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.
+
+common_snapshot_info = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'volumeId': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'size': {'type': 'integer'},
+ 'createdAt': {'type': 'string'},
+ 'displayName': {'type': ['string', 'null']},
+ 'displayDescription': {'type': ['string', 'null']}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'volumeId', 'status', 'size',
+ 'createdAt', 'displayName', 'displayDescription']
+}
+
+create_get_snapshot = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'snapshot': common_snapshot_info
+ },
+ 'additionalProperties': False,
+ 'required': ['snapshot']
+ }
+}
+
+list_snapshots = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'snapshots': {
+ 'type': 'array',
+ 'items': common_snapshot_info
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['snapshots']
+ }
+}
+
+delete_snapshot = {
+ 'status_code': [202]
+}
diff --git a/tempest/clients.py b/tempest/clients.py
index c0d4585..28efd9d 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -16,8 +16,8 @@
import copy
from oslo_log import log as logging
-from tempest_lib.services.identity.v2.token_client import TokenClientJSON
-from tempest_lib.services.identity.v3.token_client import V3TokenClientJSON
+from tempest_lib.services.identity.v2.token_client import TokenClient
+from tempest_lib.services.identity.v3.token_client import V3TokenClient
from tempest.common import cred_provider
from tempest.common import negative_rest_client
@@ -73,6 +73,8 @@
ServerGroupsClient
from tempest.services.compute.json.servers_client import ServersClient
from tempest.services.compute.json.services_client import ServicesClient
+from tempest.services.compute.json.snapshots_extensions_client import \
+ SnapshotsExtensionsClient
from tempest.services.compute.json.tenant_networks_client import \
TenantNetworksClient
from tempest.services.compute.json.tenant_usages_client import \
@@ -325,6 +327,8 @@
self.auth_provider, **params_volume)
self.compute_versions_client = VersionsClient(self.auth_provider,
**params_volume)
+ self.snapshots_extensions_client = SnapshotsExtensionsClient(
+ self.auth_provider, **params_volume)
def _set_database_clients(self):
self.database_flavors_client = DatabaseFlavorsClient(
@@ -377,14 +381,14 @@
# API version is marked as enabled
if CONF.identity_feature_enabled.api_v2:
if CONF.identity.uri:
- self.token_client = TokenClientJSON(
+ self.token_client = TokenClient(
CONF.identity.uri, **self.default_params)
else:
msg = 'Identity v2 API enabled, but no identity.uri set'
raise exceptions.InvalidConfiguration(msg)
if CONF.identity_feature_enabled.api_v3:
if CONF.identity.uri_v3:
- self.token_v3_client = V3TokenClientJSON(
+ self.token_v3_client = V3TokenClient(
CONF.identity.uri_v3, **self.default_params)
else:
msg = 'Identity v3 API enabled, but no identity.uri_v3 set'
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 4dae3de..867d3f6 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -183,6 +183,27 @@
raise exceptions.TimeoutException(message)
+def wait_for_snapshot_status(client, snapshot_id, status):
+ """Waits for a Snapshot to reach a given status."""
+ body = client.show_snapshot(snapshot_id)['snapshot']
+ snapshot_status = body['status']
+ start = int(time.time())
+
+ while snapshot_status != status:
+ time.sleep(client.build_interval)
+ body = client.show_snapshot(snapshot_id)['snapshot']
+ snapshot_status = body['status']
+ if snapshot_status == 'error':
+ raise exceptions.SnapshotBuildErrorException(
+ snapshot_id=snapshot_id)
+ if int(time.time()) - start >= client.build_timeout:
+ message = ('Snapshot %s failed to reach %s status (current %s) '
+ 'within the required time (%s s).' %
+ (snapshot_id, status, snapshot_status,
+ client.build_timeout))
+ raise exceptions.TimeoutException(message)
+
+
def wait_for_bm_node_status(client, node_id, attr, status):
"""Waits for a baremetal node attribute to reach given status.
diff --git a/tempest/config.py b/tempest/config.py
index 295b74d..ccfcd9c 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -1143,7 +1143,7 @@
title='Baremetal provisioning service options',
help='When enabling baremetal tests, Nova '
'must be configured to use the Ironic '
- 'driver. The following paremeters for the '
+ 'driver. The following parameters for the '
'[compute] section must be disabled: '
'console_output, interface_attach, '
'live_migration, pause, rescue, resize '
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index 15482ab..b3d60f6 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -64,7 +64,7 @@
class InvalidIdentityVersion(TempestException):
- message = "Invalid version %(identity_version) of the identity service"
+ message = "Invalid version %(identity_version)s of the identity service"
class TimeoutException(TempestException):
diff --git a/tempest/services/compute/json/security_group_rules_client.py b/tempest/services/compute/json/security_group_rules_client.py
index c1c6b1a..9626f60 100644
--- a/tempest/services/compute/json/security_group_rules_client.py
+++ b/tempest/services/compute/json/security_group_rules_client.py
@@ -14,7 +14,6 @@
# under the License.
from oslo_serialization import jsonutils as json
-from tempest_lib import exceptions as lib_exc
from tempest.api_schema.response.compute.v2_1 import security_groups as schema
from tempest.common import service_client
@@ -46,13 +45,3 @@
group_rule_id)
self.validate_response(schema.delete_security_group_rule, resp, body)
return service_client.ResponseBody(resp, body)
-
- def list_security_group_rules(self, security_group_id):
- """List all rules for a security group."""
- resp, body = self.get('os-security-groups')
- body = json.loads(body)
- self.validate_response(schema.list_security_groups, resp, body)
- for sg in body['security_groups']:
- if sg['id'] == security_group_id:
- return service_client.ResponseBody(resp, sg)
- raise lib_exc.NotFound('No such Security Group')
diff --git a/tempest/services/compute/json/snapshots_extensions_client.py b/tempest/services/compute/json/snapshots_extensions_client.py
new file mode 100644
index 0000000..6902a39
--- /dev/null
+++ b/tempest/services/compute/json/snapshots_extensions_client.py
@@ -0,0 +1,71 @@
+# Copyright 2015 Fujitsu(fnst) 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 oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+from tempest_lib import exceptions as lib_exc
+
+from tempest.api_schema.response.compute.v2_1 import snapshots as schema
+from tempest.common import service_client
+
+
+class SnapshotsExtensionsClient(service_client.ServiceClient):
+
+ def create_snapshot(self, volume_id, **kwargs):
+ post_body = {
+ 'volume_id': volume_id
+ }
+ post_body.update(kwargs)
+ post_body = json.dumps({'snapshot': post_body})
+ resp, body = self.post('os-snapshots', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_snapshot, resp, body)
+ return service_client.ResponseBody(resp, body)
+
+ def show_snapshot(self, snapshot_id):
+ url = "os-snapshots/%s" % snapshot_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.create_get_snapshot, resp, body)
+ return service_client.ResponseBody(resp, body)
+
+ def list_snapshots(self, detail=False, params=None):
+ url = 'os-snapshots'
+
+ if detail:
+ url += '/detail'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(schema.list_snapshots, resp, body)
+ return service_client.ResponseBody(resp, body)
+
+ def delete_snapshot(self, snapshot_id):
+ resp, body = self.delete("os-snapshots/%s" % snapshot_id)
+ self.validate_response(schema.delete_snapshot, resp, body)
+ return service_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_snapshot(id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'snapshot'
diff --git a/tempest/services/database/json/flavors_client.py b/tempest/services/database/json/flavors_client.py
index 4fe5a46..88feb17 100644
--- a/tempest/services/database/json/flavors_client.py
+++ b/tempest/services/database/json/flavors_client.py
@@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+from oslo_serialization import jsonutils as json
import urllib
from tempest.common import service_client
@@ -27,9 +28,11 @@
resp, body = self.get(url)
self.expected_success(200, resp.status)
- return service_client.ResponseBodyList(resp, self._parse_resp(body))
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
def get_db_flavor_details(self, db_flavor_id):
resp, body = self.get("flavors/%s" % str(db_flavor_id))
self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, self._parse_resp(body))
+ body = json.loads(body)
+ return service_client.ResponseBody(resp, body)
diff --git a/tempest/tests/services/compute/test_agents_client.py b/tempest/tests/services/compute/test_agents_client.py
index 9493a32..01035ab 100644
--- a/tempest/tests/services/compute/test_agents_client.py
+++ b/tempest/tests/services/compute/test_agents_client.py
@@ -56,6 +56,12 @@
'tempest.common.service_client.ServiceClient.get',
{"agents": []},
bytes_body)
+ self.check_service_client_function(
+ self.client.list_agents,
+ 'tempest.common.service_client.ServiceClient.get',
+ {"agents": []},
+ bytes_body,
+ hypervisor="kvm")
def _test_create_agent(self, bytes_body=False):
self.check_service_client_function(
diff --git a/tempest/tests/services/compute/test_server_groups_client.py b/tempest/tests/services/compute/test_server_groups_client.py
new file mode 100644
index 0000000..253dac1
--- /dev/null
+++ b/tempest/tests/services/compute/test_server_groups_client.py
@@ -0,0 +1,84 @@
+# Copyright 2015 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import httplib2
+
+from oslotest import mockpatch
+
+from tempest.services.compute.json import server_groups_client
+from tempest.tests import fake_auth_provider
+from tempest.tests.services.compute import base
+
+
+class TestServerGroupsClient(base.BaseComputeServiceTest):
+
+ server_group = {
+ "id": "5bbcc3c4-1da2-4437-a48a-66f15b1b13f9",
+ "name": "test",
+ "policies": ["anti-affinity"],
+ "members": [],
+ "metadata": {}}
+
+ def setUp(self):
+ super(TestServerGroupsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = server_groups_client.ServerGroupsClient(
+ fake_auth, 'compute', 'regionOne')
+
+ def _test_create_server_group(self, bytes_body=False):
+ expected = {"server_group": TestServerGroupsClient.server_group}
+ self.check_service_client_function(
+ self.client.create_server_group,
+ 'tempest.common.service_client.ServiceClient.post', expected,
+ bytes_body, name='fake-group', policies=['affinity'])
+
+ def test_create_server_group_str_body(self):
+ self._test_create_server_group(bytes_body=False)
+
+ def test_create_server_group_byte_body(self):
+ self._test_create_server_group(bytes_body=True)
+
+ def test_delete_server_group(self):
+ response = (httplib2.Response({'status': 204}), None)
+ self.useFixture(mockpatch.Patch(
+ 'tempest.common.service_client.ServiceClient.delete',
+ return_value=response))
+ self.client.delete_server_group('fake-group')
+
+ def _test_list_server_groups(self, bytes_body=False):
+ expected = {"server_groups": [TestServerGroupsClient.server_group]}
+ self.check_service_client_function(
+ self.client.list_server_groups,
+ 'tempest.common.service_client.ServiceClient.get',
+ expected, bytes_body)
+
+ def test_list_server_groups_str_body(self):
+ self._test_list_server_groups(bytes_body=False)
+
+ def test_list_server_groups_byte_body(self):
+ self._test_list_server_groups(bytes_body=True)
+
+ def _test_get_server_group(self, bytes_body=False):
+ expected = {"server_group": TestServerGroupsClient.server_group}
+ self.check_service_client_function(
+ self.client.get_server_group,
+ 'tempest.common.service_client.ServiceClient.get',
+ expected, bytes_body,
+ server_group_id='5bbcc3c4-1da2-4437-a48a-66f15b1b13f9')
+
+ def test_get_server_group_str_body(self):
+ self._test_get_server_group(bytes_body=False)
+
+ def test_get_server_group_byte_body(self):
+ self._test_get_server_group(bytes_body=True)