Merge "Add network tags to test_quotas_negative"
diff --git a/etc/accounts.yaml.sample b/etc/accounts.yaml.sample
new file mode 100644
index 0000000..d191769
--- /dev/null
+++ b/etc/accounts.yaml.sample
@@ -0,0 +1,7 @@
+- username: 'user_1'
+ tenant_name: 'test_tenant_1'
+ password: 'test_password'
+
+- username: 'user_2'
+ tenant_name: 'test_tenant_2'
+ password: 'test_password'
diff --git a/etc/schemas/compute/flavors/flavor_details.json b/etc/schemas/compute/flavors/flavor_details.json
deleted file mode 100644
index c16075c..0000000
--- a/etc/schemas/compute/flavors/flavor_details.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "name": "get-flavor-details",
- "http-method": "GET",
- "url": "flavors/%s",
- "resources": [
- {"name": "flavor", "expected_result": 404}
- ]
-}
diff --git a/etc/schemas/compute/flavors/flavor_details_v3.json b/etc/schemas/compute/flavors/flavor_details_v3.json
deleted file mode 100644
index d1c1077..0000000
--- a/etc/schemas/compute/flavors/flavor_details_v3.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "get-flavor-details",
- "http-method": "GET",
- "url": "flavors/%s",
- "resources": ["flavor"]
-}
diff --git a/etc/schemas/compute/flavors/flavors_list.json b/etc/schemas/compute/flavors/flavors_list.json
deleted file mode 100644
index eb8383b..0000000
--- a/etc/schemas/compute/flavors/flavors_list.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "name": "list-flavors-with-detail",
- "http-method": "GET",
- "url": "flavors/detail",
- "json-schema": {
- "type": "object",
- "properties": {
- "minRam": {
- "type": "integer",
- "results": {
- "gen_none": 400,
- "gen_string": 400
- }
- },
- "minDisk": {
- "type": "integer",
- "results": {
- "gen_none": 400,
- "gen_string": 400
- }
- }
- }
- }
-}
diff --git a/etc/schemas/compute/flavors/flavors_list_v3.json b/etc/schemas/compute/flavors/flavors_list_v3.json
deleted file mode 100644
index d5388b3..0000000
--- a/etc/schemas/compute/flavors/flavors_list_v3.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "name": "list-flavors-with-detail",
- "http-method": "GET",
- "url": "flavors/detail",
- "json-schema": {
- "type": "object",
- "properties": {
- "min_ram": {
- "type": "integer",
- "results": {
- "gen_none": 400,
- "gen_string": 400
- }
- },
- "min_disk": {
- "type": "integer",
- "results": {
- "gen_none": 400,
- "gen_string": 400
- }
- }
- }
- }
-}
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 8a7ad9c..247f6d1 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -105,6 +105,17 @@
#syslog_log_facility=LOG_USER
+[auth]
+
+#
+# Options defined in tempest.config
+#
+
+# Path to the yaml file that contains the list of credentials
+# to use for running tests (string value)
+#test_accounts_file=etc/accounts.yaml
+
+
[baremetal]
#
@@ -386,29 +397,42 @@
# If false, skip all nova v3 tests. (boolean value)
#api_v3=false
+# If false skip all v2 api tests with xml (boolean value)
+#xml_api_v2=true
+
# If false, skip disk config tests (boolean value)
#disk_config=true
# A list of enabled compute extensions with a special entry
# all which indicates every extension is enabled. Each
-# extension should be specified with alias name (list value)
+# extension should be specified with alias name. Empty list
+# indicates all extensions are disabled (list value)
#api_extensions=all
# A list of enabled v3 extensions with a special entry all
# which indicates every extension is enabled. Each extension
-# should be specified with alias name (list value)
+# should be specified with alias name. Empty list indicates
+# all extensions are disabled (list value)
#api_v3_extensions=all
# Does the test environment support changing the admin
# password? (boolean value)
#change_password=false
+# Does the test environment support obtaining instance serial
+# console output? (boolean value)
+#console_output=true
+
# Does the test environment support resizing? (boolean value)
#resize=false
# Does the test environment support pausing? (boolean value)
#pause=true
+# Does the test environment support shelving/unshelving?
+# (boolean value)
+#shelve=true
+
# Does the test environment support suspend/resume? (boolean
# value)
#suspend=true
@@ -441,6 +465,19 @@
# (boolean value)
#rescue=true
+# Enables returning of the instance password by the relevant
+# server API calls such as create, rebuild or rescue. (boolean
+# value)
+#enable_instance_password=true
+
+# Does the test environment support dynamic network interface
+# attachment? (boolean value)
+#interface_attach=true
+
+# Does the test environment support creating snapshot images
+# of running instances? (boolean value)
+#snapshot=true
+
[dashboard]
@@ -750,7 +787,8 @@
#ipv6=true
# A list of enabled network extensions with a special entry
-# all which indicates every extension is enabled (list value)
+# all which indicates every extension is enabled. Empty list
+# indicates all extensions are disabled (list value)
#api_extensions=all
# Allow the execution of IPv6 subnet tests that use the
@@ -1105,7 +1143,8 @@
#snapshot=true
# A list of enabled volume extensions with a special entry all
-# which indicates every extension is enabled (list value)
+# which indicates every extension is enabled. Empty list
+# indicates all extensions are disabled (list value)
#api_extensions=all
# Is the v1 volume API enabled (boolean value)
diff --git a/tempest/README.rst b/tempest/README.rst
index 18c7cf3..fb25151 100644
--- a/tempest/README.rst
+++ b/tempest/README.rst
@@ -23,9 +23,8 @@
belongs in each directory, the rules and examples for good tests, are
documented in a README.rst file in the directory.
-
-api
----
+:ref:`api_field_guide`
+----------------------
API tests are validation tests for the OpenStack API. They should not
use the existing python clients for OpenStack, but should instead use
@@ -39,8 +38,8 @@
frameworks.
-cli
----
+:ref:`cli_field_guide`
+----------------------
CLI tests use the openstack CLI to interact with the OpenStack
cloud. CLI testing in unit tests is somewhat difficult because unlike
@@ -49,8 +48,8 @@
prereqs having a running OpenStack cloud.
-scenario
---------
+:ref:`scenario_field_guide`
+---------------------------
Scenario tests are complex "through path" tests for OpenStack
functionality. They are typically a series of steps where complicated
@@ -59,18 +58,26 @@
Scenario tests can and should use the OpenStack python clients.
-stress
-------
+:ref:`stress_field_guide`
+-------------------------
Stress tests are designed to stress an OpenStack environment by running a high
workload against it and seeing what breaks. The stress test framework runs
several test jobs in parallel and can run any existing test in Tempest as a
stress job.
-thirdparty
-----------
+:ref:`third_party_field_guide`
+-----------------------------
Many openstack components include 3rdparty API support. It is
completely legitimate for Tempest to include tests of 3rdparty APIs,
but those should be kept separate from the normal OpenStack
validation.
+
+:ref:`unit_tests_field_guide`
+-----------------------------
+
+Unit tests are the self checks for Tempest. They provide functional
+verification and regression checking for the internal components of tempest.
+They should be used to just verify that the individual pieces of tempest are
+working as expected.
diff --git a/tempest/api/README.rst b/tempest/api/README.rst
index 9eac19d..91e6ad6 100644
--- a/tempest/api/README.rst
+++ b/tempest/api/README.rst
@@ -1,3 +1,5 @@
+.. _api_field_guide:
+
Tempest Field Guide to API tests
================================
diff --git a/tempest/api/baremetal/README.rst b/tempest/api/baremetal/README.rst
new file mode 100644
index 0000000..759c937
--- /dev/null
+++ b/tempest/api/baremetal/README.rst
@@ -0,0 +1,25 @@
+Tempest Field Guide to Baremetal API tests
+==========================================
+
+
+What are these tests?
+---------------------
+
+These tests stress the OpenStack baremetal provisioning API provided by
+Ironic.
+
+
+Why are these tests in tempest?
+------------------------------
+
+The purpose of these tests is to exercise the various APIs provided by Ironic
+for managing baremetal nodes.
+
+
+Scope of these tests
+--------------------
+
+The baremetal API test perform basic CRUD operations on the Ironic node
+inventory. They do not actually perform hardware provisioning. It is important
+to note that all Ironic API actions are admin operations meant to be used
+either by cloud operators or other OpenStack services (i.e., Nova).
diff --git a/tempest/api_schema/compute/__init__.py b/tempest/api/baremetal/admin/__init__.py
similarity index 100%
copy from tempest/api_schema/compute/__init__.py
copy to tempest/api/baremetal/admin/__init__.py
diff --git a/tempest/api/baremetal/base.py b/tempest/api/baremetal/admin/base.py
similarity index 100%
rename from tempest/api/baremetal/base.py
rename to tempest/api/baremetal/admin/base.py
diff --git a/tempest/api/baremetal/test_api_discovery.py b/tempest/api/baremetal/admin/test_api_discovery.py
similarity index 97%
rename from tempest/api/baremetal/test_api_discovery.py
rename to tempest/api/baremetal/admin/test_api_discovery.py
index bee10b9..7368b3e 100644
--- a/tempest/api/baremetal/test_api_discovery.py
+++ b/tempest/api/baremetal/admin/test_api_discovery.py
@@ -10,7 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api.baremetal import base
+from tempest.api.baremetal.admin import base
from tempest import test
diff --git a/tempest/api/baremetal/test_chassis.py b/tempest/api/baremetal/admin/test_chassis.py
similarity index 98%
rename from tempest/api/baremetal/test_chassis.py
rename to tempest/api/baremetal/admin/test_chassis.py
index 4ab86c2..c306c34 100644
--- a/tempest/api/baremetal/test_chassis.py
+++ b/tempest/api/baremetal/admin/test_chassis.py
@@ -11,7 +11,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api.baremetal import base
+from tempest.api.baremetal.admin import base
from tempest.common.utils import data_utils
from tempest import exceptions as exc
from tempest import test
diff --git a/tempest/api/baremetal/test_drivers.py b/tempest/api/baremetal/admin/test_drivers.py
similarity index 96%
rename from tempest/api/baremetal/test_drivers.py
rename to tempest/api/baremetal/admin/test_drivers.py
index 852b6ab..649886b 100644
--- a/tempest/api/baremetal/test_drivers.py
+++ b/tempest/api/baremetal/admin/test_drivers.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api.baremetal import base
+from tempest.api.baremetal.admin import base
from tempest import config
from tempest import test
diff --git a/tempest/api/baremetal/test_nodes.py b/tempest/api/baremetal/admin/test_nodes.py
similarity index 98%
rename from tempest/api/baremetal/test_nodes.py
rename to tempest/api/baremetal/admin/test_nodes.py
index 1572840..fc67854 100644
--- a/tempest/api/baremetal/test_nodes.py
+++ b/tempest/api/baremetal/admin/test_nodes.py
@@ -12,7 +12,7 @@
import six
-from tempest.api.baremetal import base
+from tempest.api.baremetal.admin import base
from tempest import exceptions as exc
from tempest import test
diff --git a/tempest/api/baremetal/test_nodestates.py b/tempest/api/baremetal/admin/test_nodestates.py
similarity index 97%
rename from tempest/api/baremetal/test_nodestates.py
rename to tempest/api/baremetal/admin/test_nodestates.py
index 3044bc6..f24f490 100644
--- a/tempest/api/baremetal/test_nodestates.py
+++ b/tempest/api/baremetal/admin/test_nodestates.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api.baremetal import base
+from tempest.api.baremetal.admin import base
from tempest import exceptions
from tempest.openstack.common import timeutils
from tempest import test
diff --git a/tempest/api/baremetal/test_ports.py b/tempest/api/baremetal/admin/test_ports.py
similarity index 99%
rename from tempest/api/baremetal/test_ports.py
rename to tempest/api/baremetal/admin/test_ports.py
index 4ac7e29..d4adba9 100644
--- a/tempest/api/baremetal/test_ports.py
+++ b/tempest/api/baremetal/admin/test_ports.py
@@ -10,7 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api.baremetal import base
+from tempest.api.baremetal.admin import base
from tempest.common.utils import data_utils
from tempest import exceptions as exc
from tempest import test
diff --git a/tempest/api/baremetal/test_ports_negative.py b/tempest/api/baremetal/admin/test_ports_negative.py
similarity index 99%
rename from tempest/api/baremetal/test_ports_negative.py
rename to tempest/api/baremetal/admin/test_ports_negative.py
index 3e77a5f..7646677 100644
--- a/tempest/api/baremetal/test_ports_negative.py
+++ b/tempest/api/baremetal/admin/test_ports_negative.py
@@ -10,7 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api.baremetal import base
+from tempest.api.baremetal.admin import base
from tempest.common.utils import data_utils
from tempest import exceptions as exc
from tempest import test
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index c2376c9..3485943 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -145,7 +145,7 @@
self.assertEqual(200, resp.status)
self.assertIn((aggregate_id, new_aggregate_name, new_az_name),
map(lambda x:
- (x['id'], x['name'], x['availability_zone']),
+ (x['id'], x['name'], x['availability_zone']),
aggregates))
@test.attr(type='gate')
diff --git a/tempest/api/compute/admin/test_networks.py b/tempest/api/compute/admin/test_networks.py
new file mode 100644
index 0000000..032e2f5
--- /dev/null
+++ b/tempest/api/compute/admin/test_networks.py
@@ -0,0 +1,52 @@
+# 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.
+
+from tempest.api.compute import base
+from tempest import config
+
+CONF = config.CONF
+
+
+class NetworksTest(base.BaseComputeAdminTest):
+ _api_version = 2
+
+ """
+ Tests Nova Networks API that usually requires admin privileges.
+ API docs:
+ http://developer.openstack.org/api-ref-compute-v2-ext.html#ext-os-networks
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ super(NetworksTest, cls).setUpClass()
+ cls.client = cls.os_adm.networks_client
+
+ def test_get_network(self):
+ resp, networks = self.client.list_networks()
+ configured_network = [x for x in networks if x['label'] ==
+ CONF.compute.fixed_network_name]
+ self.assertEqual(1, len(configured_network),
+ "{0} networks with label {1}".format(
+ len(configured_network),
+ CONF.compute.fixed_network_name))
+ configured_network = configured_network[0]
+ _, network = self.client.get_network(configured_network['id'])
+ self.assertEqual(configured_network['label'], network['label'])
+
+ def test_list_all_networks(self):
+ _, networks = self.client.list_networks()
+ # Check the configured network is in the list
+ configured_network = CONF.compute.fixed_network_name
+ self.assertIn(configured_network, [x['label'] for x in networks])
diff --git a/tempest/api/compute/admin/test_quotas_negative.py b/tempest/api/compute/admin/test_quotas_negative.py
index aa91761..4afda03 100644
--- a/tempest/api/compute/admin/test_quotas_negative.py
+++ b/tempest/api/compute/admin/test_quotas_negative.py
@@ -112,7 +112,9 @@
security_groups=default_sg_quota)
# Check we cannot create anymore
- self.assertRaises(exceptions.OverLimit,
+ # A 403 Forbidden or 413 Overlimit (old behaviour) exception
+ # will be raised when out of quota
+ self.assertRaises((exceptions.Unauthorized, exceptions.OverLimit),
self.sg_client.create_security_group,
"sg-overlimit", "sg-desc")
@@ -149,7 +151,9 @@
ip_protocol = 'tcp'
# Check we cannot create SG rule anymore
- self.assertRaises(exceptions.OverLimit,
+ # A 403 Forbidden or 413 Overlimit (old behaviour) exception
+ # will be raised when out of quota
+ self.assertRaises((exceptions.OverLimit, exceptions.Unauthorized),
self.sg_client.create_security_group_rule,
secgroup_id, ip_protocol, 1025, 1025)
diff --git a/tempest/api/compute/admin/test_security_group_default_rules.py b/tempest/api/compute/admin/test_security_group_default_rules.py
new file mode 100644
index 0000000..07408a8
--- /dev/null
+++ b/tempest/api/compute/admin/test_security_group_default_rules.py
@@ -0,0 +1,127 @@
+# Copyright 2014 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 testtools
+
+from tempest.api.compute import base
+from tempest import config
+from tempest import exceptions
+from tempest import test
+
+CONF = config.CONF
+
+
+class SecurityGroupDefaultRulesTest(base.BaseV2ComputeAdminTest):
+
+ @classmethod
+ # TODO(GMann): Once Bug# 1311500 is fixed, these test can run
+ # for Neutron also.
+ @testtools.skipIf(CONF.service_available.neutron,
+ "Skip as this functionality is not yet "
+ "implemented in Neutron. Related Bug#1311500")
+ @test.safe_setup
+ def setUpClass(cls):
+ # A network and a subnet will be created for these tests
+ cls.set_network_resources(network=True, subnet=True)
+ super(SecurityGroupDefaultRulesTest, cls).setUpClass()
+ cls.adm_client = cls.os_adm.security_group_default_rules_client
+
+ def _create_security_group_default_rules(self, ip_protocol='tcp',
+ from_port=22, to_port=22,
+ cidr='10.10.0.0/24'):
+ # Create Security Group default rule
+ _, rule = self.adm_client.create_security_default_group_rule(
+ ip_protocol,
+ from_port,
+ to_port,
+ cidr=cidr)
+ self.assertEqual(ip_protocol, rule['ip_protocol'])
+ self.assertEqual(from_port, rule['from_port'])
+ self.assertEqual(to_port, rule['to_port'])
+ self.assertEqual(cidr, rule['ip_range']['cidr'])
+ return rule
+
+ @test.attr(type='smoke')
+ def test_create_delete_security_group_default_rules(self):
+ # Create and delete Security Group default rule
+ ip_protocols = {'tcp', 'udp', 'icmp'}
+ for ip_protocol in ip_protocols:
+ rule = self._create_security_group_default_rules(ip_protocol)
+ # Delete Security Group default rule
+ self.adm_client.delete_security_group_default_rule(rule['id'])
+ self.assertRaises(exceptions.NotFound,
+ self.adm_client.get_security_group_default_rule,
+ rule['id'])
+
+ @test.attr(type='smoke')
+ def test_create_security_group_default_rule_without_cidr(self):
+ ip_protocol = 'udp'
+ from_port = 80
+ to_port = 80
+ _, rule = self.adm_client.create_security_default_group_rule(
+ ip_protocol,
+ from_port,
+ to_port)
+ self.addCleanup(self.adm_client.delete_security_group_default_rule,
+ rule['id'])
+ self.assertNotEqual(0, rule['id'])
+ self.assertEqual('0.0.0.0/0', rule['ip_range']['cidr'])
+
+ @test.attr(type='smoke')
+ def test_create_security_group_default_rule_with_blank_cidr(self):
+ ip_protocol = 'icmp'
+ from_port = 10
+ to_port = 10
+ cidr = ''
+ _, rule = self.adm_client.create_security_default_group_rule(
+ ip_protocol,
+ from_port,
+ to_port,
+ cidr=cidr)
+ self.addCleanup(self.adm_client.delete_security_group_default_rule,
+ rule['id'])
+ self.assertNotEqual(0, rule['id'])
+ self.assertEqual('0.0.0.0/0', rule['ip_range']['cidr'])
+
+ @test.attr(type='smoke')
+ def test_security_group_default_rules_list(self):
+ ip_protocol = 'tcp'
+ from_port = 22
+ to_port = 22
+ cidr = '10.10.0.0/24'
+ rule = self._create_security_group_default_rules(ip_protocol,
+ from_port,
+ to_port,
+ cidr)
+ self.addCleanup(self.adm_client.delete_security_group_default_rule,
+ rule['id'])
+ _, rules = self.adm_client.list_security_group_default_rules()
+ self.assertNotEqual(0, len(rules))
+ self.assertIn(rule, rules)
+
+ @test.attr(type='smoke')
+ def test_default_security_group_default_rule_show(self):
+ ip_protocol = 'tcp'
+ from_port = 22
+ to_port = 22
+ cidr = '10.10.0.0/24'
+ rule = self._create_security_group_default_rules(ip_protocol,
+ from_port,
+ to_port,
+ cidr)
+ self.addCleanup(self.adm_client.delete_security_group_default_rule,
+ rule['id'])
+ _, fetched_rule = self.adm_client.get_security_group_default_rule(
+ rule['id'])
+ self.assertEqual(rule, fetched_rule)
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 70a9604..343a39a 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -37,6 +37,9 @@
def setUpClass(cls):
cls.set_network_resources()
super(BaseComputeTest, cls).setUpClass()
+ if getattr(cls, '_interface', None) == 'xml' and cls._api_version == 2:
+ if not CONF.compute_feature_enabled.xml_api_v2:
+ raise cls.skipException('XML API is not enabled')
# TODO(andreaf) WE should care also for the alt_manager here
# but only once client lazy load in the manager is done
@@ -83,6 +86,8 @@
cls.hypervisor_client = cls.os.hypervisor_client
cls.certificates_client = cls.os.certificates_client
cls.migrations_client = cls.os.migrations_client
+ cls.security_group_default_rules_client = (
+ cls.os.security_group_default_rules_client)
elif cls._api_version == 3:
if not CONF.compute_feature_enabled.api_v3:
diff --git a/tempest/api/compute/flavors/test_flavors_negative.py b/tempest/api/compute/flavors/test_flavors_negative.py
index 1638f2d..7672fc6 100644
--- a/tempest/api/compute/flavors/test_flavors_negative.py
+++ b/tempest/api/compute/flavors/test_flavors_negative.py
@@ -13,8 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-
from tempest.api.compute import base
+from tempest.api_schema.request.compute.v2 import flavors
from tempest import test
@@ -25,14 +25,14 @@
class FlavorsListWithDetailsNegativeTestJSON(base.BaseV2ComputeTest,
test.NegativeAutoTest):
_service = 'compute'
- _schema_file = 'compute/flavors/flavors_list.json'
+ _schema = flavors.flavor_list
@test.SimpleNegativeAutoTest
class FlavorDetailsNegativeTestJSON(base.BaseV2ComputeTest,
test.NegativeAutoTest):
_service = 'compute'
- _schema_file = 'compute/flavors/flavor_details.json'
+ _schema = flavors.flavors_details
@classmethod
def setUpClass(cls):
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index 29df2b0..bbb887f 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -28,6 +28,12 @@
if not CONF.service_available.glance:
skip_msg = ("%s skipped as glance is not available" % cls.__name__)
raise cls.skipException(skip_msg)
+
+ if not CONF.compute_feature_enabled.snapshot:
+ skip_msg = ("%s skipped as instance snapshotting is not supported"
+ % cls.__name__)
+ raise cls.skipException(skip_msg)
+
cls.client = cls.images_client
cls.servers_client = cls.servers_client
diff --git a/tempest/api/compute/images/test_images_negative.py b/tempest/api/compute/images/test_images_negative.py
index 6163f4d..771040b 100644
--- a/tempest/api/compute/images/test_images_negative.py
+++ b/tempest/api/compute/images/test_images_negative.py
@@ -29,6 +29,12 @@
if not CONF.service_available.glance:
skip_msg = ("%s skipped as glance is not available" % cls.__name__)
raise cls.skipException(skip_msg)
+
+ if not CONF.compute_feature_enabled.snapshot:
+ skip_msg = ("%s skipped as instance snapshotting is not supported"
+ % cls.__name__)
+ raise cls.skipException(skip_msg)
+
cls.client = cls.images_client
cls.servers_client = cls.servers_client
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index c81cec5..187c0d4 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -54,6 +54,11 @@
skip_msg = ("%s skipped as glance is not available" % cls.__name__)
raise cls.skipException(skip_msg)
+ if not CONF.compute_feature_enabled.snapshot:
+ skip_msg = ("%s skipped as instance snapshotting is not supported"
+ % cls.__name__)
+ raise cls.skipException(skip_msg)
+
try:
resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
diff --git a/tempest/api/compute/images/test_images_oneserver_negative.py b/tempest/api/compute/images/test_images_oneserver_negative.py
index 9c4ab00..4e84e08 100644
--- a/tempest/api/compute/images/test_images_oneserver_negative.py
+++ b/tempest/api/compute/images/test_images_oneserver_negative.py
@@ -62,6 +62,11 @@
skip_msg = ("%s skipped as glance is not available" % cls.__name__)
raise cls.skipException(skip_msg)
+ if not CONF.compute_feature_enabled.snapshot:
+ skip_msg = ("%s skipped as instance snapshotting is not supported"
+ % cls.__name__)
+ raise cls.skipException(skip_msg)
+
try:
resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index f9350e1..68794b1 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -16,6 +16,8 @@
import StringIO
import time
+import testtools
+
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import config
@@ -63,6 +65,9 @@
cls.image3 = _create_image()
cls.image3_id = cls.image3['id']
+ if not CONF.compute_feature_enabled.snapshot:
+ return
+
# Create instances and snapshots via nova
try:
resp, cls.server1 = cls.create_test_server()
@@ -114,6 +119,8 @@
self.assertFalse(any([i for i in images if i['id'] == self.image2_id]))
self.assertFalse(any([i for i in images if i['id'] == self.image3_id]))
+ @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
+ 'Snapshotting is not available.')
@test.attr(type='gate')
def test_list_images_filter_by_server_id(self):
# The images should contain images filtered by server id
@@ -129,6 +136,8 @@
self.assertFalse(any([i for i in images
if i['id'] == self.snapshot3_id]))
+ @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
+ 'Snapshotting is not available.')
@test.attr(type='gate')
def test_list_images_filter_by_server_ref(self):
# The list of servers should be filtered by server ref
@@ -146,6 +155,8 @@
self.assertTrue(any([i for i in images
if i['id'] == self.snapshot3_id]))
+ @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
+ 'Snapshotting is not available.')
@test.attr(type='gate')
def test_list_images_filter_by_type(self):
# The list of servers should be filtered by image type
@@ -211,6 +222,8 @@
resp, images = self.client.list_images_with_detail(params)
self.assertEqual(1, len(images))
+ @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
+ 'Snapshotting is not available.')
@test.attr(type='gate')
def test_list_images_with_detail_filter_by_server_ref(self):
# Detailed list of servers should be filtered by server ref
@@ -228,6 +241,8 @@
self.assertTrue(any([i for i in images
if i['id'] == self.snapshot3_id]))
+ @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
+ 'Snapshotting is not available.')
@test.attr(type='gate')
def test_list_images_with_detail_filter_by_type(self):
# The detailed list of servers should be filtered by image type
diff --git a/tempest/api/compute/limits/test_absolute_limits_negative.py b/tempest/api/compute/limits/test_absolute_limits_negative.py
index f88699b..b2e2981 100644
--- a/tempest/api/compute/limits/test_absolute_limits_negative.py
+++ b/tempest/api/compute/limits/test_absolute_limits_negative.py
@@ -39,7 +39,9 @@
for xx in range(max_meta_data):
meta_data[str(xx)] = str(xx)
- self.assertRaises(exceptions.OverLimit,
+ # A 403 Forbidden or 413 Overlimit (old behaviour) exception
+ # will be raised when out of quota
+ self.assertRaises((exceptions.Unauthorized, exceptions.OverLimit),
self.server_client.create_server,
name='test', meta=meta_data,
flavor_ref=self.flavor_ref,
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index 067d721..d1192c0 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -29,6 +29,8 @@
def setUpClass(cls):
if not CONF.service_available.neutron:
raise cls.skipException("Neutron is required")
+ if not CONF.compute_feature_enabled.interface_attach:
+ raise cls.skipException("Interface attachment is not available.")
# This test class requires network and subnet
cls.set_network_resources(network=True, subnet=True)
super(AttachInterfacesTestJSON, cls).setUpClass()
diff --git a/tempest/api/compute/servers/test_delete_server.py b/tempest/api/compute/servers/test_delete_server.py
index 9e34922..9c8271f 100644
--- a/tempest/api/compute/servers/test_delete_server.py
+++ b/tempest/api/compute/servers/test_delete_server.py
@@ -70,6 +70,8 @@
self.assertEqual('204', resp['status'])
self.client.wait_for_server_termination(server['id'])
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type='gate')
def test_delete_server_while_in_shelved_state(self):
# Delete a server while it's VM state is Shelved
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 71fcbff..f684a5a 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -15,9 +15,9 @@
import base64
import logging
+import urlparse
import testtools
-import urlparse
from tempest.api.compute import base
from tempest.common.utils import data_utils
@@ -256,6 +256,8 @@
resp, server = self.client.get_server(self.server_id)
self.assertEqual(previous_flavor_ref, server['flavor']['id'])
+ @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
+ 'Snapshotting not available, backup not possible.')
@test.attr(type='gate')
def test_create_backup(self):
# Positive test:create backup successfully and rotate backups correctly
@@ -348,6 +350,8 @@
lines = len(output.split('\n'))
self.assertEqual(lines, 10)
+ @testtools.skipUnless(CONF.compute_feature_enabled.console_output,
+ 'Console output not supported.')
@test.attr(type='gate')
def test_get_console_output(self):
# Positive test:Should be able to GET the console output
@@ -364,6 +368,8 @@
self.wait_for(self._get_output)
+ @testtools.skipUnless(CONF.compute_feature_enabled.console_output,
+ 'Console output not supported.')
@test.attr(type='gate')
def test_get_console_output_server_id_in_shutoff_status(self):
# Positive test:Should be able to GET the console output
@@ -403,6 +409,8 @@
self.assertEqual(202, resp.status)
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type='gate')
def test_shelve_unshelve_server(self):
resp, server = self.client.shelve_server(self.server_id)
diff --git a/tempest/api/compute/servers/test_server_metadata_negative.py b/tempest/api/compute/servers/test_server_metadata_negative.py
index b55833c..fbda401 100644
--- a/tempest/api/compute/servers/test_server_metadata_negative.py
+++ b/tempest/api/compute/servers/test_server_metadata_negative.py
@@ -119,20 +119,22 @@
@test.attr(type=['negative', 'gate'])
def test_metadata_items_limit(self):
- # Raise a 413 OverLimit exception while exceeding metadata items limit
- # for tenant.
+ # A 403 Forbidden or 413 Overlimit (old behaviour) exception
+ # will be raised while exceeding metadata items limit for
+ # tenant.
_, quota_set = self.quotas.get_quota_set(self.tenant_id)
quota_metadata = quota_set['metadata_items']
req_metadata = {}
for num in range(1, quota_metadata + 2):
req_metadata['key' + str(num)] = 'val' + str(num)
- self.assertRaises(exceptions.OverLimit,
+ self.assertRaises((exceptions.OverLimit, exceptions.Unauthorized),
self.client.set_server_metadata,
self.server_id, req_metadata)
- # Raise a 413 OverLimit exception while exceeding metadata items limit
- # for tenant (update).
- self.assertRaises(exceptions.OverLimit,
+ # A 403 Forbidden or 413 Overlimit (old behaviour) exception
+ # will be raised while exceeding metadata items limit for
+ # tenant.
+ self.assertRaises((exceptions.Unauthorized, exceptions.OverLimit),
self.client.update_server_metadata,
self.server_id, req_metadata)
diff --git a/tempest/api/compute/servers/test_server_personality.py b/tempest/api/compute/servers/test_server_personality.py
index b7e4e38..6cc463d 100644
--- a/tempest/api/compute/servers/test_server_personality.py
+++ b/tempest/api/compute/servers/test_server_personality.py
@@ -40,8 +40,10 @@
path = 'etc/test' + str(i) + '.txt'
personality.append({'path': path,
'contents': base64.b64encode(file_contents)})
- self.assertRaises(exceptions.OverLimit, self.create_test_server,
- personality=personality)
+ # A 403 Forbidden or 413 Overlimit (old behaviour) exception
+ # will be raised when out of quota
+ self.assertRaises((exceptions.Unauthorized, exceptions.OverLimit),
+ self.create_test_server, personality=personality)
@test.attr(type='gate')
def test_can_create_server_with_max_number_personality_files(self):
diff --git a/tempest/api/compute/servers/test_server_rescue_negative.py b/tempest/api/compute/servers/test_server_rescue_negative.py
index b35e55c..4582a46 100644
--- a/tempest/api/compute/servers/test_server_rescue_negative.py
+++ b/tempest/api/compute/servers/test_server_rescue_negative.py
@@ -34,7 +34,7 @@
cls.set_network_resources(network=True, subnet=True, router=True)
super(ServerRescueNegativeTestJSON, cls).setUpClass()
- cls.device = 'vdf'
+ cls.device = CONF.compute.volume_device_name
# Create a volume and wait for it to become ready for attach
resp, cls.volume = cls.volumes_extensions_client.create_volume(
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index d3297ce..792b523 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -425,6 +425,8 @@
self.client.restore_soft_deleted_server,
self.server_id)
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type=['negative', 'gate'])
def test_shelve_non_existent_server(self):
# shelve a non existent server
@@ -432,6 +434,8 @@
self.assertRaises(exceptions.NotFound, self.client.shelve_server,
nonexistent_server)
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type=['negative', 'gate'])
def test_shelve_shelved_server(self):
# shelve a shelved server.
@@ -460,6 +464,8 @@
self.client.unshelve_server(self.server_id)
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type=['negative', 'gate'])
def test_unshelve_non_existent_server(self):
# unshelve a non existent server
@@ -467,6 +473,8 @@
self.assertRaises(exceptions.NotFound, self.client.unshelve_server,
nonexistent_server)
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type=['negative', 'gate'])
def test_unshelve_server_invalid_state(self):
# unshelve an active server.
diff --git a/tempest/api/compute/test_authorization.py b/tempest/api/compute/test_authorization.py
index fb8ded3..3fa4a89 100644
--- a/tempest/api/compute/test_authorization.py
+++ b/tempest/api/compute/test_authorization.py
@@ -62,9 +62,9 @@
name = data_utils.rand_name('image')
resp, body = cls.glance_client.create_image(name=name,
- container_format='bare',
- disk_format='raw',
- is_public=False)
+ container_format='bare',
+ disk_format='raw',
+ is_public=False)
image_id = body['id']
image_file = StringIO.StringIO(('*' * 1024))
resp, body = cls.glance_client.update_image(image_id, data=image_file)
diff --git a/tempest/api/compute/v3/admin/test_aggregates.py b/tempest/api/compute/v3/admin/test_aggregates.py
index e5ec08b..886b6a7 100644
--- a/tempest/api/compute/v3/admin/test_aggregates.py
+++ b/tempest/api/compute/v3/admin/test_aggregates.py
@@ -135,7 +135,7 @@
self.assertEqual(200, resp.status)
self.assertIn((aggregate_id, new_aggregate_name, new_az_name),
map(lambda x:
- (x['id'], x['name'], x['availability_zone']),
+ (x['id'], x['name'], x['availability_zone']),
aggregates))
@test.attr(type='gate')
diff --git a/tempest/api/compute/v3/admin/test_flavors_access.py b/tempest/api/compute/v3/admin/test_flavors_access.py
index c641bf6..09b6ebd 100644
--- a/tempest/api/compute/v3/admin/test_flavors_access.py
+++ b/tempest/api/compute/v3/admin/test_flavors_access.py
@@ -38,7 +38,6 @@
cls.vcpus = 1
cls.disk = 10
- @test.skip_because(bug='1265416')
@test.attr(type='gate')
def test_flavor_access_list_with_private_flavor(self):
# Test to list flavor access successfully by querying private flavor
@@ -58,7 +57,6 @@
self.assertEqual(str(new_flavor_id), str(first_flavor['flavor_id']))
self.assertEqual(self.adm_tenant_id, first_flavor['tenant_id'])
- @test.skip_because(bug='1265416')
@test.attr(type='gate')
def test_flavor_access_add_remove(self):
# Test to add and remove flavor access to a given tenant.
diff --git a/tempest/api/compute/v3/admin/test_flavors_access_negative.py b/tempest/api/compute/v3/admin/test_flavors_access_negative.py
index 02ecb24..0fdfabd 100644
--- a/tempest/api/compute/v3/admin/test_flavors_access_negative.py
+++ b/tempest/api/compute/v3/admin/test_flavors_access_negative.py
@@ -55,7 +55,6 @@
self.client.list_flavor_access,
new_flavor_id)
- @test.skip_because(bug='1265416')
@test.attr(type=['negative', 'gate'])
def test_flavor_non_admin_add(self):
# Test to add flavor access as a user without admin privileges.
@@ -72,7 +71,6 @@
new_flavor['id'],
self.tenant_id)
- @test.skip_because(bug='1265416')
@test.attr(type=['negative', 'gate'])
def test_flavor_non_admin_remove(self):
# Test to remove flavor access as a user without admin privileges.
@@ -93,7 +91,6 @@
new_flavor['id'],
self.tenant_id)
- @test.skip_because(bug='1265416')
@test.attr(type=['negative', 'gate'])
def test_add_flavor_access_duplicate(self):
# Create a new flavor.
@@ -118,7 +115,6 @@
new_flavor['id'],
self.tenant_id)
- @test.skip_because(bug='1265416')
@test.attr(type=['negative', 'gate'])
def test_remove_flavor_access_not_found(self):
# Create a new flavor.
diff --git a/tempest/api/compute/v3/admin/test_servers.py b/tempest/api/compute/v3/admin/test_servers.py
index 366cfc6..d99c329 100644
--- a/tempest/api/compute/v3/admin/test_servers.py
+++ b/tempest/api/compute/v3/admin/test_servers.py
@@ -51,7 +51,6 @@
self.assertEqual('200', resp['status'])
self.assertEqual([], servers)
- @test.skip_because(bug='1265416')
@test.attr(type='gate')
def test_list_servers_by_admin_with_all_tenants(self):
# Listing servers by admin user with all tenants parameter
diff --git a/tempest/api/compute/v3/flavors/test_flavors_negative.py b/tempest/api/compute/v3/flavors/test_flavors_negative.py
index 657e2cd..cdf018f 100644
--- a/tempest/api/compute/v3/flavors/test_flavors_negative.py
+++ b/tempest/api/compute/v3/flavors/test_flavors_negative.py
@@ -14,6 +14,7 @@
# under the License.
from tempest.api.compute import base
+from tempest.api_schema.request.compute.v3 import flavors
from tempest import test
@@ -24,14 +25,14 @@
class FlavorsListNegativeV3Test(base.BaseV3ComputeTest,
test.NegativeAutoTest):
_service = 'computev3'
- _schema_file = 'compute/flavors/flavors_list_v3.json'
+ _schema = flavors.flavor_list
@test.SimpleNegativeAutoTest
class FlavorDetailsNegativeV3Test(base.BaseV3ComputeTest,
test.NegativeAutoTest):
_service = 'computev3'
- _schema_file = 'compute/flavors/flavor_details_v3.json'
+ _schema = flavors.flavors_details
@classmethod
def setUpClass(cls):
diff --git a/tempest/api/compute/v3/servers/test_attach_interfaces.py b/tempest/api/compute/v3/servers/test_attach_interfaces.py
index 43440c1..c2cf7e0 100644
--- a/tempest/api/compute/v3/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/v3/servers/test_attach_interfaces.py
@@ -29,6 +29,8 @@
def setUpClass(cls):
if not CONF.service_available.neutron:
raise cls.skipException("Neutron is required")
+ if not CONF.compute_feature_enabled.interface_attach:
+ raise cls.skipException("Interface attachment is not available.")
# This test class requires network and subnet
cls.set_network_resources(network=True, subnet=True)
super(AttachInterfacesV3Test, cls).setUpClass()
diff --git a/tempest/api/compute/v3/servers/test_delete_server.py b/tempest/api/compute/v3/servers/test_delete_server.py
index add69ab..e2b47ee 100644
--- a/tempest/api/compute/v3/servers/test_delete_server.py
+++ b/tempest/api/compute/v3/servers/test_delete_server.py
@@ -68,6 +68,8 @@
self.assertEqual('204', resp['status'])
self.client.wait_for_server_termination(server['id'])
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type='gate')
def test_delete_server_while_in_shelved_state(self):
# Delete a server while it's VM state is Shelved
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
index 3ee8050..538507f 100644
--- a/tempest/api/compute/v3/servers/test_server_actions.py
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -14,9 +14,9 @@
# under the License.
import logging
+import urlparse
import testtools
-import urlparse
from tempest.api.compute import base
from tempest.common.utils import data_utils
@@ -250,6 +250,8 @@
resp, server = self.client.get_server(self.server_id)
self.assertEqual(previous_flavor_ref, server['flavor']['id'])
+ @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
+ 'Snapshotting not available, backup not possible.')
@test.attr(type='gate')
def test_create_backup(self):
# Positive test:create backup successfully and rotate backups correctly
@@ -339,6 +341,8 @@
lines = len(output.split('\n'))
self.assertEqual(lines, 10)
+ @testtools.skipUnless(CONF.compute_feature_enabled.console_output,
+ 'Console output not supported.')
@test.attr(type='gate')
def test_get_console_output(self):
# Positive test:Should be able to GET the console output
@@ -355,6 +359,8 @@
self.wait_for(self._get_output)
+ @testtools.skipUnless(CONF.compute_feature_enabled.console_output,
+ 'Console output not supported.')
@test.attr(type='gate')
def test_get_console_output_server_id_in_shutoff_status(self):
# Positive test:Should be able to GET the console output
@@ -394,6 +400,8 @@
self.assertEqual(202, resp.status)
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type='gate')
def test_shelve_unshelve_server(self):
resp, server = self.client.shelve_server(self.server_id)
diff --git a/tempest/api/compute/v3/servers/test_server_rescue_negative.py b/tempest/api/compute/v3/servers/test_server_rescue_negative.py
index 5eb6c9a..6d192a3 100644
--- a/tempest/api/compute/v3/servers/test_server_rescue_negative.py
+++ b/tempest/api/compute/v3/servers/test_server_rescue_negative.py
@@ -33,7 +33,7 @@
raise cls.skipException(msg)
super(ServerRescueNegativeV3Test, cls).setUpClass()
- cls.device = 'vdf'
+ cls.device = CONF.compute.volume_device_name
# Create a volume and wait for it to become ready for attach
resp, cls.volume = cls.volumes_client.create_volume(
@@ -56,7 +56,8 @@
@classmethod
def tearDownClass(cls):
- cls.delete_volume(cls.volume['id'])
+ if hasattr(cls, 'volume'):
+ cls.delete_volume(cls.volume['id'])
super(ServerRescueNegativeV3Test, cls).tearDownClass()
def _detach(self, server_id, volume_id):
diff --git a/tempest/api/compute/v3/servers/test_servers_negative.py b/tempest/api/compute/v3/servers/test_servers_negative.py
index 90deaa9..f8ff7c8 100644
--- a/tempest/api/compute/v3/servers/test_servers_negative.py
+++ b/tempest/api/compute/v3/servers/test_servers_negative.py
@@ -397,6 +397,8 @@
self.client.restore_soft_deleted_server,
self.server_id)
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type=['negative', 'gate'])
def test_shelve_non_existent_server(self):
# shelve a non existent server
@@ -404,6 +406,8 @@
self.assertRaises(exceptions.NotFound, self.client.shelve_server,
nonexistent_server)
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type=['negative', 'gate'])
def test_shelve_shelved_server(self):
# shelve a shelved server.
@@ -431,6 +435,8 @@
self.client.unshelve_server(self.server_id)
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type=['negative', 'gate'])
def test_unshelve_non_existent_server(self):
# unshelve a non existent server
@@ -438,6 +444,8 @@
self.assertRaises(exceptions.NotFound, self.client.unshelve_server,
nonexistent_server)
+ @testtools.skipUnless(CONF.compute_feature_enabled.shelve,
+ 'Shelve is not available.')
@test.attr(type=['negative', 'gate'])
def test_unshelve_server_invalid_state(self):
# unshelve an active server.
diff --git a/tempest/api/compute/volumes/test_volumes_get.py b/tempest/api/compute/volumes/test_volumes_get.py
index c3d6ba6..708524c 100644
--- a/tempest/api/compute/volumes/test_volumes_get.py
+++ b/tempest/api/compute/volumes/test_volumes_get.py
@@ -13,11 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
+from testtools import matchers
+
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import config
from tempest import test
-from testtools import matchers
+
CONF = config.CONF
diff --git a/tempest/api/data_processing/base.py b/tempest/api/data_processing/base.py
index cfb5a3d..65085b9 100644
--- a/tempest/api/data_processing/base.py
+++ b/tempest/api/data_processing/base.py
@@ -40,6 +40,7 @@
cls._data_sources = []
cls._job_binary_internals = []
cls._job_binaries = []
+ cls._jobs = []
@classmethod
def tearDownClass(cls):
@@ -47,12 +48,13 @@
cls.client.delete_cluster_template)
cls.cleanup_resources(getattr(cls, '_node_group_templates', []),
cls.client.delete_node_group_template)
- cls.cleanup_resources(getattr(cls, '_data_sources', []),
- cls.client.delete_data_source)
- cls.cleanup_resources(getattr(cls, '_job_binary_internals', []),
- cls.client.delete_job_binary_internal)
+ cls.cleanup_resources(getattr(cls, '_jobs', []), cls.client.delete_job)
cls.cleanup_resources(getattr(cls, '_job_binaries', []),
cls.client.delete_job_binary)
+ cls.cleanup_resources(getattr(cls, '_job_binary_internals', []),
+ cls.client.delete_job_binary_internal)
+ cls.cleanup_resources(getattr(cls, '_data_sources', []),
+ cls.client.delete_data_source)
cls.clear_isolated_creds()
super(BaseDataProcessingTest, cls).tearDownClass()
@@ -132,6 +134,7 @@
return resp_body
+ @classmethod
def create_job_binary(cls, name, url, extra=None, **kwargs):
"""Creates watched job binary with specified params.
@@ -144,3 +147,18 @@
cls._job_binaries.append(resp_body['id'])
return resp_body
+
+ @classmethod
+ def create_job(cls, name, job_type, mains, libs=None, **kwargs):
+ """Creates watched job with specified params.
+
+ It supports passing additional params using kwargs and returns created
+ object. All resources created in this method will be automatically
+ removed in tearDownClass method.
+ """
+ _, resp_body = cls.client.create_job(name,
+ job_type, mains, libs, **kwargs)
+ # store id of created job
+ cls._jobs.append(resp_body['id'])
+
+ return resp_body
diff --git a/tempest/api/data_processing/test_jobs.py b/tempest/api/data_processing/test_jobs.py
new file mode 100644
index 0000000..8591dbd
--- /dev/null
+++ b/tempest/api/data_processing/test_jobs.py
@@ -0,0 +1,90 @@
+# Copyright (c) 2014 Mirantis Inc.
+#
+# 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.data_processing import base as dp_base
+from tempest.common.utils import data_utils
+from tempest import test
+
+
+class JobTest(dp_base.BaseDataProcessingTest):
+ """Link to the API documentation is http://docs.openstack.org/developer/
+ sahara/restapi/rest_api_v1.1_EDP.html#jobs
+ """
+ @classmethod
+ @test.safe_setup
+ def setUpClass(cls):
+ super(JobTest, cls).setUpClass()
+ # create job binary
+ job_binary = {
+ 'name': data_utils.rand_name('sahara-job-binary'),
+ 'url': 'swift://sahara-container.sahara/example.jar',
+ 'description': 'Test job binary',
+ 'extra': {
+ 'user': cls.os.credentials.username,
+ 'password': cls.os.credentials.password
+ }
+ }
+ resp_body = cls.create_job_binary(**job_binary)
+ job_binary_id = resp_body['id']
+
+ cls.job = {
+ 'job_type': 'Pig',
+ 'mains': [job_binary_id]
+ }
+
+ def _create_job(self, job_name=None):
+ """Creates Job with optional name specified.
+
+ It creates job and ensures job name. Returns id and name of created
+ job.
+ """
+ if not job_name:
+ # generate random name if it's not specified
+ job_name = data_utils.rand_name('sahara-job')
+
+ # create job
+ resp_body = self.create_job(job_name, **self.job)
+
+ # ensure that job created successfully
+ self.assertEqual(job_name, resp_body['name'])
+
+ return resp_body['id'], job_name
+
+ @test.attr(type='smoke')
+ def test_job_create(self):
+ self._create_job()
+
+ @test.attr(type='smoke')
+ def test_job_list(self):
+ job_info = self._create_job()
+
+ # check for job in list
+ _, jobs = self.client.list_jobs()
+ jobs_info = [(job['id'], job['name']) for job in jobs]
+ self.assertIn(job_info, jobs_info)
+
+ @test.attr(type='smoke')
+ def test_job_get(self):
+ job_id, job_name = self._create_job()
+
+ # check job fetch by id
+ _, job = self.client.get_job(job_id)
+ self.assertEqual(job_name, job['name'])
+
+ @test.attr(type='smoke')
+ def test_job_delete(self):
+ job_id, _ = self._create_job()
+
+ # delete the job by id
+ self.client.delete_job(job_id)
diff --git a/tempest/api/identity/admin/test_roles.py b/tempest/api/identity/admin/test_roles.py
index 7a6d07f..492d56f 100644
--- a/tempest/api/identity/admin/test_roles.py
+++ b/tempest/api/identity/admin/test_roles.py
@@ -49,7 +49,7 @@
@test.attr(type='gate')
def test_list_roles(self):
- # Return a list of all roles
+ """Return a list of all roles."""
_, body = self.client.list_roles()
found = [role for role in body if role in self.data.roles]
self.assertTrue(any(found))
@@ -57,7 +57,7 @@
@test.attr(type='gate')
def test_role_create_delete(self):
- # Role should be created, verified, and deleted
+ """Role should be created, verified, and deleted."""
role_name = data_utils.rand_name(name='role-test-')
_, body = self.client.create_role(role_name)
self.assertEqual(role_name, body['name'])
@@ -74,7 +74,7 @@
@test.attr(type='gate')
def test_get_role_by_id(self):
- # Get a role by its id
+ """Get a role by its id."""
self.data.setup_test_role()
role_id = self.data.role['id']
role_name = self.data.role['name']
@@ -84,7 +84,7 @@
@test.attr(type='gate')
def test_assign_user_role(self):
- # Assign a role to a user on a tenant
+ """Assign a role to a user on a tenant."""
(user, tenant, role) = self._get_role_params()
self.client.assign_user_role(tenant['id'], user['id'], role['id'])
_, roles = self.client.list_user_roles(tenant['id'], user['id'])
@@ -92,7 +92,7 @@
@test.attr(type='gate')
def test_remove_user_role(self):
- # Remove a role assigned to a user on a tenant
+ """Remove a role assigned to a user on a tenant."""
(user, tenant, role) = self._get_role_params()
_, user_role = self.client.assign_user_role(tenant['id'],
user['id'], role['id'])
@@ -101,7 +101,7 @@
@test.attr(type='gate')
def test_list_user_roles(self):
- # List roles assigned to a user on tenant
+ """List roles assigned to a user on tenant."""
(user, tenant, role) = self._get_role_params()
self.client.assign_user_role(tenant['id'], user['id'], role['id'])
_, roles = self.client.list_user_roles(tenant['id'], user['id'])
diff --git a/tempest/api/identity/admin/v3/test_list_projects.py b/tempest/api/identity/admin/v3/test_list_projects.py
new file mode 100644
index 0000000..a3944e2
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_list_projects.py
@@ -0,0 +1,73 @@
+# 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.
+
+from tempest.api.identity import base
+from tempest.common.utils import data_utils
+from tempest import test
+
+
+class ListProjectsTestJSON(base.BaseIdentityV3AdminTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ListProjectsTestJSON, cls).setUpClass()
+ cls.project_ids = list()
+ cls.data.setup_test_domain()
+ # Create project with domain
+ cls.p1_name = data_utils.rand_name('project')
+ _, cls.p1 = cls.client.create_project(
+ cls.p1_name, enabled=False, domain_id=cls.data.domain['id'])
+ cls.data.projects.append(cls.p1)
+ cls.project_ids.append(cls.p1['id'])
+ # Create default project
+ p2_name = data_utils.rand_name('project')
+ _, cls.p2 = cls.client.create_project(p2_name)
+ cls.data.projects.append(cls.p2)
+ cls.project_ids.append(cls.p2['id'])
+
+ @test.attr(type='gate')
+ def test_projects_list(self):
+ # List projects
+ resp, list_projects = self.client.list_projects()
+
+ for p in self.project_ids:
+ _, get_project = self.client.get_project(p)
+ self.assertIn(get_project, list_projects)
+
+ @test.attr(type='gate')
+ def test_list_projects_with_domains(self):
+ # List projects with domain
+ self._list_projects_with_params(
+ {'domain_id': self.data.domain['id']}, 'domain_id')
+
+ @test.attr(type='gate')
+ def test_list_projects_with_enabled(self):
+ # List the projects with enabled
+ self._list_projects_with_params({'enabled': False}, 'enabled')
+
+ @test.attr(type='gate')
+ def test_list_projects_with_name(self):
+ # List projects with name
+ self._list_projects_with_params({'name': self.p1_name}, 'name')
+
+ def _list_projects_with_params(self, params, key):
+ resp, body = self.client.list_projects(params)
+ self.assertIn(self.p1[key], map(lambda x: x[key], body))
+ self.assertNotIn(self.p2[key], map(lambda x: x[key], body))
+
+
+class ListProjectsTestXML(ListProjectsTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/identity/admin/v3/test_projects.py b/tempest/api/identity/admin/v3/test_projects.py
index 77acd57..5890eab 100644
--- a/tempest/api/identity/admin/v3/test_projects.py
+++ b/tempest/api/identity/admin/v3/test_projects.py
@@ -13,35 +13,14 @@
# License for the specific language governing permissions and limitations
# under the License.
-from six import moves
-
from tempest.api.identity import base
from tempest.common.utils import data_utils
-from tempest import exceptions
from tempest import test
class ProjectsTestJSON(base.BaseIdentityV3AdminTest):
_interface = 'json'
- def _delete_project(self, project_id):
- self.client.delete_project(project_id)
- self.assertRaises(
- exceptions.NotFound, self.client.get_project, project_id)
-
- @test.attr(type='gate')
- def test_project_list_delete(self):
- # Create several projects and delete them
- for _ in moves.xrange(3):
- _, project = self.client.create_project(
- data_utils.rand_name('project-new'))
- self.addCleanup(self._delete_project, project['id'])
-
- _, list_projects = self.client.list_projects()
-
- _, get_project = self.client.get_project(project['id'])
- self.assertIn(get_project, list_projects)
-
@test.attr(type='gate')
def test_project_create_with_description(self):
# Create project with a description
@@ -60,6 +39,21 @@
'to be set')
@test.attr(type='gate')
+ def test_project_create_with_domain(self):
+ # Create project with a domain
+ self.data.setup_test_domain()
+ project_name = data_utils.rand_name('project')
+ resp, project = self.client.create_project(
+ project_name, domain_id=self.data.domain['id'])
+ self.data.projects.append(project)
+ project_id = project['id']
+ self.assertEqual(project_name, project['name'])
+ self.assertEqual(self.data.domain['id'], project['domain_id'])
+ _, body = self.client.get_project(project_id)
+ self.assertEqual(project_name, body['name'])
+ self.assertEqual(self.data.domain['id'], body['domain_id'])
+
+ @test.attr(type='gate')
def test_project_create_enabled(self):
# Create a project that is enabled
project_name = data_utils.rand_name('project-')
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index e61b738..bd08614 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -48,6 +48,7 @@
self.assertRaises(exceptions.NotFound, self.client.get_token,
subject_token)
+ @test.skip_because(bug="1351026")
@test.attr(type='gate')
def test_rescope_token(self):
"""Rescope a token.
diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py
index 1561a6e..886c808 100644
--- a/tempest/api/identity/admin/v3/test_trusts.py
+++ b/tempest/api/identity/admin/v3/test_trusts.py
@@ -12,6 +12,7 @@
import datetime
import re
+
from tempest.api.identity import base
from tempest import auth
from tempest import clients
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 8eb7d33..3996cc1 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -18,9 +18,12 @@
from tempest import clients
from tempest.common.utils import data_utils
from tempest import config
+from tempest import exceptions
+from tempest.openstack.common import log as logging
import tempest.test
CONF = config.CONF
+LOG = logging.getLogger(__name__)
class BaseIdentityAdminTest(tempest.test.BaseTestCase):
@@ -195,19 +198,36 @@
description=self.test_description)
self.domains.append(self.domain)
+ @staticmethod
+ def _try_wrapper(func, item, **kwargs):
+ try:
+ if kwargs:
+ func(item['id'], kwargs)
+ else:
+ func(item['id'])
+ except exceptions.NotFound:
+ pass
+ except Exception:
+ LOG.exception("Unexpected exception occurred in %s deletion."
+ " But ignored here." % item['id'])
+
def teardown_all(self):
+ # NOTE(masayukig): v3 client doesn't have v2 method.
+ # (e.g. delete_tenant) So we need to check resources existence
+ # before using client methods.
for user in self.users:
- self.client.delete_user(user['id'])
+ self._try_wrapper(self.client.delete_user, user)
for tenant in self.tenants:
- self.client.delete_tenant(tenant['id'])
+ self._try_wrapper(self.client.delete_tenant, tenant)
for role in self.roles:
- self.client.delete_role(role['id'])
+ self._try_wrapper(self.client.delete_role, role)
for v3_user in self.v3_users:
- self.client.delete_user(v3_user['id'])
+ self._try_wrapper(self.client.delete_user, v3_user)
for v3_project in self.projects:
- self.client.delete_project(v3_project['id'])
+ self._try_wrapper(self.client.delete_project, v3_project)
for v3_role in self.v3_roles:
- self.client.delete_role(v3_role['id'])
+ self._try_wrapper(self.client.delete_role, v3_role)
for domain in self.domains:
- self.client.update_domain(domain['id'], enabled=False)
- self.client.delete_domain(domain['id'])
+ self._try_wrapper(self.client.update_domain, domain,
+ enabled=False)
+ self._try_wrapper(self.client.delete_domain, domain)
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index 02d391b..c875b2f 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -100,7 +100,7 @@
cls.alt_tenant_id = cls.alt_img_cli.tenant_id
def _create_image(self):
- image_file = StringIO.StringIO('*' * 1024)
+ image_file = StringIO.StringIO(data_utils.random_bytes())
resp, image = self.create_image(container_format='bare',
disk_format='raw',
is_public=False,
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index 8528e42..bf55b89 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -44,7 +44,7 @@
self.assertEqual(val, body.get('properties')[key])
# Now try uploading an image file
- image_file = StringIO.StringIO(('*' * 1024))
+ image_file = StringIO.StringIO(data_utils.random_bytes())
_, body = self.client.update_image(image_id, data=image_file)
self.assertIn('size', body)
self.assertEqual(1024, body.get('size'))
@@ -157,7 +157,7 @@
image. Note that the size of the new image is a random number between
1024 and 4096
"""
- image_file = StringIO.StringIO('*' * size)
+ image_file = StringIO.StringIO(data_utils.random_bytes(size))
name = 'New Standard Image %s' % name
_, image = cls.create_image(name=name,
container_format=container_format,
@@ -338,10 +338,9 @@
disk_format, size):
"""
Create a new standard image and return the ID of the newly-registered
- image. Note that the size of the new image is a random number between
- 1024 and 4096
+ image.
"""
- image_file = StringIO.StringIO('*' * size)
+ image_file = StringIO.StringIO(data_utils.random_bytes(size))
name = 'New Standard Image %s' % name
_, image = cls.create_image(name=name,
container_format=container_format,
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 4226815..a974ebb 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -52,7 +52,7 @@
self.assertEqual('queued', body['status'])
# Now try uploading an image file
- file_content = '*' * 1024
+ file_content = data_utils.random_bytes()
image_file = StringIO.StringIO(file_content)
self.client.store_image(image_id, image_file)
@@ -104,8 +104,7 @@
image_id = body['id']
# Now try uploading an image file
- file_content = '*' * 1024
- image_file = StringIO.StringIO(file_content)
+ image_file = StringIO.StringIO(data_utils.random_bytes())
self.client.store_image(image_id, image_file)
# Update Image
@@ -146,7 +145,8 @@
image. Note that the size of the new image is a random number between
1024 and 4096
"""
- image_file = StringIO.StringIO('*' * random.randint(1024, 4096))
+ size = random.randint(1024, 4096)
+ image_file = StringIO.StringIO(data_utils.random_bytes(size))
name = data_utils.rand_name('image-')
_, body = cls.create_image(name=name,
container_format=container_format,
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 087b87a..d75339c 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -85,57 +85,58 @@
@classmethod
def tearDownClass(cls):
- # Clean up ipsec policies
- for ipsecpolicy in cls.ipsecpolicies:
- cls.client.delete_ipsecpolicy(ipsecpolicy['id'])
- # Clean up firewall policies
- for fw_policy in cls.fw_policies:
- cls.client.delete_firewall_policy(fw_policy['id'])
- # Clean up firewall rules
- for fw_rule in cls.fw_rules:
- cls.client.delete_firewall_rule(fw_rule['id'])
- # Clean up ike policies
- for ikepolicy in cls.ikepolicies:
- cls.client.delete_ikepolicy(ikepolicy['id'])
- # Clean up vpn services
- for vpnservice in cls.vpnservices:
- cls.client.delete_vpnservice(vpnservice['id'])
- # Clean up floating IPs
- for floating_ip in cls.floating_ips:
- cls.client.delete_floatingip(floating_ip['id'])
- # Clean up routers
- for router in cls.routers:
- cls.delete_router(router)
+ if CONF.service_available.neutron:
+ # Clean up ipsec policies
+ for ipsecpolicy in cls.ipsecpolicies:
+ cls.client.delete_ipsecpolicy(ipsecpolicy['id'])
+ # Clean up firewall policies
+ for fw_policy in cls.fw_policies:
+ cls.client.delete_firewall_policy(fw_policy['id'])
+ # Clean up firewall rules
+ for fw_rule in cls.fw_rules:
+ cls.client.delete_firewall_rule(fw_rule['id'])
+ # Clean up ike policies
+ for ikepolicy in cls.ikepolicies:
+ cls.client.delete_ikepolicy(ikepolicy['id'])
+ # Clean up vpn services
+ for vpnservice in cls.vpnservices:
+ cls.client.delete_vpnservice(vpnservice['id'])
+ # Clean up floating IPs
+ for floating_ip in cls.floating_ips:
+ cls.client.delete_floatingip(floating_ip['id'])
+ # Clean up routers
+ for router in cls.routers:
+ cls.delete_router(router)
- # Clean up health monitors
- for health_monitor in cls.health_monitors:
- cls.client.delete_health_monitor(health_monitor['id'])
- # Clean up members
- for member in cls.members:
- cls.client.delete_member(member['id'])
- # Clean up vips
- for vip in cls.vips:
- cls.client.delete_vip(vip['id'])
- # Clean up pools
- for pool in cls.pools:
- cls.client.delete_pool(pool['id'])
- # Clean up metering label rules
- for metering_label_rule in cls.metering_label_rules:
- cls.admin_client.delete_metering_label_rule(
- metering_label_rule['id'])
- # Clean up metering labels
- for metering_label in cls.metering_labels:
- cls.admin_client.delete_metering_label(metering_label['id'])
- # Clean up ports
- for port in cls.ports:
- cls.client.delete_port(port['id'])
- # Clean up subnets
- for subnet in cls.subnets:
- cls.client.delete_subnet(subnet['id'])
- # Clean up networks
- for network in cls.networks:
- cls.client.delete_network(network['id'])
- cls.clear_isolated_creds()
+ # Clean up health monitors
+ for health_monitor in cls.health_monitors:
+ cls.client.delete_health_monitor(health_monitor['id'])
+ # Clean up members
+ for member in cls.members:
+ cls.client.delete_member(member['id'])
+ # Clean up vips
+ for vip in cls.vips:
+ cls.client.delete_vip(vip['id'])
+ # Clean up pools
+ for pool in cls.pools:
+ cls.client.delete_pool(pool['id'])
+ # Clean up metering label rules
+ for metering_label_rule in cls.metering_label_rules:
+ cls.admin_client.delete_metering_label_rule(
+ metering_label_rule['id'])
+ # Clean up metering labels
+ for metering_label in cls.metering_labels:
+ cls.admin_client.delete_metering_label(metering_label['id'])
+ # Clean up ports
+ for port in cls.ports:
+ cls.client.delete_port(port['id'])
+ # Clean up subnets
+ for subnet in cls.subnets:
+ cls.client.delete_subnet(subnet['id'])
+ # Clean up networks
+ for network in cls.networks:
+ cls.client.delete_network(network['id'])
+ cls.clear_isolated_creds()
super(BaseNetworkTest, cls).tearDownClass()
@classmethod
diff --git a/tempest/api/network/test_allowed_address_pair.py b/tempest/api/network/test_allowed_address_pair.py
index c897716..8d984d1 100644
--- a/tempest/api/network/test_allowed_address_pair.py
+++ b/tempest/api/network/test_allowed_address_pair.py
@@ -85,8 +85,8 @@
# Update allowed address pair attribute of port
allowed_address_pairs = [{'ip_address': self.ip_address,
'mac_address': self.mac_address}]
- resp, body = self.client.update_port(port_id,
- allowed_address_pairs=allowed_address_pairs)
+ resp, body = self.client.update_port(
+ port_id, allowed_address_pairs=allowed_address_pairs)
self.assertEqual('200', resp['status'])
newport = body['port']
self._confirm_allowed_address_pair(newport, self.ip_address)
diff --git a/tempest/api/network/test_load_balancer.py b/tempest/api/network/test_load_balancer.py
index db24e0d..7a12ef6 100644
--- a/tempest/api/network/test_load_balancer.py
+++ b/tempest/api/network/test_load_balancer.py
@@ -290,8 +290,8 @@
health_monitor = body['health_monitor']
# Verification of health_monitor update
resp, body = (self.client.update_health_monitor
- (health_monitor['id'],
- admin_state_up=False))
+ (health_monitor['id'],
+ admin_state_up=False))
self.assertEqual('200', resp['status'])
updated_health_monitor = body['health_monitor']
self.assertFalse(updated_health_monitor['admin_state_up'])
@@ -323,10 +323,10 @@
self.addCleanup(self.client.delete_health_monitor,
health_monitor['id'])
resp, body = (self.client.update_health_monitor
- (health_monitor['id'],
- http_method="POST",
- url_path="/home/user",
- expected_codes="290"))
+ (health_monitor['id'],
+ http_method="POST",
+ url_path="/home/user",
+ expected_codes="290"))
self.assertEqual('200', resp['status'])
updated_health_monitor = body['health_monitor']
self.assertEqual("POST", updated_health_monitor['http_method'])
@@ -348,7 +348,7 @@
def test_associate_disassociate_health_monitor_with_pool(self):
# Verify that a health monitor can be associated with a pool
resp, body = (self.client.associate_health_monitor_with_pool
- (self.health_monitor['id'], self.pool['id']))
+ (self.health_monitor['id'], self.pool['id']))
self.assertEqual('201', resp['status'])
resp, body = self.client.show_health_monitor(
self.health_monitor['id'])
@@ -360,7 +360,7 @@
self.assertIn(health_monitor['id'], pool['health_monitors'])
# Verify that a health monitor can be disassociated from a pool
resp, body = (self.client.disassociate_health_monitor_with_pool
- (self.health_monitor['id'], self.pool['id']))
+ (self.health_monitor['id'], self.pool['id']))
self.assertEqual('204', resp['status'])
resp, body = self.client.show_pool(self.pool['id'])
pool = body['pool']
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index d38633f..878335d 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -110,6 +110,36 @@
create_body['router']['id'])
self.assertEqual(tenant_id, create_body['router']['tenant_id'])
+ @test.requires_ext(extension='ext-gw-mode', service='network')
+ @test.attr(type='smoke')
+ def test_create_router_with_default_snat_value(self):
+ # Create a router with default snat rule
+ name = data_utils.rand_name('router')
+ router = self._create_router(
+ name, external_network_id=CONF.network.public_network_id)
+ self._verify_router_gateway(
+ router['id'], {'network_id': CONF.network.public_network_id,
+ 'enable_snat': True})
+
+ @test.requires_ext(extension='ext-gw-mode', service='network')
+ @test.attr(type='smoke')
+ def test_create_router_with_snat_explicit(self):
+ name = data_utils.rand_name('snat-router')
+ # Create a router enabling snat attributes
+ enable_snat_states = [False, True]
+ for enable_snat in enable_snat_states:
+ external_gateway_info = {
+ 'network_id': CONF.network.public_network_id,
+ 'enable_snat': enable_snat}
+ resp, create_body = self.admin_client.create_router(
+ name, external_gateway_info=external_gateway_info)
+ self.assertEqual('201', resp['status'])
+ self.addCleanup(self.admin_client.delete_router,
+ create_body['router']['id'])
+ # Verify snat attributes after router creation
+ self._verify_router_gateway(create_body['router']['id'],
+ exp_ext_gw_info=external_gateway_info)
+
@test.attr(type='smoke')
def test_add_remove_router_interface_with_subnet_id(self):
network = self.create_network()
@@ -151,7 +181,7 @@
router['id'])
def _verify_router_gateway(self, router_id, exp_ext_gw_info=None):
- resp, show_body = self.client.show_router(router_id)
+ resp, show_body = self.admin_client.show_router(router_id)
self.assertEqual('200', resp['status'])
actual_ext_gw_info = show_body['router']['external_gateway_info']
if exp_ext_gw_info is None:
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 1ef9aa1..8b74b7e 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -17,10 +17,11 @@
import hashlib
import random
import re
-from six import moves
import time
import zlib
+import six
+
from tempest.api.object_storage import base
from tempest.common import custom_matchers
from tempest.common.utils import data_utils
@@ -54,15 +55,36 @@
object_name = data_utils.rand_name(name='LObject')
data = data_utils.arbitrary_string()
segments = 10
- data_segments = [data + str(i) for i in moves.xrange(segments)]
+ data_segments = [data + str(i) for i in six.moves.xrange(segments)]
# uploading segments
- for i in moves.xrange(segments):
+ for i in six.moves.xrange(segments):
resp, _ = self.object_client.create_object_segments(
self.container_name, object_name, i, data_segments[i])
self.assertEqual(resp['status'], '201')
return object_name, data_segments
+ def _copy_object_2d(self, src_object_name, metadata=None):
+ dst_object_name = data_utils.rand_name(name='TestObject')
+ resp, _ = self.object_client.copy_object_2d_way(self.container_name,
+ src_object_name,
+ dst_object_name,
+ metadata=metadata)
+ return dst_object_name, resp
+
+ def _check_copied_obj(self, dst_object_name, src_body,
+ in_meta=None, not_in_meta=None):
+ resp, dest_body = self.object_client.get_object(self.container_name,
+ dst_object_name)
+
+ self.assertEqual(src_body, dest_body)
+ if in_meta:
+ for meta_key in in_meta:
+ self.assertIn('x-object-meta-' + meta_key, resp)
+ if not_in_meta:
+ for meta_key in not_in_meta:
+ self.assertNotIn('x-object-meta-' + meta_key, resp)
+
@test.attr(type='gate')
def test_create_object(self):
# create object
@@ -765,10 +787,7 @@
# change the content type of an existing object
# create object
- object_name = data_utils.rand_name(name='TestObject')
- data = data_utils.arbitrary_string()
- self.object_client.create_object(self.container_name,
- object_name, data)
+ object_name, data = self._create_object()
# get the old content type
resp_tmp, _ = self.object_client.list_object_metadata(
self.container_name, object_name)
@@ -805,20 +824,12 @@
dst_object_name)
self.assertEqual(resp['status'], '201')
self.assertHeaders(resp, 'Object', 'COPY')
-
- self.assertIn('last-modified', resp)
- self.assertIn('x-copied-from', resp)
- self.assertIn('x-copied-from-last-modified', resp)
- self.assertNotEqual(len(resp['last-modified']), 0)
self.assertEqual(
resp['x-copied-from'],
self.container_name + "/" + src_object_name)
- self.assertNotEqual(len(resp['x-copied-from-last-modified']), 0)
# check data
- resp, body = self.object_client.get_object(self.container_name,
- dst_object_name)
- self.assertEqual(body, src_data)
+ self._check_copied_obj(dst_object_name, src_data)
@test.attr(type='smoke')
def test_copy_object_across_containers(self):
@@ -862,15 +873,82 @@
self.assertIn(actual_meta_key, resp)
self.assertEqual(resp[actual_meta_key], meta_value)
+ @test.attr(type='smoke')
+ def test_copy_object_with_x_fresh_metadata(self):
+ # create source object
+ metadata = {'x-object-meta-src': 'src_value'}
+ src_object_name, data = self._create_object(metadata)
+
+ # copy source object with x_fresh_metadata header
+ metadata = {'X-Fresh-Metadata': 'true'}
+ dst_object_name, resp = self._copy_object_2d(src_object_name,
+ metadata)
+
+ self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'COPY')
+
+ self.assertNotIn('x-object-meta-src', resp)
+ self.assertEqual(resp['x-copied-from'],
+ self.container_name + "/" + src_object_name)
+
+ # check that destination object does NOT have any object-meta
+ self._check_copied_obj(dst_object_name, data, not_in_meta=["src"])
+
+ @test.attr(type='smoke')
+ def test_copy_object_with_x_object_metakey(self):
+ # create source object
+ metadata = {'x-object-meta-src': 'src_value'}
+ src_obj_name, data = self._create_object(metadata)
+
+ # copy source object to destination with x-object-meta-key
+ metadata = {'x-object-meta-test': ''}
+ dst_obj_name, resp = self._copy_object_2d(src_obj_name, metadata)
+
+ self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'COPY')
+
+ expected = {'x-object-meta-test': '',
+ 'x-object-meta-src': 'src_value',
+ 'x-copied-from': self.container_name + "/" + src_obj_name}
+ for key, value in six.iteritems(expected):
+ self.assertIn(key, resp)
+ self.assertEqual(value, resp[key])
+
+ # check destination object
+ self._check_copied_obj(dst_obj_name, data, in_meta=["test", "src"])
+
+ @test.attr(type='smoke')
+ def test_copy_object_with_x_object_meta(self):
+ # create source object
+ metadata = {'x-object-meta-src': 'src_value'}
+ src_obj_name, data = self._create_object(metadata)
+
+ # copy source object to destination with object metadata
+ metadata = {'x-object-meta-test': 'value'}
+ dst_obj_name, resp = self._copy_object_2d(src_obj_name, metadata)
+
+ self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'COPY')
+
+ expected = {'x-object-meta-test': 'value',
+ 'x-object-meta-src': 'src_value',
+ 'x-copied-from': self.container_name + "/" + src_obj_name}
+ for key, value in six.iteritems(expected):
+ self.assertIn(key, resp)
+ self.assertEqual(value, resp[key])
+
+ # check destination object
+ self._check_copied_obj(dst_obj_name, data, in_meta=["test", "src"])
+
@test.attr(type='gate')
def test_object_upload_in_segments(self):
# create object
object_name = data_utils.rand_name(name='LObject')
data = data_utils.arbitrary_string()
segments = 10
- data_segments = [data + str(i) for i in moves.xrange(segments)]
+ data_segments = [data + str(i) for i in six.moves.xrange(segments)]
# uploading segments
- for i in moves.xrange(segments):
+ for i in six.moves.xrange(segments):
resp, _ = self.object_client.create_object_segments(
self.container_name, object_name, i, data_segments[i])
self.assertEqual(resp['status'], '201')
diff --git a/tempest/api/orchestration/base.py b/tempest/api/orchestration/base.py
index 531df2d..cfebc2c 100644
--- a/tempest/api/orchestration/base.py
+++ b/tempest/api/orchestration/base.py
@@ -11,6 +11,7 @@
# under the License.
import os.path
+
import yaml
from tempest import clients
diff --git a/tempest/api/orchestration/stacks/test_neutron_resources.py b/tempest/api/orchestration/stacks/test_neutron_resources.py
index 27c6196..26e3ac6 100644
--- a/tempest/api/orchestration/stacks/test_neutron_resources.py
+++ b/tempest/api/orchestration/stacks/test_neutron_resources.py
@@ -12,6 +12,7 @@
import logging
+
import netaddr
from tempest.api.orchestration import base
@@ -66,16 +67,17 @@
cls.client.wait_for_stack_status(cls.stack_id, 'CREATE_COMPLETE')
_, resources = cls.client.list_resources(cls.stack_identifier)
except exceptions.TimeoutException as e:
- # attempt to log the server console to help with debugging
- # the cause of the server not signalling the waitcondition
- # to heat.
- resp, body = cls.client.get_resource(cls.stack_identifier,
- 'Server')
- server_id = body['physical_resource_id']
- LOG.debug('Console output for %s', server_id)
- resp, output = cls.servers_client.get_console_output(
- server_id, None)
- LOG.debug(output)
+ if CONF.compute_feature_enabled.console_output:
+ # attempt to log the server console to help with debugging
+ # the cause of the server not signalling the waitcondition
+ # to heat.
+ resp, body = cls.client.get_resource(cls.stack_identifier,
+ 'Server')
+ server_id = body['physical_resource_id']
+ LOG.debug('Console output for %s', server_id)
+ resp, output = cls.servers_client.get_console_output(
+ server_id, None)
+ LOG.debug(output)
raise e
cls.test_resources = {}
diff --git a/tempest/api/orchestration/stacks/test_nova_keypair_resources.py b/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
index e22a08b..c6f880b 100644
--- a/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
+++ b/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
@@ -48,7 +48,7 @@
for resource in resources:
cls.test_resources[resource['logical_resource_id']] = resource
- @test.attr(type='slow')
+ @test.attr(type='gate')
def test_created_resources(self):
"""Verifies created keypair resource."""
@@ -56,10 +56,10 @@
ext=self._tpl_type)
resources = [('KeyPairSavePrivate',
nova_keypair_template[self._resource][
- 'KeyPairSavePrivate'][self._type]),
+ 'KeyPairSavePrivate'][self._type]),
('KeyPairDontSavePrivate',
nova_keypair_template[self._resource][
- 'KeyPairDontSavePrivate'][self._type])]
+ 'KeyPairDontSavePrivate'][self._type])]
for resource_name, resource_type in resources:
resource = self.test_resources.get(resource_name, None)
@@ -68,7 +68,7 @@
self.assertEqual(resource_type, resource['resource_type'])
self.assertEqual('CREATE_COMPLETE', resource['resource_status'])
- @test.attr(type='slow')
+ @test.attr(type='gate')
def test_stack_keypairs_output(self):
resp, stack = self.client.get_stack(self.stack_name)
self.assertEqual('200', resp['status'])
diff --git a/tempest/api/orchestration/stacks/test_stacks.py b/tempest/api/orchestration/stacks/test_stacks.py
index 5b45d82..d5e66e8 100644
--- a/tempest/api/orchestration/stacks/test_stacks.py
+++ b/tempest/api/orchestration/stacks/test_stacks.py
@@ -64,3 +64,4 @@
# delete the stack
resp = self.client.delete_stack(stack_identifier)
self.assertEqual('204', resp[0]['status'])
+ self.client.wait_for_stack_status(stack_identifier, 'DELETE_COMPLETE')
diff --git a/tempest/api/orchestration/stacks/test_volumes.py b/tempest/api/orchestration/stacks/test_volumes.py
index d422752..f11ac2a 100644
--- a/tempest/api/orchestration/stacks/test_volumes.py
+++ b/tempest/api/orchestration/stacks/test_volumes.py
@@ -79,8 +79,7 @@
def _cleanup_volume(self, volume_id):
"""Cleanup the volume direct with cinder."""
- resp = self.volumes_client.delete_volume(volume_id)
- self.assertEqual(202, resp[0].status)
+ self.volumes_client.delete_volume(volume_id)
self.volumes_client.wait_for_resource_deletion(volume_id)
@test.attr(type='gate')
diff --git a/tempest/api/queuing/test_queues.py b/tempest/api/queuing/test_queues.py
index e43178a..b340b60 100644
--- a/tempest/api/queuing/test_queues.py
+++ b/tempest/api/queuing/test_queues.py
@@ -14,6 +14,7 @@
# limitations under the License.
import logging
+
from six import moves
from testtools import matchers
diff --git a/tempest/api/telemetry/test_telemetry_notification_api.py b/tempest/api/telemetry/test_telemetry_notification_api.py
index 2a170c7..9b15c51 100644
--- a/tempest/api/telemetry/test_telemetry_notification_api.py
+++ b/tempest/api/telemetry/test_telemetry_notification_api.py
@@ -47,6 +47,7 @@
@test.services("image")
@testtools.skipIf(not CONF.image_feature_enabled.api_v1,
"Glance api v1 is disabled")
+ @test.skip_because(bug='1351627')
def test_check_glance_v1_notifications(self):
_, body = self.create_image(self.image_client)
self.image_client.update_image(body['id'], data='data')
@@ -62,6 +63,7 @@
@test.services("image")
@testtools.skipIf(not CONF.image_feature_enabled.api_v2,
"Glance api v2 is disabled")
+ @test.skip_because(bug='1351627')
def test_check_glance_v2_notifications(self):
_, body = self.create_image(self.image_client_v2)
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index d451517..f3b1ad5 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -61,11 +61,11 @@
extra_specs = {spec_key_with_prefix: backend_name_key}
else:
extra_specs = {spec_key_without_prefix: backend_name_key}
- resp, self.type = self.client.create_volume_type(
+ _, self.type = self.client.create_volume_type(
type_name, extra_specs=extra_specs)
self.volume_type_id_list.append(self.type['id'])
- resp, self.volume = self.volume_client.create_volume(
+ _, self.volume = self.volume_client.create_volume(
size=1, display_name=vol_name, volume_type=type_name)
self.volume_client.wait_for_volume_status(
self.volume['id'], 'available')
@@ -130,8 +130,7 @@
# the multi backend feature has been enabled
# if multi-backend is enabled: os-vol-attr:host should be like:
# host@backend_name
- resp, volume = self.volume_client.get_volume(volume_id)
- self.assertEqual(200, resp.status)
+ _, volume = self.volume_client.get_volume(volume_id)
volume1_host = volume['os-vol-host-attr:host']
msg = ("multi-backend reporting incorrect values for volume %s" %
@@ -142,10 +141,10 @@
# this test checks that the two volumes created at setUp don't
# belong to the same backend (if they are, than the
# volume backend distinction is not working properly)
- resp, volume = self.volume_client.get_volume(volume1_id)
+ _, volume = self.volume_client.get_volume(volume1_id)
volume1_host = volume['os-vol-host-attr:host']
- resp, volume = self.volume_client.get_volume(volume2_id)
+ _, volume = self.volume_client.get_volume(volume2_id)
volume2_host = volume['os-vol-host-attr:host']
msg = ("volumes %s and %s were created in the same backend" %
diff --git a/tempest/api/volume/admin/test_snapshots_actions.py b/tempest/api/volume/admin/test_snapshots_actions.py
index 594c703..abbe1e9 100644
--- a/tempest/api/volume/admin/test_snapshots_actions.py
+++ b/tempest/api/volume/admin/test_snapshots_actions.py
@@ -32,14 +32,14 @@
# Create a test shared volume for tests
vol_name = data_utils.rand_name(cls.__name__ + '-Volume-')
- resp_vol, cls.volume = \
+ _, cls.volume = \
cls.volumes_client.create_volume(size=1, display_name=vol_name)
cls.volumes_client.wait_for_volume_status(cls.volume['id'],
'available')
# Create a test shared snapshot for tests
snap_name = data_utils.rand_name(cls.__name__ + '-Snapshot-')
- resp_snap, cls.snapshot = \
+ _, cls.snapshot = \
cls.client.create_snapshot(cls.volume['id'],
display_name=snap_name)
cls.client.wait_for_snapshot_status(cls.snapshot['id'],
@@ -70,12 +70,10 @@
# and force delete temp snapshot
temp_snapshot = self.create_snapshot(self.volume['id'])
if status:
- resp, body = self.admin_snapshots_client.\
+ _, body = self.admin_snapshots_client.\
reset_snapshot_status(temp_snapshot['id'], status)
- self.assertEqual(202, resp.status)
- resp_delete, volume_delete = self.admin_snapshots_client.\
+ _, volume_delete = self.admin_snapshots_client.\
force_delete_snapshot(temp_snapshot['id'])
- self.assertEqual(202, resp_delete.status)
self.client.wait_for_resource_deletion(temp_snapshot['id'])
def _get_progress_alias(self):
@@ -85,12 +83,10 @@
def test_reset_snapshot_status(self):
# Reset snapshot status to creating
status = 'creating'
- resp, body = self.admin_snapshots_client.\
+ _, body = self.admin_snapshots_client.\
reset_snapshot_status(self.snapshot['id'], status)
- self.assertEqual(202, resp.status)
- resp_get, snapshot_get \
+ _, snapshot_get \
= self.admin_snapshots_client.get_snapshot(self.snapshot['id'])
- self.assertEqual(200, resp_get.status)
self.assertEqual(status, snapshot_get['status'])
@test.attr(type='gate')
@@ -104,12 +100,10 @@
progress = '80%'
status = 'error'
progress_alias = self._get_progress_alias()
- resp, body = self.client.update_snapshot_status(self.snapshot['id'],
- status, progress)
- self.assertEqual(202, resp.status)
- resp_get, snapshot_get \
+ _, body = self.client.update_snapshot_status(self.snapshot['id'],
+ status, progress)
+ _, snapshot_get \
= self.admin_snapshots_client.get_snapshot(self.snapshot['id'])
- self.assertEqual(200, resp_get.status)
self.assertEqual(status, snapshot_get['status'])
self.assertEqual(progress, snapshot_get[progress_alias])
diff --git a/tempest/api/volume/admin/test_volume_hosts.py b/tempest/api/volume/admin/test_volume_hosts.py
index 01ba915..017363d 100644
--- a/tempest/api/volume/admin/test_volume_hosts.py
+++ b/tempest/api/volume/admin/test_volume_hosts.py
@@ -22,8 +22,7 @@
@test.attr(type='gate')
def test_list_hosts(self):
- resp, hosts = self.hosts_client.list_hosts()
- self.assertEqual(200, resp.status)
+ _, hosts = self.hosts_client.list_hosts()
self.assertTrue(len(hosts) >= 2, "No. of hosts are < 2,"
"response of list hosts is: % s" % hosts)
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index ecd8836..fa3b667 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -34,30 +34,28 @@
@test.attr(type='gate')
def test_list_quotas(self):
- resp, quotas = self.quotas_client.get_quota_set(self.demo_tenant_id)
- self.assertEqual(200, resp.status)
+ _, quotas = self.quotas_client.get_quota_set(self.demo_tenant_id)
for key in QUOTA_KEYS:
self.assertIn(key, quotas)
@test.attr(type='gate')
def test_list_default_quotas(self):
- resp, quotas = self.quotas_client.get_default_quota_set(
+ _, quotas = self.quotas_client.get_default_quota_set(
self.demo_tenant_id)
- self.assertEqual(200, resp.status)
for key in QUOTA_KEYS:
self.assertIn(key, quotas)
@test.attr(type='gate')
def test_update_all_quota_resources_for_tenant(self):
# Admin can update all the resource quota limits for a tenant
- resp, default_quota_set = self.quotas_client.get_default_quota_set(
+ _, default_quota_set = self.quotas_client.get_default_quota_set(
self.demo_tenant_id)
new_quota_set = {'gigabytes': 1009,
'volumes': 11,
'snapshots': 11}
# Update limits for all quota resources
- resp, quota_set = self.quotas_client.update_quota_set(
+ _, quota_set = self.quotas_client.update_quota_set(
self.demo_tenant_id,
**new_quota_set)
@@ -66,7 +64,6 @@
if k in QUOTA_KEYS)
self.addCleanup(self.quotas_client.update_quota_set,
self.demo_tenant_id, **cleanup_quota_set)
- self.assertEqual(200, resp.status)
# test that the specific values we set are actually in
# the final result. There is nothing here that ensures there
# would be no other values in there.
@@ -74,8 +71,7 @@
@test.attr(type='gate')
def test_show_quota_usage(self):
- resp, quota_usage = self.quotas_client.get_quota_usage(self.adm_tenant)
- self.assertEqual(200, resp.status)
+ _, quota_usage = self.quotas_client.get_quota_usage(self.adm_tenant)
for key in QUOTA_KEYS:
self.assertIn(key, quota_usage)
for usage_key in QUOTA_USAGE_KEYS:
@@ -83,17 +79,16 @@
@test.attr(type='gate')
def test_quota_usage(self):
- resp, quota_usage = self.quotas_client.get_quota_usage(
+ _, quota_usage = self.quotas_client.get_quota_usage(
self.demo_tenant_id)
volume = self.create_volume(size=1)
self.addCleanup(self.admin_volume_client.delete_volume,
volume['id'])
- resp, new_quota_usage = self.quotas_client.get_quota_usage(
+ _, new_quota_usage = self.quotas_client.get_quota_usage(
self.demo_tenant_id)
- self.assertEqual(200, resp.status)
self.assertEqual(quota_usage['volumes']['in_use'] + 1,
new_quota_usage['volumes']['in_use'])
@@ -115,9 +110,7 @@
self.quotas_client.update_quota_set(tenant_id,
volumes=(int(volume_default) + 5))
- resp, _ = self.quotas_client.delete_quota_set(tenant_id)
- self.assertEqual(200, resp.status)
-
+ self.quotas_client.delete_quota_set(tenant_id)
_, quota_set_new = self.quotas_client.get_quota_set(tenant_id)
self.assertEqual(volume_default, quota_set_new['volumes'])
diff --git a/tempest/api/volume/admin/test_volume_quotas_negative.py b/tempest/api/volume/admin/test_volume_quotas_negative.py
index ab88b90..515024f 100644
--- a/tempest/api/volume/admin/test_volume_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_quotas_negative.py
@@ -32,7 +32,7 @@
# NOTE(gfidente): no need to restore original quota set
# after the tests as they only work with tenant isolation.
- resp, quota_set = cls.quotas_client.update_quota_set(
+ _, quota_set = cls.quotas_client.update_quota_set(
cls.demo_tenant_id,
**cls.shared_quota_set)
@@ -63,7 +63,7 @@
**self.shared_quota_set)
new_quota_set = {'gigabytes': 2, 'volumes': 2, 'snapshots': 1}
- resp, quota_set = self.quotas_client.update_quota_set(
+ _, quota_set = self.quotas_client.update_quota_set(
self.demo_tenant_id,
**new_quota_set)
self.assertRaises(exceptions.OverLimit,
@@ -71,7 +71,7 @@
size=1)
new_quota_set = {'gigabytes': 2, 'volumes': 1, 'snapshots': 2}
- resp, quota_set = self.quotas_client.update_quota_set(
+ _, quota_set = self.quotas_client.update_quota_set(
self.demo_tenant_id,
**self.shared_quota_set)
self.assertRaises(exceptions.OverLimit,
diff --git a/tempest/api/volume/admin/test_volume_services.py b/tempest/api/volume/admin/test_volume_services.py
index 012c231..4a68e05 100644
--- a/tempest/api/volume/admin/test_volume_services.py
+++ b/tempest/api/volume/admin/test_volume_services.py
@@ -28,21 +28,19 @@
def setUpClass(cls):
super(VolumesServicesTestJSON, cls).setUpClass()
cls.client = cls.os_adm.volume_services_client
- resp, cls.services = cls.client.list_services()
+ _, cls.services = cls.client.list_services()
cls.host_name = cls.services[0]['host']
cls.binary_name = cls.services[0]['binary']
@test.attr(type='gate')
def test_list_services(self):
- resp, services = self.client.list_services()
- self.assertEqual(200, resp.status)
+ _, services = self.client.list_services()
self.assertNotEqual(0, len(services))
@test.attr(type='gate')
def test_get_service_by_service_binary_name(self):
params = {'binary': self.binary_name}
- resp, services = self.client.list_services(params)
- self.assertEqual(200, resp.status)
+ _, services = self.client.list_services(params)
self.assertNotEqual(0, len(services))
for service in services:
self.assertEqual(self.binary_name, service['binary'])
@@ -53,7 +51,7 @@
service['host'] == self.host_name]
params = {'host': self.host_name}
- resp, services = self.client.list_services(params)
+ _, services = self.client.list_services(params)
# we could have a periodic job checkin between the 2 service
# lookups, so only compare binary lists.
@@ -67,8 +65,7 @@
def test_get_service_by_service_and_host_name(self):
params = {'host': self.host_name, 'binary': self.binary_name}
- resp, services = self.client.list_services(params)
- self.assertEqual(200, resp.status)
+ _, services = self.client.list_services(params)
self.assertEqual(1, len(services))
self.assertEqual(self.host_name, services[0]['host'])
self.assertEqual(self.binary_name, services[0]['binary'])
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 3b8c214..070d38f 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -25,19 +25,16 @@
_interface = "json"
def _delete_volume(self, volume_id):
- resp, _ = self.volumes_client.delete_volume(volume_id)
- self.assertEqual(202, resp.status)
+ self.volumes_client.delete_volume(volume_id)
self.volumes_client.wait_for_resource_deletion(volume_id)
def _delete_volume_type(self, volume_type_id):
- resp, _ = self.client.delete_volume_type(volume_type_id)
- self.assertEqual(202, resp.status)
+ self.client.delete_volume_type(volume_type_id)
@test.attr(type='smoke')
def test_volume_type_list(self):
# List Volume types.
- resp, body = self.client.list_volume_types()
- self.assertEqual(200, resp.status)
+ _, body = self.client.list_volume_types()
self.assertIsInstance(body, list)
@test.attr(type='smoke')
@@ -51,17 +48,15 @@
extra_specs = {"storage_protocol": proto,
"vendor_name": vendor}
body = {}
- resp, body = self.client.create_volume_type(
+ _, body = self.client.create_volume_type(
vol_type_name,
extra_specs=extra_specs)
- self.assertEqual(200, resp.status)
self.assertIn('id', body)
self.addCleanup(self._delete_volume_type, body['id'])
self.assertIn('name', body)
- resp, volume = self.volumes_client.create_volume(
+ _, volume = self.volumes_client.create_volume(
size=1, display_name=vol_name,
volume_type=vol_type_name)
- self.assertEqual(200, resp.status)
self.assertIn('id', volume)
self.addCleanup(self._delete_volume, volume['id'])
self.assertIn('display_name', volume)
@@ -72,8 +67,7 @@
"Field volume id is empty or not found.")
self.volumes_client.wait_for_volume_status(volume['id'],
'available')
- resp, fetched_volume = self.volumes_client.get_volume(volume['id'])
- self.assertEqual(200, resp.status)
+ _, fetched_volume = self.volumes_client.get_volume(volume['id'])
self.assertEqual(vol_name, fetched_volume['display_name'],
'The fetched Volume is different '
'from the created Volume')
@@ -93,10 +87,9 @@
vendor = CONF.volume.vendor_name
extra_specs = {"storage_protocol": proto,
"vendor_name": vendor}
- resp, body = self.client.create_volume_type(
+ _, body = self.client.create_volume_type(
name,
extra_specs=extra_specs)
- self.assertEqual(200, resp.status)
self.assertIn('id', body)
self.addCleanup(self._delete_volume_type, body['id'])
self.assertIn('name', body)
@@ -105,8 +98,7 @@
"to the requested name")
self.assertTrue(body['id'] is not None,
"Field volume_type id is empty or not found.")
- resp, fetched_volume_type = self.client.get_volume_type(body['id'])
- self.assertEqual(200, resp.status)
+ _, fetched_volume_type = self.client.get_volume_type(body['id'])
self.assertEqual(name, fetched_volume_type['name'],
'The fetched Volume_type is different '
'from the created Volume_type')
@@ -123,15 +115,13 @@
provider = "LuksEncryptor"
control_location = "front-end"
name = data_utils.rand_name("volume-type-")
- resp, body = self.client.create_volume_type(name)
- self.assertEqual(200, resp.status)
+ _, body = self.client.create_volume_type(name)
self.addCleanup(self._delete_volume_type, body['id'])
# Create encryption type
- resp, encryption_type = self.client.create_encryption_type(
+ _, encryption_type = self.client.create_encryption_type(
body['id'], provider=provider,
control_location=control_location)
- self.assertEqual(200, resp.status)
self.assertIn('volume_type_id', encryption_type)
self.assertEqual(provider, encryption_type['provider'],
"The created encryption_type provider is not equal "
@@ -141,9 +131,8 @@
"equal to the requested control_location")
# Get encryption type
- resp, fetched_encryption_type = self.client.get_encryption_type(
+ _, fetched_encryption_type = self.client.get_encryption_type(
encryption_type['volume_type_id'])
- self.assertEqual(200, resp.status)
self.assertEqual(provider,
fetched_encryption_type['provider'],
'The fetched encryption_type provider is different '
@@ -154,13 +143,11 @@
'different from the created encryption_type')
# Delete encryption type
- resp, _ = self.client.delete_encryption_type(
+ self.client.delete_encryption_type(
encryption_type['volume_type_id'])
- self.assertEqual(202, resp.status)
resource = {"id": encryption_type['volume_type_id'],
"type": "encryption-type"}
self.client.wait_for_resource_deletion(resource)
- resp, deleted_encryption_type = self.client.get_encryption_type(
+ _, deleted_encryption_type = self.client.get_encryption_type(
encryption_type['volume_type_id'])
- self.assertEqual(200, resp.status)
self.assertEmpty(deleted_encryption_type)
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs.py b/tempest/api/volume/admin/test_volume_types_extra_specs.py
index 06a0b34..c682866 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs.py
@@ -25,7 +25,7 @@
def setUpClass(cls):
super(VolumeTypesExtraSpecsTest, cls).setUpClass()
vol_type_name = data_utils.rand_name('Volume-type-')
- resp, cls.volume_type = cls.client.create_volume_type(vol_type_name)
+ _, cls.volume_type = cls.client.create_volume_type(vol_type_name)
@classmethod
def tearDownClass(cls):
@@ -36,14 +36,12 @@
def test_volume_type_extra_specs_list(self):
# List Volume types extra specs.
extra_specs = {"spec1": "val1"}
- resp, body = self.client.create_volume_type_extra_specs(
+ _, body = self.client.create_volume_type_extra_specs(
self.volume_type['id'], extra_specs)
- self.assertEqual(200, resp.status)
self.assertEqual(extra_specs, body,
"Volume type extra spec incorrectly created")
- resp, body = self.client.list_volume_types_extra_specs(
+ _, body = self.client.list_volume_types_extra_specs(
self.volume_type['id'])
- self.assertEqual(200, resp.status)
self.assertIsInstance(body, dict)
self.assertIn('spec1', body)
@@ -51,18 +49,16 @@
def test_volume_type_extra_specs_update(self):
# Update volume type extra specs
extra_specs = {"spec2": "val1"}
- resp, body = self.client.create_volume_type_extra_specs(
+ _, body = self.client.create_volume_type_extra_specs(
self.volume_type['id'], extra_specs)
- self.assertEqual(200, resp.status)
self.assertEqual(extra_specs, body,
"Volume type extra spec incorrectly created")
extra_spec = {"spec2": "val2"}
- resp, body = self.client.update_volume_type_extra_specs(
+ _, body = self.client.update_volume_type_extra_specs(
self.volume_type['id'],
extra_spec.keys()[0],
extra_spec)
- self.assertEqual(200, resp.status)
self.assertIn('spec2', body)
self.assertEqual(extra_spec['spec2'], body['spec2'],
"Volume type extra spec incorrectly updated")
@@ -71,21 +67,18 @@
def test_volume_type_extra_spec_create_get_delete(self):
# Create/Get/Delete volume type extra spec.
extra_specs = {"spec3": "val1"}
- resp, body = self.client.create_volume_type_extra_specs(
+ _, body = self.client.create_volume_type_extra_specs(
self.volume_type['id'],
extra_specs)
- self.assertEqual(200, resp.status)
self.assertEqual(extra_specs, body,
"Volume type extra spec incorrectly created")
- resp, _ = self.client.get_volume_type_extra_specs(
+ self.client.get_volume_type_extra_specs(
self.volume_type['id'],
extra_specs.keys()[0])
- self.assertEqual(200, resp.status)
self.assertEqual(extra_specs, body,
"Volume type extra spec incorrectly fetched")
- resp, _ = self.client.delete_volume_type_extra_specs(
+ self.client.delete_volume_type_extra_specs(
self.volume_type['id'],
extra_specs.keys()[0])
- self.assertEqual(202, resp.status)
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
index da421dc..ff4f113 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
@@ -29,7 +29,7 @@
super(ExtraSpecsNegativeTest, cls).setUpClass()
vol_type_name = data_utils.rand_name('Volume-type-')
cls.extra_specs = {"spec1": "val1"}
- resp, cls.volume_type = cls.client.create_volume_type(
+ _, cls.volume_type = cls.client.create_volume_type(
vol_type_name,
extra_specs=cls.extra_specs)
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index 008f739..d6db1df 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -33,8 +33,8 @@
# Create a test shared volume for tests
vol_name = utils.rand_name(cls.__name__ + '-Volume-')
- resp, cls.volume = cls.client.create_volume(size=1,
- display_name=vol_name)
+ _, cls.volume = cls.client.create_volume(size=1,
+ display_name=vol_name)
cls.client.wait_for_volume_status(cls.volume['id'], 'available')
@classmethod
@@ -47,9 +47,9 @@
def _reset_volume_status(self, volume_id, status):
# Reset the volume status
- resp, body = self.admin_volume_client.reset_volume_status(volume_id,
- status)
- return resp, body
+ _, body = self.admin_volume_client.reset_volume_status(volume_id,
+ status)
+ return _, body
def tearDown(self):
# Set volume's status to available after test
@@ -59,8 +59,8 @@
def _create_temp_volume(self):
# Create a temp volume for force delete tests
vol_name = utils.rand_name('Volume')
- resp, temp_volume = self.client.create_volume(size=1,
- display_name=vol_name)
+ _, temp_volume = self.client.create_volume(size=1,
+ display_name=vol_name)
self.client.wait_for_volume_status(temp_volume['id'], 'available')
return temp_volume
@@ -69,19 +69,16 @@
# Create volume, reset volume status, and force delete temp volume
temp_volume = self._create_temp_volume()
if status:
- resp, body = self._reset_volume_status(temp_volume['id'], status)
- self.assertEqual(202, resp.status)
- resp_delete, volume_delete = self.admin_volume_client.\
+ _, body = self._reset_volume_status(temp_volume['id'], status)
+ _, volume_delete = self.admin_volume_client.\
force_delete_volume(temp_volume['id'])
- self.assertEqual(202, resp_delete.status)
self.client.wait_for_resource_deletion(temp_volume['id'])
@test.attr(type='gate')
def test_volume_reset_status(self):
# test volume reset status : available->error->available
- resp, body = self._reset_volume_status(self.volume['id'], 'error')
- self.assertEqual(202, resp.status)
- resp_get, volume_get = self.admin_volume_client.get_volume(
+ _, body = self._reset_volume_status(self.volume['id'], 'error')
+ _, volume_get = self.admin_volume_client.get_volume(
self.volume['id'])
self.assertEqual('error', volume_get['status'])
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index f9fbe18..3699e9c 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -43,9 +43,8 @@
# Create backup
backup_name = data_utils.rand_name('Backup')
create_backup = self.backups_adm_client.create_backup
- resp, backup = create_backup(self.volume['id'],
- name=backup_name)
- self.assertEqual(202, resp.status)
+ _, backup = create_backup(self.volume['id'],
+ name=backup_name)
self.addCleanup(self.backups_adm_client.delete_backup,
backup['id'])
self.assertEqual(backup_name, backup['name'])
@@ -55,19 +54,16 @@
'available')
# Get a given backup
- resp, backup = self.backups_adm_client.get_backup(backup['id'])
- self.assertEqual(200, resp.status)
+ _, backup = self.backups_adm_client.get_backup(backup['id'])
self.assertEqual(backup_name, backup['name'])
# Get all backups with detail
- resp, backups = self.backups_adm_client.list_backups_with_detail()
- self.assertEqual(200, resp.status)
+ _, backups = self.backups_adm_client.list_backups_with_detail()
self.assertIn((backup['name'], backup['id']),
[(m['name'], m['id']) for m in backups])
# Restore backup
- resp, restore = self.backups_adm_client.restore_backup(backup['id'])
- self.assertEqual(202, resp.status)
+ _, restore = self.backups_adm_client.restore_backup(backup['id'])
# Delete backup
self.addCleanup(self.volumes_adm_client.delete_volume,
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index c5be1f3..3cd0827 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -61,12 +61,21 @@
cls.volumes_extension_client = cls.os.volumes_extension_client
cls.availability_zone_client = (
cls.os.volume_availability_zone_client)
+ # Special fields and resp code for cinder v1
+ cls.special_fields = {'name_field': 'display_name',
+ 'descrip_field': 'display_description'}
elif cls._api_version == 2:
if not CONF.volume_feature_enabled.api_v2:
msg = "Volume API v2 is disabled"
raise cls.skipException(msg)
cls.volumes_client = cls.os.volumes_v2_client
+ cls.volumes_extension_client = cls.os.volumes_v2_extension_client
+ cls.availability_zone_client = (
+ cls.os.volume_v2_availability_zone_client)
+ # Special fields and resp code for cinder v2
+ cls.special_fields = {'name_field': 'name',
+ 'descrip_field': 'description'}
else:
msg = ("Invalid Cinder API version (%s)" % cls._api_version)
@@ -82,15 +91,13 @@
@classmethod
def create_volume(cls, size=1, **kwargs):
"""Wrapper utility that returns a test volume."""
- vol_name = data_utils.rand_name('Volume')
- if cls._api_version == 1:
- resp, volume = cls.volumes_client.create_volume(
- size, display_name=vol_name, **kwargs)
- assert 200 == resp.status
- elif cls._api_version == 2:
- resp, volume = cls.volumes_client.create_volume(
- size, name=vol_name, **kwargs)
- assert 202 == resp.status
+ name = data_utils.rand_name('Volume')
+
+ name_field = cls.special_fields['name_field']
+
+ kwargs[name_field] = name
+ _, volume = cls.volumes_client.create_volume(size, **kwargs)
+
cls.volumes.append(volume)
cls.volumes_client.wait_for_volume_status(volume['id'], 'available')
return volume
@@ -98,9 +105,8 @@
@classmethod
def create_snapshot(cls, volume_id=1, **kwargs):
"""Wrapper utility that returns a test snapshot."""
- resp, snapshot = cls.snapshots_client.create_snapshot(volume_id,
- **kwargs)
- assert 200 == resp.status
+ _, snapshot = cls.snapshots_client.create_snapshot(volume_id,
+ **kwargs)
cls.snapshots.append(snapshot)
cls.snapshots_client.wait_for_snapshot_status(snapshot['id'],
'available')
diff --git a/tempest/api/volume/test_availability_zone.py b/tempest/api/volume/test_availability_zone.py
index fe8f96e..c026f71 100644
--- a/tempest/api/volume/test_availability_zone.py
+++ b/tempest/api/volume/test_availability_zone.py
@@ -17,25 +17,31 @@
from tempest import test
-class AvailabilityZoneTestJSON(base.BaseVolumeV1Test):
+class AvailabilityZoneV2TestJSON(base.BaseVolumeTest):
"""
- Tests Availability Zone API List
+ Tests Availability Zone V2 API List
"""
- _interface = 'json'
@classmethod
def setUpClass(cls):
- super(AvailabilityZoneTestJSON, cls).setUpClass()
+ super(AvailabilityZoneV2TestJSON, cls).setUpClass()
cls.client = cls.availability_zone_client
@test.attr(type='gate')
def test_get_availability_zone_list(self):
# List of availability zone
- resp, availability_zone = self.client.get_availability_zone_list()
- self.assertEqual(200, resp.status)
+ _, availability_zone = self.client.get_availability_zone_list()
self.assertTrue(len(availability_zone) > 0)
-class AvailabilityZoneTestXML(AvailabilityZoneTestJSON):
+class AvailabilityZoneV2TestXML(AvailabilityZoneV2TestJSON):
+ _interface = 'xml'
+
+
+class AvailabilityZoneV1TestJSON(AvailabilityZoneV2TestJSON):
+ _api_version = 1
+
+
+class AvailabilityZoneV1TestXML(AvailabilityZoneV1TestJSON):
_interface = 'xml'
diff --git a/tempest/api/volume/test_extensions.py b/tempest/api/volume/test_extensions.py
index ce019a2..4fc6ee4 100644
--- a/tempest/api/volume/test_extensions.py
+++ b/tempest/api/volume/test_extensions.py
@@ -25,14 +25,12 @@
LOG = logging.getLogger(__name__)
-class ExtensionsTestJSON(base.BaseVolumeV1Test):
- _interface = 'json'
+class ExtensionsV2TestJSON(base.BaseVolumeTest):
@test.attr(type='gate')
def test_list_extensions(self):
# List of all extensions
- resp, extensions = self.volumes_extension_client.list_extensions()
- self.assertEqual(200, resp.status)
+ _, extensions = self.volumes_extension_client.list_extensions()
if len(CONF.volume_feature_enabled.api_extensions) == 0:
raise self.skipException('There are not any extensions configured')
extension_list = [extension.get('alias') for extension in extensions]
@@ -46,5 +44,13 @@
raise self.skipException('There are not any extensions configured')
-class ExtensionsTestXML(ExtensionsTestJSON):
+class ExtensionsV2TestXML(ExtensionsV2TestJSON):
+ _interface = 'xml'
+
+
+class ExtensionsV1TestJSON(ExtensionsV2TestJSON):
+ _api_version = 1
+
+
+class ExtensionsV1TestXML(ExtensionsV1TestJSON):
_interface = 'xml'
diff --git a/tempest/api/volume/test_snapshot_metadata.py b/tempest/api/volume/test_snapshot_metadata.py
index d2c4ab7..94ba095 100644
--- a/tempest/api/volume/test_snapshot_metadata.py
+++ b/tempest/api/volume/test_snapshot_metadata.py
@@ -44,19 +44,15 @@
"key3": "value3"}
expected = {"key2": "value2",
"key3": "value3"}
- resp, body = self.client.create_snapshot_metadata(self.snapshot_id,
- metadata)
- self.assertEqual(200, resp.status)
+ _, body = self.client.create_snapshot_metadata(self.snapshot_id,
+ metadata)
# Get the metadata of the snapshot
- resp, body = self.client.get_snapshot_metadata(self.snapshot_id)
- self.assertEqual(200, resp.status)
+ _, body = self.client.get_snapshot_metadata(self.snapshot_id)
self.assertEqual(metadata, body)
# Delete one item metadata of the snapshot
- resp, body = self.client.delete_snapshot_metadata_item(
- self.snapshot_id,
- "key1")
- self.assertEqual(200, resp.status)
- resp, body = self.client.get_snapshot_metadata(self.snapshot_id)
+ self.client.delete_snapshot_metadata_item(
+ self.snapshot_id, "key1")
+ _, body = self.client.get_snapshot_metadata(self.snapshot_id)
self.assertEqual(expected, body)
@test.attr(type='gate')
@@ -68,21 +64,16 @@
update = {"key3": "value3_update",
"key4": "value4"}
# Create metadata for the snapshot
- resp, body = self.client.create_snapshot_metadata(self.snapshot_id,
- metadata)
- self.assertEqual(200, resp.status)
+ _, body = self.client.create_snapshot_metadata(self.snapshot_id,
+ metadata)
# Get the metadata of the snapshot
- resp, body = self.client.get_snapshot_metadata(self.snapshot_id)
- self.assertEqual(200, resp.status)
+ _, body = self.client.get_snapshot_metadata(self.snapshot_id)
self.assertEqual(metadata, body)
# Update metadata item
- resp, body = self.client.update_snapshot_metadata(
- self.snapshot_id,
- update)
- self.assertEqual(200, resp.status)
+ _, body = self.client.update_snapshot_metadata(
+ self.snapshot_id, update)
# Get the metadata of the snapshot
- resp, body = self.client.get_snapshot_metadata(self.snapshot_id)
- self.assertEqual(200, resp.status)
+ _, body = self.client.get_snapshot_metadata(self.snapshot_id)
self.assertEqual(update, body)
@test.attr(type='gate')
@@ -96,21 +87,16 @@
"key2": "value2",
"key3": "value3_update"}
# Create metadata for the snapshot
- resp, body = self.client.create_snapshot_metadata(self.snapshot_id,
- metadata)
- self.assertEqual(200, resp.status)
+ _, body = self.client.create_snapshot_metadata(self.snapshot_id,
+ metadata)
# Get the metadata of the snapshot
- resp, body = self.client.get_snapshot_metadata(self.snapshot_id)
+ _, body = self.client.get_snapshot_metadata(self.snapshot_id)
self.assertEqual(metadata, body)
# Update metadata item
- resp, body = self.client.update_snapshot_metadata_item(
- self.snapshot_id,
- "key3",
- update_item)
- self.assertEqual(200, resp.status)
+ _, body = self.client.update_snapshot_metadata_item(
+ self.snapshot_id, "key3", update_item)
# Get the metadata of the snapshot
- resp, body = self.client.get_snapshot_metadata(self.snapshot_id)
- self.assertEqual(200, resp.status)
+ _, body = self.client.get_snapshot_metadata(self.snapshot_id)
self.assertEqual(expect, body)
diff --git a/tempest/api/volume/test_volume_metadata.py b/tempest/api/volume/test_volume_metadata.py
index 0d57d47..ac760aa 100644
--- a/tempest/api/volume/test_volume_metadata.py
+++ b/tempest/api/volume/test_volume_metadata.py
@@ -19,13 +19,12 @@
from tempest import test
-class VolumeMetadataTest(base.BaseVolumeV1Test):
- _interface = "json"
+class VolumesV2MetadataTest(base.BaseVolumeTest):
@classmethod
@test.safe_setup
def setUpClass(cls):
- super(VolumeMetadataTest, cls).setUpClass()
+ super(VolumesV2MetadataTest, cls).setUpClass()
# Create a volume
cls.volume = cls.create_volume()
cls.volume_id = cls.volume['id']
@@ -33,7 +32,7 @@
def tearDown(self):
# Update the metadata to {}
self.volumes_client.update_volume_metadata(self.volume_id, {})
- super(VolumeMetadataTest, self).tearDown()
+ super(VolumesV2MetadataTest, self).tearDown()
@test.attr(type='gate')
def test_create_get_delete_volume_metadata(self):
@@ -43,19 +42,15 @@
"key3": "value3",
"key4": "<value&special_chars>"}
- rsp, body = self.volumes_client.create_volume_metadata(self.volume_id,
- metadata)
- self.assertEqual(200, rsp.status)
+ _, body = self.volumes_client.create_volume_metadata(self.volume_id,
+ metadata)
# Get the metadata of the volume
- resp, body = self.volumes_client.get_volume_metadata(self.volume_id)
- self.assertEqual(200, resp.status)
+ _, body = self.volumes_client.get_volume_metadata(self.volume_id)
self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
# Delete one item metadata of the volume
- rsp, body = self.volumes_client.delete_volume_metadata_item(
- self.volume_id,
- "key1")
- self.assertEqual(200, rsp.status)
- resp, body = self.volumes_client.get_volume_metadata(self.volume_id)
+ self.volumes_client.delete_volume_metadata_item(
+ self.volume_id, "key1")
+ _, body = self.volumes_client.get_volume_metadata(self.volume_id)
self.assertNotIn("key1", body)
del metadata["key1"]
self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
@@ -71,22 +66,16 @@
"key1": "value1_update"}
# Create metadata for the volume
- resp, body = self.volumes_client.create_volume_metadata(
- self.volume_id,
- metadata)
- self.assertEqual(200, resp.status)
+ _, body = self.volumes_client.create_volume_metadata(
+ self.volume_id, metadata)
# Get the metadata of the volume
- resp, body = self.volumes_client.get_volume_metadata(self.volume_id)
- self.assertEqual(200, resp.status)
+ _, body = self.volumes_client.get_volume_metadata(self.volume_id)
self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
# Update metadata
- resp, body = self.volumes_client.update_volume_metadata(
- self.volume_id,
- update)
- self.assertEqual(200, resp.status)
+ _, body = self.volumes_client.update_volume_metadata(
+ self.volume_id, update)
# Get the metadata of the volume
- resp, body = self.volumes_client.get_volume_metadata(self.volume_id)
- self.assertEqual(200, resp.status)
+ _, body = self.volumes_client.get_volume_metadata(self.volume_id)
self.assertThat(body.items(), matchers.ContainsAll(update.items()))
@test.attr(type='gate')
@@ -100,22 +89,24 @@
"key2": "value2",
"key3": "value3_update"}
# Create metadata for the volume
- resp, body = self.volumes_client.create_volume_metadata(
- self.volume_id,
- metadata)
- self.assertEqual(200, resp.status)
+ _, body = self.volumes_client.create_volume_metadata(
+ self.volume_id, metadata)
self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
# Update metadata item
- resp, body = self.volumes_client.update_volume_metadata_item(
- self.volume_id,
- "key3",
- update_item)
- self.assertEqual(200, resp.status)
+ _, body = self.volumes_client.update_volume_metadata_item(
+ self.volume_id, "key3", update_item)
# Get the metadata of the volume
- resp, body = self.volumes_client.get_volume_metadata(self.volume_id)
- self.assertEqual(200, resp.status)
+ _, body = self.volumes_client.get_volume_metadata(self.volume_id)
self.assertThat(body.items(), matchers.ContainsAll(expect.items()))
-class VolumeMetadataTestXML(VolumeMetadataTest):
+class VolumesV2MetadataTestXML(VolumesV2MetadataTest):
+ _interface = "xml"
+
+
+class VolumesV1MetadataTest(VolumesV2MetadataTest):
+ _api_version = 1
+
+
+class VolumesV1MetadataTestXML(VolumesV1MetadataTest):
_interface = "xml"
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
index 82d1364..4a6ba03 100644
--- a/tempest/api/volume/test_volume_transfers.py
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -23,12 +23,11 @@
CONF = config.CONF
-class VolumesTransfersTest(base.BaseVolumeV1Test):
- _interface = "json"
+class VolumesV2TransfersTest(base.BaseVolumeTest):
@classmethod
def setUpClass(cls):
- super(VolumesTransfersTest, cls).setUpClass()
+ super(VolumesV2TransfersTest, cls).setUpClass()
# Add another tenant to test volume-transfer
if CONF.compute.allow_tenant_isolation:
@@ -48,8 +47,7 @@
def _delete_volume(self, volume_id):
# Delete the specified volume using admin creds
- resp, _ = self.adm_client.delete_volume(volume_id)
- self.assertEqual(202, resp.status)
+ self.adm_client.delete_volume(volume_id)
self.adm_client.wait_for_resource_deletion(volume_id)
@test.attr(type='gate')
@@ -59,28 +57,24 @@
self.addCleanup(self._delete_volume, volume['id'])
# Create a volume transfer
- resp, transfer = self.client.create_volume_transfer(volume['id'])
- self.assertEqual(202, resp.status)
+ _, transfer = self.client.create_volume_transfer(volume['id'])
transfer_id = transfer['id']
auth_key = transfer['auth_key']
self.client.wait_for_volume_status(volume['id'],
'awaiting-transfer')
# Get a volume transfer
- resp, body = self.client.get_volume_transfer(transfer_id)
- self.assertEqual(200, resp.status)
+ _, body = self.client.get_volume_transfer(transfer_id)
self.assertEqual(volume['id'], body['volume_id'])
# List volume transfers, the result should be greater than
# or equal to 1
- resp, body = self.client.list_volume_transfers()
- self.assertEqual(200, resp.status)
+ _, body = self.client.list_volume_transfers()
self.assertThat(len(body), matchers.GreaterThan(0))
# Accept a volume transfer by alt_tenant
- resp, body = self.alt_client.accept_volume_transfer(transfer_id,
- auth_key)
- self.assertEqual(202, resp.status)
+ _, body = self.alt_client.accept_volume_transfer(transfer_id,
+ auth_key)
self.alt_client.wait_for_volume_status(volume['id'], 'available')
def test_create_list_delete_volume_transfer(self):
@@ -89,15 +83,13 @@
self.addCleanup(self._delete_volume, volume['id'])
# Create a volume transfer
- resp, body = self.client.create_volume_transfer(volume['id'])
- self.assertEqual(202, resp.status)
+ _, body = self.client.create_volume_transfer(volume['id'])
transfer_id = body['id']
self.client.wait_for_volume_status(volume['id'],
'awaiting-transfer')
# List all volume transfers (looking for the one we created)
- resp, body = self.client.list_volume_transfers()
- self.assertEqual(200, resp.status)
+ _, body = self.client.list_volume_transfers()
for transfer in body:
if volume['id'] == transfer['volume_id']:
break
@@ -105,10 +97,17 @@
self.fail('Transfer not found for volume %s' % volume['id'])
# Delete a volume transfer
- resp, body = self.client.delete_volume_transfer(transfer_id)
- self.assertEqual(202, resp.status)
+ self.client.delete_volume_transfer(transfer_id)
self.client.wait_for_volume_status(volume['id'], 'available')
-class VolumesTransfersTestXML(VolumesTransfersTest):
+class VolumesV2TransfersTestXML(VolumesV2TransfersTest):
+ _interface = "xml"
+
+
+class VolumesV1TransfersTest(VolumesV2TransfersTest):
+ _api_version = 1
+
+
+class VolumesV1TransfersTestXML(VolumesV1TransfersTest):
_interface = "xml"
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index cfab0bd..c87878d 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -21,13 +21,12 @@
CONF = config.CONF
-class VolumesActionsTest(base.BaseVolumeV1Test):
- _interface = "json"
+class VolumesV2ActionsTest(base.BaseVolumeTest):
@classmethod
@test.safe_setup
def setUpClass(cls):
- super(VolumesActionsTest, cls).setUpClass()
+ super(VolumesV2ActionsTest, cls).setUpClass()
cls.client = cls.volumes_client
cls.image_client = cls.os.image_client
@@ -41,13 +40,17 @@
# Create a test shared volume for attach/detach tests
cls.volume = cls.create_volume()
+ def _delete_image_with_wait(self, image_id):
+ self.image_client.delete_image(image_id)
+ self.image_client.wait_for_resource_deletion(image_id)
+
@classmethod
def tearDownClass(cls):
# Delete the test instance
cls.servers_client.delete_server(cls.server['id'])
cls.servers_client.wait_for_server_termination(cls.server['id'])
- super(VolumesActionsTest, cls).tearDownClass()
+ super(VolumesV2ActionsTest, cls).tearDownClass()
@test.stresstest(class_setup_per='process')
@test.attr(type='smoke')
@@ -55,13 +58,11 @@
def test_attach_detach_volume_to_instance(self):
# Volume is attached and detached successfully from an instance
mountpoint = '/dev/vdc'
- resp, body = self.client.attach_volume(self.volume['id'],
- self.server['id'],
- mountpoint)
- self.assertEqual(202, resp.status)
+ _, body = self.client.attach_volume(self.volume['id'],
+ self.server['id'],
+ mountpoint)
self.client.wait_for_volume_status(self.volume['id'], 'in-use')
- resp, body = self.client.detach_volume(self.volume['id'])
- self.assertEqual(202, resp.status)
+ _, body = self.client.detach_volume(self.volume['id'])
self.client.wait_for_volume_status(self.volume['id'], 'available')
@test.stresstest(class_setup_per='process')
@@ -70,10 +71,9 @@
def test_get_volume_attachment(self):
# Verify that a volume's attachment information is retrieved
mountpoint = '/dev/vdc'
- resp, body = self.client.attach_volume(self.volume['id'],
- self.server['id'],
- mountpoint)
- self.assertEqual(202, resp.status)
+ _, body = self.client.attach_volume(self.volume['id'],
+ self.server['id'],
+ mountpoint)
self.client.wait_for_volume_status(self.volume['id'], 'in-use')
# NOTE(gfidente): added in reverse order because functions will be
# called in reverse order to the order they are added (LIFO)
@@ -81,8 +81,7 @@
self.volume['id'],
'available')
self.addCleanup(self.client.detach_volume, self.volume['id'])
- resp, volume = self.client.get_volume(self.volume['id'])
- self.assertEqual(200, resp.status)
+ _, volume = self.client.get_volume(self.volume['id'])
self.assertIn('attachments', volume)
attachment = self.client.get_attachment_from_volume(volume)
self.assertEqual(mountpoint, attachment['device'])
@@ -98,41 +97,25 @@
# there is no way to delete it from Cinder, so we delete it from Glance
# using the Glance image_client and from Cinder via tearDownClass.
image_name = data_utils.rand_name('Image-')
- resp, body = self.client.upload_volume(self.volume['id'],
- image_name,
- CONF.volume.disk_format)
+ _, body = self.client.upload_volume(self.volume['id'],
+ image_name,
+ CONF.volume.disk_format)
image_id = body["image_id"]
self.addCleanup(self.image_client.delete_image, image_id)
- self.assertEqual(202, resp.status)
self.image_client.wait_for_image_status(image_id, 'active')
self.client.wait_for_volume_status(self.volume['id'], 'available')
@test.attr(type='gate')
- def test_volume_extend(self):
- # Extend Volume Test.
- extend_size = int(self.volume['size']) + 1
- resp, body = self.client.extend_volume(self.volume['id'], extend_size)
- self.assertEqual(202, resp.status)
- self.client.wait_for_volume_status(self.volume['id'], 'available')
- resp, volume = self.client.get_volume(self.volume['id'])
- self.assertEqual(200, resp.status)
- self.assertEqual(int(volume['size']), extend_size)
-
- @test.attr(type='gate')
def test_reserve_unreserve_volume(self):
# Mark volume as reserved.
- resp, body = self.client.reserve_volume(self.volume['id'])
- self.assertEqual(202, resp.status)
+ _, body = self.client.reserve_volume(self.volume['id'])
# To get the volume info
- resp, body = self.client.get_volume(self.volume['id'])
- self.assertEqual(200, resp.status)
+ _, body = self.client.get_volume(self.volume['id'])
self.assertIn('attaching', body['status'])
# Unmark volume as reserved.
- resp, body = self.client.unreserve_volume(self.volume['id'])
- self.assertEqual(202, resp.status)
+ _, body = self.client.unreserve_volume(self.volume['id'])
# To get the volume info
- resp, body = self.client.get_volume(self.volume['id'])
- self.assertEqual(200, resp.status)
+ _, body = self.client.get_volume(self.volume['id'])
self.assertIn('available', body['status'])
def _is_true(self, val):
@@ -142,28 +125,31 @@
def test_volume_readonly_update(self):
# Update volume readonly true
readonly = True
- resp, body = self.client.update_volume_readonly(self.volume['id'],
- readonly)
- self.assertEqual(202, resp.status)
-
+ _, body = self.client.update_volume_readonly(self.volume['id'],
+ readonly)
# Get Volume information
- resp, fetched_volume = self.client.get_volume(self.volume['id'])
+ _, fetched_volume = self.client.get_volume(self.volume['id'])
bool_flag = self._is_true(fetched_volume['metadata']['readonly'])
- self.assertEqual(200, resp.status)
self.assertEqual(True, bool_flag)
# Update volume readonly false
readonly = False
- resp, body = self.client.update_volume_readonly(self.volume['id'],
- readonly)
- self.assertEqual(202, resp.status)
+ _, body = self.client.update_volume_readonly(self.volume['id'],
+ readonly)
# Get Volume information
- resp, fetched_volume = self.client.get_volume(self.volume['id'])
+ _, fetched_volume = self.client.get_volume(self.volume['id'])
bool_flag = self._is_true(fetched_volume['metadata']['readonly'])
- self.assertEqual(200, resp.status)
self.assertEqual(False, bool_flag)
-class VolumesActionsTestXML(VolumesActionsTest):
+class VolumesV2ActionsTestXML(VolumesV2ActionsTest):
+ _interface = "xml"
+
+
+class VolumesV1ActionsTest(VolumesV2ActionsTest):
+ _api_version = 1
+
+
+class VolumesV1ActionsTestXML(VolumesV1ActionsTest):
_interface = "xml"
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
new file mode 100644
index 0000000..c9e80aa
--- /dev/null
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -0,0 +1,51 @@
+# 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.
+
+from tempest.api.volume import base
+from tempest import config
+from tempest import test
+
+CONF = config.CONF
+
+
+class VolumesV2ExtendTest(base.BaseVolumeTest):
+
+ @classmethod
+ @test.safe_setup
+ def setUpClass(cls):
+ super(VolumesV2ExtendTest, cls).setUpClass()
+ cls.client = cls.volumes_client
+
+ @test.attr(type='gate')
+ def test_volume_extend(self):
+ # Extend Volume Test.
+ self.volume = self.create_volume()
+ extend_size = int(self.volume['size']) + 1
+ _, body = self.client.extend_volume(self.volume['id'], extend_size)
+ self.client.wait_for_volume_status(self.volume['id'], 'available')
+ _, volume = self.client.get_volume(self.volume['id'])
+ self.assertEqual(int(volume['size']), extend_size)
+
+
+class VolumesV2ExtendTestXML(VolumesV2ExtendTest):
+ _interface = "xml"
+
+
+class VolumesV1ExtendTest(VolumesV2ExtendTest):
+ _api_version = 1
+
+
+class VolumesV1ExtendTestXML(VolumesV1ExtendTest):
+ _interface = "xml"
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 2745b95..a346a17 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -23,17 +23,18 @@
CONF = config.CONF
-class VolumesGetTest(base.BaseVolumeV1Test):
- _interface = "json"
+class VolumesV2GetTest(base.BaseVolumeTest):
@classmethod
def setUpClass(cls):
- super(VolumesGetTest, cls).setUpClass()
+ super(VolumesV2GetTest, cls).setUpClass()
cls.client = cls.volumes_client
+ cls.name_field = cls.special_fields['name_field']
+ cls.descrip_field = cls.special_fields['descrip_field']
+
def _delete_volume(self, volume_id):
- resp, _ = self.client.delete_volume(volume_id)
- self.assertEqual(202, resp.status)
+ self.client.delete_volume(volume_id)
self.client.wait_for_resource_deletion(volume_id)
def _is_true(self, val):
@@ -51,24 +52,22 @@
v_name = data_utils.rand_name('Volume')
metadata = {'Type': 'Test'}
# Create a volume
- resp, volume = self.client.create_volume(display_name=v_name,
- metadata=metadata,
- **kwargs)
- self.assertEqual(200, resp.status)
+ kwargs[self.name_field] = v_name
+ kwargs['metadata'] = metadata
+ _, volume = self.client.create_volume(**kwargs)
self.assertIn('id', volume)
self.addCleanup(self._delete_volume, volume['id'])
- self.assertIn('display_name', volume)
- self.assertEqual(volume['display_name'], v_name,
+ self.client.wait_for_volume_status(volume['id'], 'available')
+ self.assertIn(self.name_field, volume)
+ self.assertEqual(volume[self.name_field], v_name,
"The created volume name is not equal "
"to the requested name")
self.assertTrue(volume['id'] is not None,
"Field volume id is empty or not found.")
- self.client.wait_for_volume_status(volume['id'], 'available')
# Get Volume information
- resp, fetched_volume = self.client.get_volume(volume['id'])
- self.assertEqual(200, resp.status)
+ _, fetched_volume = self.client.get_volume(volume['id'])
self.assertEqual(v_name,
- fetched_volume['display_name'],
+ fetched_volume[self.name_field],
'The fetched Volume name is different '
'from the created Volume')
self.assertEqual(volume['id'],
@@ -90,27 +89,22 @@
# Update Volume
# Test volume update when display_name is same with original value
- resp, update_volume = \
- self.client.update_volume(volume['id'],
- display_name=v_name)
- self.assertEqual(200, resp.status)
+ params = {self.name_field: v_name}
+ _, update_volume = self.client.update_volume(volume['id'], **params)
# Test volume update when display_name is new
new_v_name = data_utils.rand_name('new-Volume')
new_desc = 'This is the new description of volume'
- resp, update_volume = \
- self.client.update_volume(volume['id'],
- display_name=new_v_name,
- display_description=new_desc)
+ params = {self.name_field: new_v_name,
+ self.descrip_field: new_desc}
+ _, update_volume = self.client.update_volume(volume['id'], **params)
# Assert response body for update_volume method
- self.assertEqual(200, resp.status)
- self.assertEqual(new_v_name, update_volume['display_name'])
- self.assertEqual(new_desc, update_volume['display_description'])
+ self.assertEqual(new_v_name, update_volume[self.name_field])
+ self.assertEqual(new_desc, update_volume[self.descrip_field])
# Assert response body for get_volume method
- resp, updated_volume = self.client.get_volume(volume['id'])
- self.assertEqual(200, resp.status)
+ _, updated_volume = self.client.get_volume(volume['id'])
self.assertEqual(volume['id'], updated_volume['id'])
- self.assertEqual(new_v_name, updated_volume['display_name'])
- self.assertEqual(new_desc, updated_volume['display_description'])
+ self.assertEqual(new_v_name, updated_volume[self.name_field])
+ self.assertEqual(new_desc, updated_volume[self.descrip_field])
self.assertThat(updated_volume['metadata'].items(),
matchers.ContainsAll(metadata.items()),
'The fetched Volume metadata misses data '
@@ -120,21 +114,17 @@
# then test volume update if display_name is duplicated
new_volume = {}
new_v_desc = data_utils.rand_name('@#$%^* description')
- resp, new_volume = \
- self.client.create_volume(
- size=1,
- display_description=new_v_desc,
- availability_zone=volume['availability_zone'])
- self.assertEqual(200, resp.status)
+ params = {self.descrip_field: new_v_desc,
+ 'availability_zone': volume['availability_zone']}
+ _, new_volume = self.client.create_volume(size=1, **params)
self.assertIn('id', new_volume)
self.addCleanup(self._delete_volume, new_volume['id'])
self.client.wait_for_volume_status(new_volume['id'], 'available')
- resp, update_volume = \
- self.client.update_volume(
- new_volume['id'],
- display_name=volume['display_name'],
- display_description=volume['display_description'])
- self.assertEqual(200, resp.status)
+
+ params = {self.name_field: volume[self.name_field],
+ self.descrip_field: volume[self.descrip_field]}
+ _, update_volume = self.client.update_volume(new_volume['id'],
+ **params)
# NOTE(jdg): Revert back to strict true/false checking
# after fix for bug #1227837 merges
@@ -159,5 +149,13 @@
self._volume_create_get_update_delete(source_volid=origin['id'])
-class VolumesGetTestXML(VolumesGetTest):
+class VolumesV2GetTestXML(VolumesV2GetTest):
+ _interface = "xml"
+
+
+class VolumesV1GetTest(VolumesV2GetTest):
+ _api_version = 1
+
+
+class VolumesV1GetTestXML(VolumesV1GetTest):
_interface = "xml"
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index b8a2faa..272a41a 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -15,11 +15,12 @@
# under the License.
import operator
+from testtools import matchers
+
from tempest.api.volume import base
from tempest.common.utils import data_utils
from tempest.openstack.common import log as logging
from tempest import test
-from testtools import matchers
LOG = logging.getLogger(__name__)
@@ -66,7 +67,7 @@
cls.metadata = {'Type': 'work'}
for i in range(3):
volume = cls.create_volume(metadata=cls.metadata)
- resp, volume = cls.client.get_volume(volume['id'])
+ _, volume = cls.client.get_volume(volume['id'])
cls.volume_list.append(volume)
cls.volume_id_list.append(volume['id'])
@@ -74,7 +75,7 @@
def tearDownClass(cls):
# Delete the created volumes
for volid in cls.volume_id_list:
- resp, _ = cls.client.delete_volume(volid)
+ cls.client.delete_volume(volid)
cls.client.wait_for_resource_deletion(volid)
super(VolumesV2ListTestJSON, cls).tearDownClass()
@@ -84,12 +85,11 @@
and validates result.
"""
if with_detail:
- resp, fetched_vol_list = \
+ _, fetched_vol_list = \
self.client.list_volumes_with_detail(params=params)
else:
- resp, fetched_vol_list = self.client.list_volumes(params=params)
+ _, fetched_vol_list = self.client.list_volumes(params=params)
- self.assertEqual(200, resp.status)
# Validating params of fetched volumes
# In v2, only list detail view includes items in params.
# In v1, list view and list detail view are same. So the
@@ -112,8 +112,7 @@
def test_volume_list(self):
# Get a list of Volumes
# Fetch all volumes
- resp, fetched_list = self.client.list_volumes()
- self.assertEqual(200, resp.status)
+ _, fetched_list = self.client.list_volumes()
self.assertVolumesIn(fetched_list, self.volume_list,
fields=self.VOLUME_FIELDS)
@@ -121,16 +120,14 @@
def test_volume_list_with_details(self):
# Get a list of Volumes with details
# Fetch all Volumes
- resp, fetched_list = self.client.list_volumes_with_detail()
- self.assertEqual(200, resp.status)
+ _, fetched_list = self.client.list_volumes_with_detail()
self.assertVolumesIn(fetched_list, self.volume_list)
@test.attr(type='gate')
def test_volume_list_by_name(self):
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
params = {self.name: volume[self.name]}
- resp, fetched_vol = self.client.list_volumes(params)
- self.assertEqual(200, resp.status)
+ _, fetched_vol = self.client.list_volumes(params)
self.assertEqual(1, len(fetched_vol), str(fetched_vol))
self.assertEqual(fetched_vol[0][self.name],
volume[self.name])
@@ -139,8 +136,7 @@
def test_volume_list_details_by_name(self):
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
params = {self.name: volume[self.name]}
- resp, fetched_vol = self.client.list_volumes_with_detail(params)
- self.assertEqual(200, resp.status)
+ _, fetched_vol = self.client.list_volumes_with_detail(params)
self.assertEqual(1, len(fetched_vol), str(fetched_vol))
self.assertEqual(fetched_vol[0][self.name],
volume[self.name])
@@ -148,8 +144,7 @@
@test.attr(type='gate')
def test_volumes_list_by_status(self):
params = {'status': 'available'}
- resp, fetched_list = self.client.list_volumes(params)
- self.assertEqual(200, resp.status)
+ _, fetched_list = self.client.list_volumes(params)
self._list_by_param_value_and_assert(params)
self.assertVolumesIn(fetched_list, self.volume_list,
fields=self.VOLUME_FIELDS)
@@ -157,8 +152,7 @@
@test.attr(type='gate')
def test_volumes_list_details_by_status(self):
params = {'status': 'available'}
- resp, fetched_list = self.client.list_volumes_with_detail(params)
- self.assertEqual(200, resp.status)
+ _, fetched_list = self.client.list_volumes_with_detail(params)
for volume in fetched_list:
self.assertEqual('available', volume['status'])
self.assertVolumesIn(fetched_list, self.volume_list)
@@ -168,8 +162,7 @@
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
zone = volume['availability_zone']
params = {'availability_zone': zone}
- resp, fetched_list = self.client.list_volumes(params)
- self.assertEqual(200, resp.status)
+ _, fetched_list = self.client.list_volumes(params)
self._list_by_param_value_and_assert(params)
self.assertVolumesIn(fetched_list, self.volume_list,
fields=self.VOLUME_FIELDS)
@@ -179,8 +172,7 @@
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
zone = volume['availability_zone']
params = {'availability_zone': zone}
- resp, fetched_list = self.client.list_volumes_with_detail(params)
- self.assertEqual(200, resp.status)
+ _, fetched_list = self.client.list_volumes_with_detail(params)
for volume in fetched_list:
self.assertEqual(zone, volume['availability_zone'])
self.assertVolumesIn(fetched_list, self.volume_list)
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index bc5b1dc..5f0cffa 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -21,15 +21,16 @@
from tempest import test
-class VolumesNegativeTest(base.BaseVolumeV1Test):
- _interface = 'json'
+class VolumesV2NegativeTest(base.BaseVolumeTest):
@classmethod
@test.safe_setup
def setUpClass(cls):
- super(VolumesNegativeTest, cls).setUpClass()
+ super(VolumesV2NegativeTest, cls).setUpClass()
cls.client = cls.volumes_client
+ cls.name_field = cls.special_fields['name_field']
+
# Create a test shared instance and volume for attach/detach tests
cls.volume = cls.create_volume()
cls.mountpoint = "/dev/vdc"
@@ -224,46 +225,49 @@
@test.attr(type=['negative', 'gate'])
def test_reserve_volume_with_negative_volume_status(self):
# Mark volume as reserved.
- resp, body = self.client.reserve_volume(self.volume['id'])
- self.assertEqual(202, resp.status)
+ _, body = self.client.reserve_volume(self.volume['id'])
# Mark volume which is marked as reserved before
self.assertRaises(exceptions.BadRequest,
self.client.reserve_volume,
self.volume['id'])
# Unmark volume as reserved.
- resp, body = self.client.unreserve_volume(self.volume['id'])
- self.assertEqual(202, resp.status)
+ _, body = self.client.unreserve_volume(self.volume['id'])
@test.attr(type=['negative', 'gate'])
def test_list_volumes_with_nonexistent_name(self):
v_name = data_utils.rand_name('Volume-')
- params = {'display_name': v_name}
- resp, fetched_volume = self.client.list_volumes(params)
- self.assertEqual(200, resp.status)
+ params = {self.name_field: v_name}
+ _, fetched_volume = self.client.list_volumes(params)
self.assertEqual(0, len(fetched_volume))
@test.attr(type=['negative', 'gate'])
def test_list_volumes_detail_with_nonexistent_name(self):
v_name = data_utils.rand_name('Volume-')
- params = {'display_name': v_name}
- resp, fetched_volume = self.client.list_volumes_with_detail(params)
- self.assertEqual(200, resp.status)
+ params = {self.name_field: v_name}
+ _, fetched_volume = self.client.list_volumes_with_detail(params)
self.assertEqual(0, len(fetched_volume))
@test.attr(type=['negative', 'gate'])
def test_list_volumes_with_invalid_status(self):
params = {'status': 'null'}
- resp, fetched_volume = self.client.list_volumes(params)
- self.assertEqual(200, resp.status)
+ _, fetched_volume = self.client.list_volumes(params)
self.assertEqual(0, len(fetched_volume))
@test.attr(type=['negative', 'gate'])
def test_list_volumes_detail_with_invalid_status(self):
params = {'status': 'null'}
- resp, fetched_volume = self.client.list_volumes_with_detail(params)
- self.assertEqual(200, resp.status)
+ _, fetched_volume = self.client.list_volumes_with_detail(params)
self.assertEqual(0, len(fetched_volume))
-class VolumesNegativeTestXML(VolumesNegativeTest):
+class VolumesV2NegativeTestXML(VolumesV2NegativeTest):
+ _interface = 'xml'
+
+
+class VolumesV1NegativeTest(VolumesV2NegativeTest):
+ _api_version = 1
+ _name = 'display_name'
+
+
+class VolumesV1NegativeTestXML(VolumesV1NegativeTest):
_interface = 'xml'
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 26316d2..7db1ef1 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -47,14 +47,13 @@
and validates result.
"""
if with_detail:
- resp, fetched_snap_list = \
+ _, fetched_snap_list = \
self.snapshots_client.\
list_snapshots_with_detail(params=params)
else:
- resp, fetched_snap_list = \
+ _, fetched_snap_list = \
self.snapshots_client.list_snapshots(params=params)
- self.assertEqual(200, resp.status)
# Validating params of fetched snapshots
for snap in fetched_snap_list:
for key in params:
@@ -74,9 +73,8 @@
self.addCleanup(self.servers_client.delete_server, server['id'])
self.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
mountpoint = '/dev/%s' % CONF.compute.volume_device_name
- resp, body = self.volumes_client.attach_volume(
+ _, body = self.volumes_client.attach_volume(
self.volume_origin['id'], server['id'], mountpoint)
- self.assertEqual(202, resp.status)
self.volumes_client.wait_for_volume_status(self.volume_origin['id'],
'in-use')
self.addCleanup(self._detach, self.volume_origin['id'])
@@ -85,7 +83,6 @@
force=True)
# Delete the snapshot
self.snapshots_client.delete_snapshot(snapshot['id'])
- self.assertEqual(202, resp.status)
self.snapshots_client.wait_for_resource_deletion(snapshot['id'])
self.snapshots.remove(snapshot)
@@ -97,40 +94,35 @@
display_name=s_name)
# Get the snap and check for some of its details
- resp, snap_get = self.snapshots_client.get_snapshot(snapshot['id'])
- self.assertEqual(200, resp.status)
+ _, snap_get = self.snapshots_client.get_snapshot(snapshot['id'])
self.assertEqual(self.volume_origin['id'],
snap_get['volume_id'],
"Referred volume origin mismatch")
# Compare also with the output from the list action
tracking_data = (snapshot['id'], snapshot['display_name'])
- resp, snaps_list = self.snapshots_client.list_snapshots()
- self.assertEqual(200, resp.status)
+ _, snaps_list = self.snapshots_client.list_snapshots()
snaps_data = [(f['id'], f['display_name']) for f in snaps_list]
self.assertIn(tracking_data, snaps_data)
# Updates snapshot with new values
new_s_name = data_utils.rand_name('new-snap')
new_desc = 'This is the new description of snapshot.'
- resp, update_snapshot = \
+ _, update_snapshot = \
self.snapshots_client.update_snapshot(snapshot['id'],
display_name=new_s_name,
display_description=new_desc)
# Assert response body for update_snapshot method
- self.assertEqual(200, resp.status)
self.assertEqual(new_s_name, update_snapshot['display_name'])
self.assertEqual(new_desc, update_snapshot['display_description'])
# Assert response body for get_snapshot method
- resp, updated_snapshot = \
+ _, updated_snapshot = \
self.snapshots_client.get_snapshot(snapshot['id'])
- self.assertEqual(200, resp.status)
self.assertEqual(new_s_name, updated_snapshot['display_name'])
self.assertEqual(new_desc, updated_snapshot['display_description'])
# Delete the snapshot
self.snapshots_client.delete_snapshot(snapshot['id'])
- self.assertEqual(200, resp.status)
self.snapshots_client.wait_for_resource_deletion(snapshot['id'])
self.snapshots.remove(snapshot)
@@ -177,13 +169,12 @@
@test.attr(type='gate')
def test_volume_from_snapshot(self):
# Create a temporary snap using wrapper method from base, then
- # create a snap based volume, check resp code and deletes it
+ # create a snap based volume and deletes it
snapshot = self.create_snapshot(self.volume_origin['id'])
# NOTE(gfidente): size is required also when passing snapshot_id
- resp, volume = self.volumes_client.create_volume(
+ _, volume = self.volumes_client.create_volume(
size=1,
snapshot_id=snapshot['id'])
- self.assertEqual(200, resp.status)
self.volumes_client.wait_for_volume_status(volume['id'], 'available')
self.volumes_client.delete_volume(volume['id'])
self.volumes_client.wait_for_resource_deletion(volume['id'])
diff --git a/tempest/api/volume/v2/test_volumes_list.py b/tempest/api/volume/v2/test_volumes_list.py
index 7ca8599..3ae227d 100644
--- a/tempest/api/volume/v2/test_volumes_list.py
+++ b/tempest/api/volume/v2/test_volumes_list.py
@@ -42,7 +42,7 @@
cls.metadata = {'Type': 'work'}
for i in range(3):
volume = cls.create_volume(metadata=cls.metadata)
- resp, volume = cls.client.get_volume(volume['id'])
+ _, volume = cls.client.get_volume(volume['id'])
cls.volume_list.append(volume)
cls.volume_id_list.append(volume['id'])
@@ -50,7 +50,7 @@
def tearDownClass(cls):
# Delete the created volumes
for volid in cls.volume_id_list:
- resp, _ = cls.client.delete_volume(volid)
+ cls.client.delete_volume(volid)
cls.client.wait_for_resource_deletion(volid)
super(VolumesV2ListTestJSON, cls).tearDownClass()
@@ -66,8 +66,7 @@
'sort_dir': sort_dir,
'sort_key': sort_key
}
- resp, fetched_volume = self.client.list_volumes_with_detail(params)
- self.assertEqual(200, resp.status)
+ _, fetched_volume = self.client.list_volumes_with_detail(params)
self.assertEqual(limit, len(fetched_volume),
"The count of volumes is %s, expected:%s " %
(len(fetched_volume), limit))
diff --git a/tempest/api_schema/compute/__init__.py b/tempest/api_schema/request/__init__.py
similarity index 100%
copy from tempest/api_schema/compute/__init__.py
copy to tempest/api_schema/request/__init__.py
diff --git a/tempest/api_schema/compute/__init__.py b/tempest/api_schema/request/compute/__init__.py
similarity index 100%
rename from tempest/api_schema/compute/__init__.py
rename to tempest/api_schema/request/compute/__init__.py
diff --git a/tempest/api_schema/request/compute/flavors.py b/tempest/api_schema/request/compute/flavors.py
new file mode 100644
index 0000000..36e5a19
--- /dev/null
+++ b/tempest/api_schema/request/compute/flavors.py
@@ -0,0 +1,32 @@
+# (c) 2014 Deutsche Telekom AG
+# 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_flavor_details = {
+ "name": "get-flavor-details",
+ "http-method": "GET",
+ "url": "flavors/%s",
+ "resources": [
+ {"name": "flavor", "expected_result": 404}
+ ]
+}
+
+common_flavor_list = {
+ "name": "list-flavors-with-detail",
+ "http-method": "GET",
+ "url": "flavors/detail",
+ "json-schema": {
+ "type": "object",
+ "properties": {
+ }
+ }
+}
diff --git a/tempest/api_schema/compute/v2/__init__.py b/tempest/api_schema/request/compute/v2/__init__.py
similarity index 100%
rename from tempest/api_schema/compute/v2/__init__.py
rename to tempest/api_schema/request/compute/v2/__init__.py
diff --git a/tempest/api_schema/request/compute/v2/flavors.py b/tempest/api_schema/request/compute/v2/flavors.py
new file mode 100644
index 0000000..08f6c28
--- /dev/null
+++ b/tempest/api_schema/request/compute/v2/flavors.py
@@ -0,0 +1,37 @@
+# (c) 2014 Deutsche Telekom AG
+# 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 copy
+
+from tempest.api_schema.request.compute import flavors
+
+flavors_details = copy.deepcopy(flavors.common_flavor_details)
+
+flavor_list = copy.deepcopy(flavors.common_flavor_list)
+
+flavor_list["json-schema"]["properties"] = {
+ "minRam": {
+ "type": "integer",
+ "results": {
+ "gen_none": 400,
+ "gen_string": 400
+ }
+ },
+ "minDisk": {
+ "type": "integer",
+ "results": {
+ "gen_none": 400,
+ "gen_string": 400
+ }
+ }
+}
diff --git a/tempest/api_schema/compute/v3/__init__.py b/tempest/api_schema/request/compute/v3/__init__.py
similarity index 100%
rename from tempest/api_schema/compute/v3/__init__.py
rename to tempest/api_schema/request/compute/v3/__init__.py
diff --git a/tempest/api_schema/request/compute/v3/flavors.py b/tempest/api_schema/request/compute/v3/flavors.py
new file mode 100644
index 0000000..b913aca
--- /dev/null
+++ b/tempest/api_schema/request/compute/v3/flavors.py
@@ -0,0 +1,37 @@
+# (c) 2014 Deutsche Telekom AG
+# 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 copy
+
+from tempest.api_schema.request.compute import flavors
+
+flavors_details = copy.deepcopy(flavors.common_flavor_details)
+
+flavor_list = copy.deepcopy(flavors.common_flavor_list)
+
+flavor_list["json-schema"]["properties"] = {
+ "min_ram": {
+ "type": "integer",
+ "results": {
+ "gen_none": 400,
+ "gen_string": 400
+ }
+ },
+ "min_disk": {
+ "type": "integer",
+ "results": {
+ "gen_none": 400,
+ "gen_string": 400
+ }
+ }
+}
diff --git a/tempest/api_schema/compute/__init__.py b/tempest/api_schema/response/__init__.py
similarity index 100%
copy from tempest/api_schema/compute/__init__.py
copy to tempest/api_schema/response/__init__.py
diff --git a/tempest/api_schema/compute/__init__.py b/tempest/api_schema/response/compute/__init__.py
similarity index 100%
copy from tempest/api_schema/compute/__init__.py
copy to tempest/api_schema/response/compute/__init__.py
diff --git a/tempest/api_schema/compute/agents.py b/tempest/api_schema/response/compute/agents.py
similarity index 100%
rename from tempest/api_schema/compute/agents.py
rename to tempest/api_schema/response/compute/agents.py
diff --git a/tempest/api_schema/compute/aggregates.py b/tempest/api_schema/response/compute/aggregates.py
similarity index 100%
rename from tempest/api_schema/compute/aggregates.py
rename to tempest/api_schema/response/compute/aggregates.py
diff --git a/tempest/api_schema/compute/availability_zone.py b/tempest/api_schema/response/compute/availability_zone.py
similarity index 100%
rename from tempest/api_schema/compute/availability_zone.py
rename to tempest/api_schema/response/compute/availability_zone.py
diff --git a/tempest/api_schema/compute/certificates.py b/tempest/api_schema/response/compute/certificates.py
similarity index 100%
rename from tempest/api_schema/compute/certificates.py
rename to tempest/api_schema/response/compute/certificates.py
diff --git a/tempest/api_schema/compute/flavors.py b/tempest/api_schema/response/compute/flavors.py
similarity index 96%
rename from tempest/api_schema/compute/flavors.py
rename to tempest/api_schema/response/compute/flavors.py
index aa019e4..44020d2 100644
--- a/tempest/api_schema/compute/flavors.py
+++ b/tempest/api_schema/response/compute/flavors.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api_schema.compute import parameter_types
+from tempest.api_schema.response.compute import parameter_types
list_flavors = {
'status_code': [200],
diff --git a/tempest/api_schema/compute/flavors_access.py b/tempest/api_schema/response/compute/flavors_access.py
similarity index 100%
rename from tempest/api_schema/compute/flavors_access.py
rename to tempest/api_schema/response/compute/flavors_access.py
diff --git a/tempest/api_schema/compute/flavors_extra_specs.py b/tempest/api_schema/response/compute/flavors_extra_specs.py
similarity index 100%
rename from tempest/api_schema/compute/flavors_extra_specs.py
rename to tempest/api_schema/response/compute/flavors_extra_specs.py
diff --git a/tempest/api_schema/compute/hosts.py b/tempest/api_schema/response/compute/hosts.py
similarity index 100%
rename from tempest/api_schema/compute/hosts.py
rename to tempest/api_schema/response/compute/hosts.py
diff --git a/tempest/api_schema/compute/hypervisors.py b/tempest/api_schema/response/compute/hypervisors.py
similarity index 100%
rename from tempest/api_schema/compute/hypervisors.py
rename to tempest/api_schema/response/compute/hypervisors.py
diff --git a/tempest/api_schema/compute/interfaces.py b/tempest/api_schema/response/compute/interfaces.py
similarity index 95%
rename from tempest/api_schema/compute/interfaces.py
rename to tempest/api_schema/response/compute/interfaces.py
index 79a8f42..fd53eb3 100644
--- a/tempest/api_schema/compute/interfaces.py
+++ b/tempest/api_schema/response/compute/interfaces.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api_schema.compute import parameter_types
+from tempest.api_schema.response.compute import parameter_types
delete_interface = {
'status_code': [202]
diff --git a/tempest/api_schema/compute/keypairs.py b/tempest/api_schema/response/compute/keypairs.py
similarity index 100%
rename from tempest/api_schema/compute/keypairs.py
rename to tempest/api_schema/response/compute/keypairs.py
diff --git a/tempest/api_schema/compute/migrations.py b/tempest/api_schema/response/compute/migrations.py
similarity index 100%
rename from tempest/api_schema/compute/migrations.py
rename to tempest/api_schema/response/compute/migrations.py
diff --git a/tempest/api_schema/compute/parameter_types.py b/tempest/api_schema/response/compute/parameter_types.py
similarity index 100%
rename from tempest/api_schema/compute/parameter_types.py
rename to tempest/api_schema/response/compute/parameter_types.py
diff --git a/tempest/api_schema/compute/quotas.py b/tempest/api_schema/response/compute/quotas.py
similarity index 100%
rename from tempest/api_schema/compute/quotas.py
rename to tempest/api_schema/response/compute/quotas.py
diff --git a/tempest/api_schema/compute/servers.py b/tempest/api_schema/response/compute/servers.py
similarity index 98%
rename from tempest/api_schema/compute/servers.py
rename to tempest/api_schema/response/compute/servers.py
index a16e425..d6c2ddb 100644
--- a/tempest/api_schema/compute/servers.py
+++ b/tempest/api_schema/response/compute/servers.py
@@ -14,7 +14,7 @@
import copy
-from tempest.api_schema.compute import parameter_types
+from tempest.api_schema.response.compute import parameter_types
get_password = {
'status_code': [200],
diff --git a/tempest/api_schema/compute/services.py b/tempest/api_schema/response/compute/services.py
similarity index 100%
rename from tempest/api_schema/compute/services.py
rename to tempest/api_schema/response/compute/services.py
diff --git a/tempest/api_schema/compute/v2/__init__.py b/tempest/api_schema/response/compute/v2/__init__.py
similarity index 100%
copy from tempest/api_schema/compute/v2/__init__.py
copy to tempest/api_schema/response/compute/v2/__init__.py
diff --git a/tempest/api_schema/compute/v2/agents.py b/tempest/api_schema/response/compute/v2/agents.py
similarity index 93%
rename from tempest/api_schema/compute/v2/agents.py
rename to tempest/api_schema/response/compute/v2/agents.py
index 30f999f..d827377 100644
--- a/tempest/api_schema/compute/v2/agents.py
+++ b/tempest/api_schema/response/compute/v2/agents.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api_schema.compute import agents
+from tempest.api_schema.response.compute import agents
create_agent = {
'status_code': [200],
diff --git a/tempest/api_schema/compute/v2/aggregates.py b/tempest/api_schema/response/compute/v2/aggregates.py
similarity index 93%
rename from tempest/api_schema/compute/v2/aggregates.py
rename to tempest/api_schema/response/compute/v2/aggregates.py
index bc36044..d87e4de 100644
--- a/tempest/api_schema/compute/v2/aggregates.py
+++ b/tempest/api_schema/response/compute/v2/aggregates.py
@@ -14,7 +14,7 @@
import copy
-from tempest.api_schema.compute import aggregates
+from tempest.api_schema.response.compute import aggregates
delete_aggregate = {
'status_code': [200]
diff --git a/tempest/api_schema/compute/v2/availability_zone.py b/tempest/api_schema/response/compute/v2/availability_zone.py
similarity index 95%
rename from tempest/api_schema/compute/v2/availability_zone.py
rename to tempest/api_schema/response/compute/v2/availability_zone.py
index d3d2787..e261d3d 100644
--- a/tempest/api_schema/compute/v2/availability_zone.py
+++ b/tempest/api_schema/response/compute/v2/availability_zone.py
@@ -14,7 +14,7 @@
import copy
-from tempest.api_schema.compute import availability_zone as common
+from tempest.api_schema.response.compute import availability_zone as common
base = {
diff --git a/tempest/api_schema/compute/v2/certificates.py b/tempest/api_schema/response/compute/v2/certificates.py
similarity index 91%
rename from tempest/api_schema/compute/v2/certificates.py
rename to tempest/api_schema/response/compute/v2/certificates.py
index 1eb38ce..bda6075 100644
--- a/tempest/api_schema/compute/v2/certificates.py
+++ b/tempest/api_schema/response/compute/v2/certificates.py
@@ -14,6 +14,6 @@
import copy
-from tempest.api_schema.compute import certificates
+from tempest.api_schema.response.compute import certificates
create_certificate = copy.deepcopy(certificates._common_schema)
diff --git a/tempest/api_schema/compute/v2/extensions.py b/tempest/api_schema/response/compute/v2/extensions.py
similarity index 100%
rename from tempest/api_schema/compute/v2/extensions.py
rename to tempest/api_schema/response/compute/v2/extensions.py
diff --git a/tempest/api_schema/compute/v2/fixed_ips.py b/tempest/api_schema/response/compute/v2/fixed_ips.py
similarity index 100%
rename from tempest/api_schema/compute/v2/fixed_ips.py
rename to tempest/api_schema/response/compute/v2/fixed_ips.py
diff --git a/tempest/api_schema/compute/v2/flavors.py b/tempest/api_schema/response/compute/v2/flavors.py
similarity index 92%
rename from tempest/api_schema/compute/v2/flavors.py
rename to tempest/api_schema/response/compute/v2/flavors.py
index bee6ecb..811ea84 100644
--- a/tempest/api_schema/compute/v2/flavors.py
+++ b/tempest/api_schema/response/compute/v2/flavors.py
@@ -14,11 +14,11 @@
import copy
-from tempest.api_schema.compute import flavors
+from tempest.api_schema.response.compute import flavors
list_flavors_details = copy.deepcopy(flavors.common_flavor_list_details)
-# 'swap' attributes comes as integre value but if it is empty it comes as "".
+# 'swap' attributes comes as integer value but if it is empty it comes as "".
# So defining type of as string and integer.
list_flavors_details['response_body']['properties']['flavors']['items'][
'properties']['swap'] = {'type': ['string', 'integer']}
@@ -38,7 +38,7 @@
create_get_flavor_details = copy.deepcopy(flavors.common_flavor_details)
-# 'swap' attributes comes as integre value but if it is empty it comes as "".
+# 'swap' attributes comes as integer value but if it is empty it comes as "".
# So defining type of as string and integer.
create_get_flavor_details['response_body']['properties']['flavor'][
'properties']['swap'] = {'type': ['string', 'integer']}
diff --git a/tempest/api_schema/compute/v2/floating_ips.py b/tempest/api_schema/response/compute/v2/floating_ips.py
similarity index 100%
rename from tempest/api_schema/compute/v2/floating_ips.py
rename to tempest/api_schema/response/compute/v2/floating_ips.py
diff --git a/tempest/api_schema/compute/v2/hosts.py b/tempest/api_schema/response/compute/v2/hosts.py
similarity index 95%
rename from tempest/api_schema/compute/v2/hosts.py
rename to tempest/api_schema/response/compute/v2/hosts.py
index 86efadf..0944792 100644
--- a/tempest/api_schema/compute/v2/hosts.py
+++ b/tempest/api_schema/response/compute/v2/hosts.py
@@ -14,7 +14,7 @@
import copy
-from tempest.api_schema.compute import hosts
+from tempest.api_schema.response.compute import hosts
startup_host = {
diff --git a/tempest/api_schema/compute/v2/hypervisors.py b/tempest/api_schema/response/compute/v2/hypervisors.py
similarity index 95%
rename from tempest/api_schema/compute/v2/hypervisors.py
rename to tempest/api_schema/response/compute/v2/hypervisors.py
index 6bb43a7..1878881 100644
--- a/tempest/api_schema/compute/v2/hypervisors.py
+++ b/tempest/api_schema/response/compute/v2/hypervisors.py
@@ -13,7 +13,9 @@
# under the License.
import copy
-from tempest.api_schema.compute import hypervisors
+
+from tempest.api_schema.response.compute import hypervisors
+
hypervisors_servers = copy.deepcopy(hypervisors.common_hypervisors_detail)
diff --git a/tempest/api_schema/compute/v2/images.py b/tempest/api_schema/response/compute/v2/images.py
similarity index 97%
rename from tempest/api_schema/compute/v2/images.py
rename to tempest/api_schema/response/compute/v2/images.py
index 90737a2..923c744 100644
--- a/tempest/api_schema/compute/v2/images.py
+++ b/tempest/api_schema/response/compute/v2/images.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api_schema.compute import parameter_types
+from tempest.api_schema.response.compute import parameter_types
common_image_schema = {
'type': 'object',
diff --git a/tempest/api_schema/compute/v2/instance_usage_audit_logs.py b/tempest/api_schema/response/compute/v2/instance_usage_audit_logs.py
similarity index 100%
rename from tempest/api_schema/compute/v2/instance_usage_audit_logs.py
rename to tempest/api_schema/response/compute/v2/instance_usage_audit_logs.py
diff --git a/tempest/api_schema/compute/v2/interfaces.py b/tempest/api_schema/response/compute/v2/interfaces.py
similarity index 92%
rename from tempest/api_schema/compute/v2/interfaces.py
rename to tempest/api_schema/response/compute/v2/interfaces.py
index 7fca791..64d161d 100644
--- a/tempest/api_schema/compute/v2/interfaces.py
+++ b/tempest/api_schema/response/compute/v2/interfaces.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api_schema.compute import interfaces as common_schema
+from tempest.api_schema.response.compute import interfaces as common_schema
list_interfaces = {
'status_code': [200],
diff --git a/tempest/api_schema/compute/v2/keypairs.py b/tempest/api_schema/response/compute/v2/keypairs.py
similarity index 96%
rename from tempest/api_schema/compute/v2/keypairs.py
rename to tempest/api_schema/response/compute/v2/keypairs.py
index 32d8cca..ec26fa0 100644
--- a/tempest/api_schema/compute/v2/keypairs.py
+++ b/tempest/api_schema/response/compute/v2/keypairs.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api_schema.compute import keypairs
+from tempest.api_schema.response.compute import keypairs
get_keypair = {
'status_code': [200],
diff --git a/tempest/api_schema/compute/v2/limits.py b/tempest/api_schema/response/compute/v2/limits.py
similarity index 100%
rename from tempest/api_schema/compute/v2/limits.py
rename to tempest/api_schema/response/compute/v2/limits.py
diff --git a/tempest/api_schema/compute/v2/quota_classes.py b/tempest/api_schema/response/compute/v2/quota_classes.py
similarity index 95%
rename from tempest/api_schema/compute/v2/quota_classes.py
rename to tempest/api_schema/response/compute/v2/quota_classes.py
index 3464fb4..5474a89 100644
--- a/tempest/api_schema/compute/v2/quota_classes.py
+++ b/tempest/api_schema/response/compute/v2/quota_classes.py
@@ -15,7 +15,7 @@
import copy
-from tempest.api_schema.compute.v2 import quotas
+from tempest.api_schema.response.compute.v2 import quotas
# NOTE(mriedem): os-quota-class-sets responses are the same as os-quota-sets
# except for the key in the response body is quota_class_set instead of
diff --git a/tempest/api_schema/compute/v2/quotas.py b/tempest/api_schema/response/compute/v2/quotas.py
similarity index 97%
rename from tempest/api_schema/compute/v2/quotas.py
rename to tempest/api_schema/response/compute/v2/quotas.py
index 31c0458..630b227 100644
--- a/tempest/api_schema/compute/v2/quotas.py
+++ b/tempest/api_schema/response/compute/v2/quotas.py
@@ -14,7 +14,7 @@
import copy
-from tempest.api_schema.compute import quotas
+from tempest.api_schema.response.compute import quotas
quota_set = copy.deepcopy(quotas.common_quota_set)
quota_set['response_body']['properties']['quota_set']['properties'][
diff --git a/tempest/api_schema/compute/v2/security_groups.py b/tempest/api_schema/response/compute/v2/security_groups.py
similarity index 100%
rename from tempest/api_schema/compute/v2/security_groups.py
rename to tempest/api_schema/response/compute/v2/security_groups.py
diff --git a/tempest/api_schema/compute/v2/servers.py b/tempest/api_schema/response/compute/v2/servers.py
similarity index 88%
rename from tempest/api_schema/compute/v2/servers.py
rename to tempest/api_schema/response/compute/v2/servers.py
index 95c5760..5fc2008 100644
--- a/tempest/api_schema/compute/v2/servers.py
+++ b/tempest/api_schema/response/compute/v2/servers.py
@@ -14,8 +14,8 @@
import copy
-from tempest.api_schema.compute import parameter_types
-from tempest.api_schema.compute import servers
+from tempest.api_schema.response.compute import parameter_types
+from tempest.api_schema.response.compute import servers
create_server = {
'status_code': [202],
@@ -28,14 +28,11 @@
'id': {'type': 'string'},
'security_groups': {'type': 'array'},
'links': parameter_types.links,
- 'adminPass': {'type': 'string'},
'OS-DCF:diskConfig': {'type': 'string'}
},
# NOTE: OS-DCF:diskConfig is API extension, and some
# environments return a response without the attribute.
# So it is not 'required'.
- # NOTE: adminPass is not required because it can be deactivated
- # with nova API flag enable_instance_password=False
'required': ['id', 'security_groups', 'links']
}
},
@@ -43,6 +40,12 @@
}
}
+create_server_with_admin_pass = copy.deepcopy(create_server)
+create_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'adminPass': {'type': 'string'}})
+create_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('adminPass')
+
update_server = copy.deepcopy(servers.base_update_get_server)
update_server['response_body']['properties']['server']['properties'].update({
'hostId': {'type': 'string'},
@@ -270,3 +273,25 @@
# without these attributes. So they are not 'required'.
list_servers_detail['response_body']['properties']['servers']['items'][
'required'].append('hostId')
+
+rebuild_server = copy.deepcopy(update_server)
+rebuild_server['status_code'] = [202]
+del rebuild_server['response_body']['properties']['server'][
+ 'properties']['OS-DCF:diskConfig']
+
+rebuild_server_with_admin_pass = copy.deepcopy(rebuild_server)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'adminPass': {'type': 'string'}})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('adminPass')
+
+rescue_server = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'adminPass': {'type': 'string'}
+ },
+ 'required': ['adminPass']
+ }
+}
diff --git a/tempest/api_schema/compute/v2/tenant_usages.py b/tempest/api_schema/response/compute/v2/tenant_usages.py
similarity index 100%
rename from tempest/api_schema/compute/v2/tenant_usages.py
rename to tempest/api_schema/response/compute/v2/tenant_usages.py
diff --git a/tempest/api_schema/compute/v2/volumes.py b/tempest/api_schema/response/compute/v2/volumes.py
similarity index 100%
rename from tempest/api_schema/compute/v2/volumes.py
rename to tempest/api_schema/response/compute/v2/volumes.py
diff --git a/tempest/api_schema/compute/v3/__init__.py b/tempest/api_schema/response/compute/v3/__init__.py
similarity index 100%
copy from tempest/api_schema/compute/v3/__init__.py
copy to tempest/api_schema/response/compute/v3/__init__.py
diff --git a/tempest/api_schema/compute/v3/agents.py b/tempest/api_schema/response/compute/v3/agents.py
similarity index 93%
rename from tempest/api_schema/compute/v3/agents.py
rename to tempest/api_schema/response/compute/v3/agents.py
index 597a089..9ef05df 100644
--- a/tempest/api_schema/compute/v3/agents.py
+++ b/tempest/api_schema/response/compute/v3/agents.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api_schema.compute import agents
+from tempest.api_schema.response.compute import agents
create_agent = {
'status_code': [201],
diff --git a/tempest/api_schema/compute/v3/aggregates.py b/tempest/api_schema/response/compute/v3/aggregates.py
similarity index 94%
rename from tempest/api_schema/compute/v3/aggregates.py
rename to tempest/api_schema/response/compute/v3/aggregates.py
index 0272641..e3bae13 100644
--- a/tempest/api_schema/compute/v3/aggregates.py
+++ b/tempest/api_schema/response/compute/v3/aggregates.py
@@ -14,7 +14,7 @@
import copy
-from tempest.api_schema.compute import aggregates
+from tempest.api_schema.response.compute import aggregates
delete_aggregate = {
'status_code': [204]
diff --git a/tempest/api_schema/compute/v3/availability_zone.py b/tempest/api_schema/response/compute/v3/availability_zone.py
similarity index 95%
rename from tempest/api_schema/compute/v3/availability_zone.py
rename to tempest/api_schema/response/compute/v3/availability_zone.py
index 5f36c33..dbb1d41 100644
--- a/tempest/api_schema/compute/v3/availability_zone.py
+++ b/tempest/api_schema/response/compute/v3/availability_zone.py
@@ -14,7 +14,7 @@
import copy
-from tempest.api_schema.compute import availability_zone as common
+from tempest.api_schema.response.compute import availability_zone as common
base = {
diff --git a/tempest/api_schema/compute/v3/certificates.py b/tempest/api_schema/response/compute/v3/certificates.py
similarity index 92%
rename from tempest/api_schema/compute/v3/certificates.py
rename to tempest/api_schema/response/compute/v3/certificates.py
index 0723a16..c768391 100644
--- a/tempest/api_schema/compute/v3/certificates.py
+++ b/tempest/api_schema/response/compute/v3/certificates.py
@@ -14,7 +14,7 @@
import copy
-from tempest.api_schema.compute import certificates
+from tempest.api_schema.response.compute import certificates
create_certificate = copy.deepcopy(certificates._common_schema)
create_certificate['status_code'] = [201]
diff --git a/tempest/api_schema/compute/v3/extensions.py b/tempest/api_schema/response/compute/v3/extensions.py
similarity index 100%
rename from tempest/api_schema/compute/v3/extensions.py
rename to tempest/api_schema/response/compute/v3/extensions.py
diff --git a/tempest/api_schema/compute/v3/flavors.py b/tempest/api_schema/response/compute/v3/flavors.py
similarity index 95%
rename from tempest/api_schema/compute/v3/flavors.py
rename to tempest/api_schema/response/compute/v3/flavors.py
index 52010f5..d6c2c85 100644
--- a/tempest/api_schema/compute/v3/flavors.py
+++ b/tempest/api_schema/response/compute/v3/flavors.py
@@ -14,8 +14,8 @@
import copy
-from tempest.api_schema.compute import flavors
-from tempest.api_schema.compute import flavors_extra_specs
+from tempest.api_schema.response.compute import flavors
+from tempest.api_schema.response.compute import flavors_extra_specs
list_flavors_details = copy.deepcopy(flavors.common_flavor_list_details)
diff --git a/tempest/api_schema/compute/v3/hosts.py b/tempest/api_schema/response/compute/v3/hosts.py
similarity index 96%
rename from tempest/api_schema/compute/v3/hosts.py
rename to tempest/api_schema/response/compute/v3/hosts.py
index eb689d1..f356371 100644
--- a/tempest/api_schema/compute/v3/hosts.py
+++ b/tempest/api_schema/response/compute/v3/hosts.py
@@ -14,7 +14,7 @@
import copy
-from tempest.api_schema.compute import hosts
+from tempest.api_schema.response.compute import hosts
startup_host = {
'status_code': [200],
diff --git a/tempest/api_schema/compute/v3/hypervisors.py b/tempest/api_schema/response/compute/v3/hypervisors.py
similarity index 96%
rename from tempest/api_schema/compute/v3/hypervisors.py
rename to tempest/api_schema/response/compute/v3/hypervisors.py
index aa31827..b36ae7e 100644
--- a/tempest/api_schema/compute/v3/hypervisors.py
+++ b/tempest/api_schema/response/compute/v3/hypervisors.py
@@ -13,7 +13,9 @@
# under the License.
import copy
-from tempest.api_schema.compute import hypervisors
+
+from tempest.api_schema.response.compute import hypervisors
+
list_hypervisors_detail = copy.deepcopy(
hypervisors.common_list_hypervisors_detail)
diff --git a/tempest/api_schema/compute/v3/interfaces.py b/tempest/api_schema/response/compute/v3/interfaces.py
similarity index 92%
rename from tempest/api_schema/compute/v3/interfaces.py
rename to tempest/api_schema/response/compute/v3/interfaces.py
index 5e1cee2..7f716ee 100644
--- a/tempest/api_schema/compute/v3/interfaces.py
+++ b/tempest/api_schema/response/compute/v3/interfaces.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api_schema.compute import interfaces as common_schema
+from tempest.api_schema.response.compute import interfaces as common_schema
list_interfaces = {
'status_code': [200],
diff --git a/tempest/api_schema/compute/v3/keypairs.py b/tempest/api_schema/response/compute/v3/keypairs.py
similarity index 95%
rename from tempest/api_schema/compute/v3/keypairs.py
rename to tempest/api_schema/response/compute/v3/keypairs.py
index de5f4ba..ea15405 100644
--- a/tempest/api_schema/compute/v3/keypairs.py
+++ b/tempest/api_schema/response/compute/v3/keypairs.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api_schema.compute import keypairs
+from tempest.api_schema.response.compute import keypairs
get_keypair = {
'status_code': [200],
diff --git a/tempest/api_schema/compute/v3/quotas.py b/tempest/api_schema/response/compute/v3/quotas.py
similarity index 97%
rename from tempest/api_schema/compute/v3/quotas.py
rename to tempest/api_schema/response/compute/v3/quotas.py
index a3212ed..85ed3b3 100644
--- a/tempest/api_schema/compute/v3/quotas.py
+++ b/tempest/api_schema/response/compute/v3/quotas.py
@@ -14,7 +14,7 @@
import copy
-from tempest.api_schema.compute import quotas
+from tempest.api_schema.response.compute import quotas
quota_set = copy.deepcopy(quotas.common_quota_set)
quota_set['response_body']['properties']['quota_set']['properties'][
diff --git a/tempest/api_schema/compute/v3/servers.py b/tempest/api_schema/response/compute/v3/servers.py
similarity index 83%
rename from tempest/api_schema/compute/v3/servers.py
rename to tempest/api_schema/response/compute/v3/servers.py
index dc800cd..d0edd44 100644
--- a/tempest/api_schema/compute/v3/servers.py
+++ b/tempest/api_schema/response/compute/v3/servers.py
@@ -14,8 +14,8 @@
import copy
-from tempest.api_schema.compute import parameter_types
-from tempest.api_schema.compute import servers
+from tempest.api_schema.response.compute import parameter_types
+from tempest.api_schema.response.compute import servers
create_server = {
'status_code': [202],
@@ -28,7 +28,6 @@
'id': {'type': 'string'},
'os-security-groups:security_groups': {'type': 'array'},
'links': parameter_types.links,
- 'admin_password': {'type': 'string'},
'os-access-ips:access_ip_v4': parameter_types.access_ip_v4,
'os-access-ips:access_ip_v6': parameter_types.access_ip_v6
},
@@ -36,13 +35,19 @@
# and some environments return a response without these
# attributes. So they are not 'required'.
'required': ['id', 'os-security-groups:security_groups',
- 'links', 'admin_password']
+ 'links']
}
},
'required': ['server']
}
}
+create_server_with_admin_pass = copy.deepcopy(create_server)
+create_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'admin_password': {'type': 'string'}})
+create_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('admin_password')
+
addresses_v3 = copy.deepcopy(parameter_types.addresses)
addresses_v3['patternProperties']['^[a-zA-Z0-9-_.]+$']['items'][
'properties'].update({
@@ -50,9 +55,7 @@
'mac_addr': {'type': 'string'}
})
addresses_v3['patternProperties']['^[a-zA-Z0-9-_.]+$']['items'][
- 'required'].extend(
- ['type', 'mac_addr']
- )
+ 'required'].extend(['type', 'mac_addr'])
update_server = copy.deepcopy(servers.base_update_get_server)
update_server['response_body']['properties']['server']['properties'].update({
@@ -181,3 +184,27 @@
# attributes. So they are not 'required'.
list_servers_detail['response_body']['properties']['servers']['items'][
'required'].append('host_id')
+
+rebuild_server = copy.deepcopy(update_server)
+rebuild_server['status_code'] = [202]
+
+rebuild_server_with_admin_pass = copy.deepcopy(rebuild_server)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'admin_password': {'type': 'string'}})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('admin_password')
+
+rescue_server_with_admin_pass = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'admin_password': {'type': 'string'}
+ },
+ 'required': ['admin_password']
+ }
+}
+
+rescue_server = copy.deepcopy(rescue_server_with_admin_pass)
+del rescue_server['response_body']['properties']
+del rescue_server['response_body']['required']
diff --git a/tempest/api_schema/compute/version.py b/tempest/api_schema/response/compute/version.py
similarity index 100%
rename from tempest/api_schema/compute/version.py
rename to tempest/api_schema/response/compute/version.py
diff --git a/tempest/api_schema/queuing/__init__.py b/tempest/api_schema/response/queuing/__init__.py
similarity index 100%
rename from tempest/api_schema/queuing/__init__.py
rename to tempest/api_schema/response/queuing/__init__.py
diff --git a/tempest/api_schema/queuing/v1/__init__.py b/tempest/api_schema/response/queuing/v1/__init__.py
similarity index 100%
rename from tempest/api_schema/queuing/v1/__init__.py
rename to tempest/api_schema/response/queuing/v1/__init__.py
diff --git a/tempest/api_schema/queuing/v1/queues.py b/tempest/api_schema/response/queuing/v1/queues.py
similarity index 100%
rename from tempest/api_schema/queuing/v1/queues.py
rename to tempest/api_schema/response/queuing/v1/queues.py
diff --git a/tempest/auth.py b/tempest/auth.py
index 830dca9..c84ad6b 100644
--- a/tempest/auth.py
+++ b/tempest/auth.py
@@ -13,24 +13,28 @@
# License for the specific language governing permissions and limitations
# under the License.
+import abc
import copy
import datetime
import exceptions
import re
import urlparse
+import six
+
from tempest import config
+from tempest.openstack.common import log as logging
from tempest.services.identity.json import identity_client as json_id
from tempest.services.identity.v3.json import identity_client as json_v3id
from tempest.services.identity.v3.xml import identity_client as xml_v3id
from tempest.services.identity.xml import identity_client as xml_id
-from tempest.openstack.common import log as logging
CONF = config.CONF
LOG = logging.getLogger(__name__)
+@six.add_metaclass(abc.ABCMeta)
class AuthProvider(object):
"""
Provide authentication
@@ -70,18 +74,21 @@
interface=self.interface, cache=self.cache
)
+ @abc.abstractmethod
def _decorate_request(self, filters, method, url, headers=None, body=None,
auth_data=None):
"""
Decorate request with authentication data
"""
- raise NotImplementedError
+ return
+ @abc.abstractmethod
def _get_auth(self):
- raise NotImplementedError
+ return
+ @abc.abstractmethod
def _fill_credentials(self, auth_data_body):
- raise NotImplementedError
+ return
def fill_credentials(self):
"""
@@ -130,8 +137,9 @@
self.cache = None
self.credentials.reset()
+ @abc.abstractmethod
def is_expired(self, auth_data):
- raise NotImplementedError
+ return
def auth_request(self, method, url, headers=None, body=None, filters=None):
"""
@@ -188,11 +196,12 @@
self.alt_part = request_part
self.alt_auth_data = auth_data
+ @abc.abstractmethod
def base_url(self, filters, auth_data=None):
"""
Extracts the base_url based on provided filters
"""
- raise NotImplementedError
+ return
class KeystoneAuthProvider(AuthProvider):
@@ -225,11 +234,13 @@
# no change to method or body
return str(_url), _headers, body
+ @abc.abstractmethod
def _auth_client(self):
- raise NotImplementedError
+ return
+ @abc.abstractmethod
def _auth_params(self):
- raise NotImplementedError
+ return
def _get_auth(self):
# Bypasses the cache
@@ -321,7 +332,7 @@
if noversion_path != "":
path += "/" + noversion_path
_base_url = _base_url.replace(parts.path, path)
- if filters.get('skip_path', None) is not None:
+ if filters.get('skip_path', None) is not None and parts.path != '':
_base_url = _base_url.replace(parts.path, "/")
return _base_url
diff --git a/tempest/cli/README.rst b/tempest/cli/README.rst
index dcd940b..bc18084 100644
--- a/tempest/cli/README.rst
+++ b/tempest/cli/README.rst
@@ -1,3 +1,5 @@
+.. _cli_field_guide:
+
Tempest Field Guide to CLI tests
================================
diff --git a/tempest/cli/__init__.py b/tempest/cli/__init__.py
index 02f8c05..c33589a 100644
--- a/tempest/cli/__init__.py
+++ b/tempest/cli/__init__.py
@@ -13,14 +13,18 @@
# License for the specific language governing permissions and limitations
# under the License.
+import functools
import os
import shlex
import subprocess
+import testtools
+
import tempest.cli.output_parser
from tempest import config
from tempest import exceptions
from tempest.openstack.common import log as logging
+from tempest.openstack.common import versionutils
import tempest.test
@@ -29,6 +33,65 @@
CONF = config.CONF
+def execute(cmd, action, flags='', params='', fail_ok=False,
+ merge_stderr=False):
+ """Executes specified command for the given action."""
+ cmd = ' '.join([os.path.join(CONF.cli.cli_dir, cmd),
+ flags, action, params])
+ LOG.info("running: '%s'" % cmd)
+ cmd = shlex.split(cmd.encode('utf-8'))
+ result = ''
+ result_err = ''
+ stdout = subprocess.PIPE
+ stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
+ proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
+ result, result_err = proc.communicate()
+ if not fail_ok and proc.returncode != 0:
+ raise exceptions.CommandFailed(proc.returncode,
+ cmd,
+ result,
+ result_err)
+ return result
+
+
+def check_client_version(client, version):
+ """Checks if the client's version is compatible with the given version
+
+ @param client: The client to check.
+ @param version: The version to compare against.
+ @return: True if the client version is compatible with the given version
+ parameter, False otherwise.
+ """
+ current_version = execute(client, '', params='--version',
+ merge_stderr=True)
+
+ if not current_version.strip():
+ raise exceptions.TempestException('"%s --version" output was empty' %
+ client)
+
+ return versionutils.is_compatible(version, current_version,
+ same_major=False)
+
+
+def min_client_version(*args, **kwargs):
+ """A decorator to skip tests if the client used isn't of the right version.
+
+ @param client: The client command to run. For python-novaclient, this is
+ 'nova', for python-cinderclient this is 'cinder', etc.
+ @param version: The minimum version required to run the CLI test.
+ """
+ def decorator(func):
+ @functools.wraps(func)
+ def wrapper(*func_args, **func_kwargs):
+ if not check_client_version(kwargs['client'], kwargs['version']):
+ msg = "requires %s client version >= %s" % (kwargs['client'],
+ kwargs['version'])
+ raise testtools.TestCase.skipException(msg)
+ return func(*func_args, **func_kwargs)
+ return wrapper
+ return decorator
+
+
class ClientTestBase(tempest.test.BaseTestCase):
@classmethod
def setUpClass(cls):
@@ -50,7 +113,7 @@
def nova_manage(self, action, flags='', params='', fail_ok=False,
merge_stderr=False):
"""Executes nova-manage command for the given action."""
- return self.cmd(
+ return execute(
'nova-manage', action, flags, params, fail_ok, merge_stderr)
def keystone(self, action, flags='', params='', admin=True, fail_ok=False):
@@ -114,28 +177,7 @@
CONF.identity.admin_password,
CONF.identity.uri))
flags = creds + ' ' + flags
- return self.cmd(cmd, action, flags, params, fail_ok, merge_stderr)
-
- def cmd(self, cmd, action, flags='', params='', fail_ok=False,
- merge_stderr=False):
- """Executes specified command for the given action."""
- cmd = ' '.join([os.path.join(CONF.cli.cli_dir, cmd),
- flags, action, params])
- LOG.info("running: '%s'" % cmd)
- cmd = shlex.split(cmd.encode('utf-8'))
- result = ''
- result_err = ''
- stdout = subprocess.PIPE
- stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
- proc = subprocess.Popen(
- cmd, stdout=stdout, stderr=stderr)
- result, result_err = proc.communicate()
- if not fail_ok and proc.returncode != 0:
- raise exceptions.CommandFailed(proc.returncode,
- cmd,
- result,
- result_err)
- return result
+ return execute(cmd, action, flags, params, fail_ok, merge_stderr)
def assertTableStruct(self, items, field_names):
"""Verify that all items has keys listed in field_names."""
diff --git a/tempest/cli/simple_read_only/test_cinder.py b/tempest/cli/simple_read_only/test_cinder.py
index 04971c1..3a9a7a6 100644
--- a/tempest/cli/simple_read_only/test_cinder.py
+++ b/tempest/cli/simple_read_only/test_cinder.py
@@ -15,6 +15,7 @@
import logging
import re
+
import testtools
from tempest import cli
diff --git a/tempest/cli/simple_read_only/test_glance.py b/tempest/cli/simple_read_only/test_glance.py
index 90cdc55..2fd8212 100644
--- a/tempest/cli/simple_read_only/test_glance.py
+++ b/tempest/cli/simple_read_only/test_glance.py
@@ -77,7 +77,7 @@
wanted_commands = set(('image-create', 'image-delete', 'help',
'image-download', 'image-show', 'image-update',
'member-create', 'member-delete',
- 'member-list'))
+ 'member-list', 'image-list'))
self.assertFalse(wanted_commands - commands)
# Optional arguments:
diff --git a/tempest/cli/simple_read_only/test_heat.py b/tempest/cli/simple_read_only/test_heat.py
index 7a952fc..8e413a9 100644
--- a/tempest/cli/simple_read_only/test_heat.py
+++ b/tempest/cli/simple_read_only/test_heat.py
@@ -12,6 +12,7 @@
import json
import os
+
import yaml
import tempest.cli
@@ -85,6 +86,7 @@
def test_heat_help(self):
self.heat('help')
+ @tempest.cli.min_client_version(client='heat', version='0.2.7')
def test_heat_bash_completion(self):
self.heat('bash-completion')
diff --git a/tempest/cli/simple_read_only/test_nova.py b/tempest/cli/simple_read_only/test_nova.py
index 7085cc9..9bac7a6 100644
--- a/tempest/cli/simple_read_only/test_nova.py
+++ b/tempest/cli/simple_read_only/test_nova.py
@@ -144,6 +144,7 @@
def test_admin_secgroup_list_rules(self):
self.nova('secgroup-list-rules')
+ @tempest.cli.min_client_version(client='nova', version='2.18')
def test_admin_server_group_list(self):
self.nova('server-group-list')
diff --git a/tempest/clients.py b/tempest/clients.py
index 4e2205e..2b8b6fb 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -50,8 +50,11 @@
from tempest.services.compute.json.limits_client import LimitsClientJSON
from tempest.services.compute.json.migrations_client import \
MigrationsClientJSON
+from tempest.services.compute.json.networks_client import NetworksClientJSON
from tempest.services.compute.json.quotas_client import QuotaClassesClientJSON
from tempest.services.compute.json.quotas_client import QuotasClientJSON
+from tempest.services.compute.json.security_group_default_rules_client import \
+ SecurityGroupDefaultRulesClientJSON
from tempest.services.compute.json.security_groups_client import \
SecurityGroupsClientJSON
from tempest.services.compute.json.servers_client import ServersClientJSON
@@ -179,7 +182,15 @@
ExtensionsClientJSON as VolumeExtensionClientJSON
from tempest.services.volume.json.snapshots_client import SnapshotsClientJSON
from tempest.services.volume.json.volumes_client import VolumesClientJSON
+from tempest.services.volume.v2.json.availability_zone_client import \
+ VolumeV2AvailabilityZoneClientJSON
+from tempest.services.volume.v2.json.extensions_client import \
+ ExtensionsV2ClientJSON as VolumeV2ExtensionClientJSON
from tempest.services.volume.v2.json.volumes_client import VolumesV2ClientJSON
+from tempest.services.volume.v2.xml.availability_zone_client import \
+ VolumeV2AvailabilityZoneClientXML
+from tempest.services.volume.v2.xml.extensions_client import \
+ ExtensionsV2ClientXML as VolumeV2ExtensionClientXML
from tempest.services.volume.v2.xml.volumes_client import VolumesV2ClientXML
from tempest.services.volume.xml.admin.volume_hosts_client import \
VolumeHostsClientXML
@@ -268,6 +279,8 @@
self.auth_provider)
self.volumes_extension_client = VolumeExtensionClientXML(
self.auth_provider)
+ self.volumes_v2_extension_client = VolumeV2ExtensionClientXML(
+ self.auth_provider)
if CONF.service_available.ceilometer:
self.telemetry_client = TelemetryClientXML(
self.auth_provider)
@@ -275,6 +288,8 @@
self.token_v3_client = V3TokenClientXML()
self.volume_availability_zone_client = \
VolumeAvailabilityZoneClientXML(self.auth_provider)
+ self.volume_v2_availability_zone_client = \
+ VolumeV2AvailabilityZoneClientXML(self.auth_provider)
elif self.interface == 'json':
self.certificates_client = CertificatesClientJSON(
@@ -360,6 +375,8 @@
self.auth_provider)
self.volumes_extension_client = VolumeExtensionClientJSON(
self.auth_provider)
+ self.volumes_v2_extension_client = VolumeV2ExtensionClientJSON(
+ self.auth_provider)
self.hosts_v3_client = HostsV3ClientJSON(self.auth_provider)
self.database_flavors_client = DatabaseFlavorsClientJSON(
self.auth_provider)
@@ -376,6 +393,8 @@
self.negative_client.service = service
self.volume_availability_zone_client = \
VolumeAvailabilityZoneClientJSON(self.auth_provider)
+ self.volume_v2_availability_zone_client = \
+ VolumeV2AvailabilityZoneClientJSON(self.auth_provider)
else:
msg = "Unsupported interface type `%s'" % interface
@@ -406,6 +425,9 @@
self.data_processing_client = DataProcessingClient(
self.auth_provider)
self.migrations_client = MigrationsClientJSON(self.auth_provider)
+ self.security_group_default_rules_client = (
+ SecurityGroupDefaultRulesClientJSON(self.auth_provider))
+ self.networks_client = NetworksClientJSON(self.auth_provider)
class AltManager(Manager):
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index c1a2e46..6761a69 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -19,13 +19,13 @@
"""
+import argparse
import logging
import os
import sys
import unittest
-import yaml
-import argparse
+import yaml
import tempest.auth
from tempest import config
@@ -219,7 +219,7 @@
def check_objects(self):
"""Check that the objects created are still there."""
- if 'objects' not in self.res:
+ if not self.res.get('objects'):
return
LOG.info("checking objects")
for obj in self.res['objects']:
@@ -231,7 +231,7 @@
def check_servers(self):
"""Check that the servers are still up and running."""
- if 'servers' not in self.res:
+ if not self.res.get('servers'):
return
LOG.info("checking servers")
for server in self.res['servers']:
@@ -249,12 +249,12 @@
if return_code is 0:
break
self.assertNotEqual(count, 59,
- "Server %s is not pingable at %s" % (
- server['name'], addr))
+ "Server %s is not pingable at %s" % (
+ server['name'], addr))
def check_volumes(self):
"""Check that the volumes are still there and attached."""
- if 'volumes' not in self.res:
+ if not self.res.get('volumes'):
return
LOG.info("checking volumes")
for volume in self.res['volumes']:
@@ -309,6 +309,14 @@
return name, fname
+def _get_image_by_name(client, name):
+ r, body = client.images.image_list()
+ for image in body:
+ if name == image['name']:
+ return image
+ return None
+
+
def create_images(images):
if not images:
return
@@ -317,9 +325,7 @@
client = client_for_user(image['owner'])
# only upload a new image if the name isn't there
- r, body = client.images.image_list()
- names = [x['name'] for x in body]
- if image['name'] in names:
+ if _get_image_by_name(client, image['name']):
LOG.info("Image '%s' already exists" % image['name'])
continue
@@ -345,6 +351,20 @@
client.images.store_image(image_id, open(fname, 'r'))
+def destroy_images(images):
+ if not images:
+ return
+ LOG.info("Destroying images")
+ for image in images:
+ client = client_for_user(image['owner'])
+
+ response = _get_image_by_name(client, image['name'])
+ if not response:
+ LOG.info("Image '%s' does not exists" % image['name'])
+ continue
+ client.images.delete_image(response['id'])
+
+
#######################
#
# SERVERS
@@ -359,14 +379,6 @@
return None
-def _get_image_by_name(client, name):
- r, body = client.images.image_list()
- for image in body:
- if name == image['name']:
- return image
- return None
-
-
def _get_flavor_by_name(client, name):
r, body = client.flavors.list_flavors()
for flavor in body:
@@ -389,7 +401,7 @@
image_id = _get_image_by_name(client, server['image'])['id']
flavor_id = _get_flavor_by_name(client, server['flavor'])['id']
resp, body = client.servers.create_server(server['name'], image_id,
- flavor_id)
+ flavor_id)
server_id = body['id']
client.servers.wait_for_server_status(server_id, 'ACTIVE')
@@ -408,7 +420,7 @@
client.servers.delete_server(response['id'])
client.servers.wait_for_server_termination(response['id'],
- ignore_error=True)
+ ignore_error=True)
#######################
@@ -478,13 +490,14 @@
# destroy_volumes
destroy_servers(RES['servers'])
- LOG.warn("Destroy mode incomplete")
- # destroy_images
+ destroy_images(RES['images'])
# destroy_objects
# destroy_users
# destroy_tenants
+ LOG.warn("Destroy mode incomplete")
+
def get_options():
global OPTS
diff --git a/tempest/cmd/run_stress.py b/tempest/cmd/run_stress.py
index 07f3f66..a3f185c 100755
--- a/tempest/cmd/run_stress.py
+++ b/tempest/cmd/run_stress.py
@@ -18,13 +18,14 @@
import inspect
import json
import sys
-from testtools import testsuite
try:
from unittest import loader
except ImportError:
# unittest in python 2.6 does not contain loader, so uses unittest2
from unittest2 import loader
+from testtools import testsuite
+
from tempest.openstack.common import log as logging
from tempest.stress import driver
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 673da4f..70fd27b 100755
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -269,7 +269,7 @@
if getattr(CONF.service_available, codename_match[cfgname]):
print('Endpoint type %s not found either disable service '
'%s or fix the catalog_type in the config file' % (
- catalog_type, codename_match[cfgname]))
+ catalog_type, codename_match[cfgname]))
if update:
change_option(codename_match[cfgname],
'service_available', False)
@@ -278,7 +278,7 @@
codename_match[cfgname]):
print('Endpoint type %s is available, service %s should be'
' set as available in the config file.' % (
- catalog_type, codename_match[cfgname]))
+ catalog_type, codename_match[cfgname]))
if update:
change_option(codename_match[cfgname],
'service_available', True)
diff --git a/tempest/common/accounts.py b/tempest/common/accounts.py
new file mode 100644
index 0000000..ad88ea2
--- /dev/null
+++ b/tempest/common/accounts.py
@@ -0,0 +1,131 @@
+# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
+#
+# 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 hashlib
+import os
+
+import yaml
+
+from tempest import auth
+from tempest.common import cred_provider
+from tempest import config
+from tempest import exceptions
+from tempest.openstack.common import lockutils
+from tempest.openstack.common import log as logging
+
+CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
+
+def read_accounts_yaml(path):
+ yaml_file = open(path, 'r')
+ accounts = yaml.load(yaml_file)
+ return accounts
+
+
+class Accounts(cred_provider.CredentialProvider):
+
+ def __init__(self, name):
+ super(Accounts, self).__init__(name)
+ accounts = read_accounts_yaml(CONF.auth.test_accounts_file)
+ self.hash_dict = self.get_hash_dict(accounts)
+ self.accounts_dir = os.path.join(CONF.lock_path, 'test_accounts')
+ self.isolated_creds = {}
+
+ @classmethod
+ def get_hash_dict(cls, accounts):
+ hash_dict = {}
+ for account in accounts:
+ temp_hash = hashlib.md5()
+ temp_hash.update(str(account))
+ hash_dict[temp_hash.hexdigest()] = account
+ return hash_dict
+
+ def _create_hash_file(self, hash):
+ path = os.path.join(os.path.join(self.accounts_dir, hash))
+ if not os.path.isfile(path):
+ open(path, 'w').close()
+ return True
+ return False
+
+ @lockutils.synchronized('test_accounts_io', external=True)
+ def _get_free_hash(self, hashes):
+ if not os.path.isdir(self.accounts_dir):
+ os.mkdir(self.accounts_dir)
+ # Create File from first hash (since none are in use)
+ self._create_hash_file(hashes[0])
+ return hashes[0]
+ for hash in hashes:
+ res = self._create_hash_file(hash)
+ if res:
+ return hash
+ msg = 'Insufficient number of users provided'
+ raise exceptions.InvalidConfiguration(msg)
+
+ def _get_creds(self):
+ free_hash = self._get_free_hash(self.hashes.keys())
+ return self.hash_dict[free_hash]
+
+ @lockutils.synchronized('test_accounts_io', external=True)
+ def remove_hash(self, hash):
+ hash_path = os.path.join(self.accounts_dir, hash)
+ if not os.path.isfile(hash_path):
+ LOG.warning('Expected an account lock file %s to remove, but '
+ 'one did not exist')
+ else:
+ os.remove(hash_path)
+ if not os.listdir(self.accounts_dir):
+ os.rmdir(self.accounts_dir)
+
+ def get_hash(self, creds):
+ for hash in self.hash_dict:
+ # NOTE(mtreinish) Assuming with v3 that username, tenant, password
+ # is unique enough
+ cred_dict = {
+ 'username': creds.username,
+ 'tenant_name': creds.tenant_name,
+ 'password': creds.password
+ }
+ if self.hash_dict[hash] == cred_dict:
+ return hash
+ raise AttributeError('Invalid credentials %s' % creds)
+
+ def remove_credentials(self, creds):
+ hash = self.get_hash(creds)
+ self.remove_hash(hash, self.accounts_dir)
+
+ def get_primary_creds(self):
+ if self.credentials.get('primary'):
+ return self.credentials.get('primary')
+ creds = self._get_creds()
+ primary_credential = auth.get_credentials(**creds)
+ self.credentials['primary'] = primary_credential
+ return primary_credential
+
+ def get_alt_creds(self):
+ if self.credentials.get('alt'):
+ return self.credentials.get('alt')
+ creds = self._get_creds()
+ alt_credential = auth.get_credentials(**creds)
+ self.credentials['alt'] = alt_credential
+ return alt_credential
+
+ def clear_isolated_creds(self):
+ for creds in self.credentials.values():
+ self.remove_credentials(creds)
+
+ def get_admin_creds(self):
+ msg = ('If admin credentials are available tenant_isolation should be'
+ ' used instead')
+ raise NotImplementedError(msg)
diff --git a/tempest/common/cred_provider.py b/tempest/common/cred_provider.py
index dc4f049..9808ed1 100644
--- a/tempest/common/cred_provider.py
+++ b/tempest/common/cred_provider.py
@@ -12,6 +12,7 @@
# limitations under the License.
import abc
+
import six
from tempest import config
diff --git a/tempest/common/custom_matchers.py b/tempest/common/custom_matchers.py
index 4a7921f..7348a7d 100644
--- a/tempest/common/custom_matchers.py
+++ b/tempest/common/custom_matchers.py
@@ -13,6 +13,9 @@
# under the License.
import re
+from unittest import util
+
+from testtools import helpers
class ExistsAllResponseHeaders(object):
@@ -69,10 +72,24 @@
elif self.target == 'Object':
if 'etag' not in actual:
return NonExistentHeader('etag')
- elif self.method == 'PUT' or self.method == 'COPY':
+ if 'last-modified' not in actual:
+ return NonExistentHeader('last-modified')
+ elif self.method == 'PUT':
if self.target == 'Object':
if 'etag' not in actual:
return NonExistentHeader('etag')
+ if 'last-modified' not in actual:
+ return NonExistentHeader('last-modified')
+ elif self.method == 'COPY':
+ if self.target == 'Object':
+ if 'etag' not in actual:
+ return NonExistentHeader('etag')
+ if 'last-modified' not in actual:
+ return NonExistentHeader('last-modified')
+ if 'x-copied-from' not in actual:
+ return NonExistentHeader('x-copied-from')
+ if 'x-copied-from-last-modified' not in actual:
+ return NonExistentHeader('x-copied-from-last-modified')
return None
@@ -122,11 +139,17 @@
return InvalidFormat(key, value)
elif key == 'content-type' and not value:
return InvalidFormat(key, value)
+ elif key == 'x-copied-from' and not re.match("\S+/\S+", value):
+ return InvalidFormat(key, value)
+ elif key == 'x-copied-from-last-modified' and not value:
+ return InvalidFormat(key, value)
elif key == 'x-trans-id' and \
not re.match("^tx[0-9a-f]{21}-[0-9a-f]{10}.*", value):
return InvalidFormat(key, value)
elif key == 'date' and not value:
return InvalidFormat(key, value)
+ elif key == 'last-modified' and not value:
+ return InvalidFormat(key, value)
elif key == 'accept-ranges' and not value == 'bytes':
return InvalidFormat(key, value)
elif key == 'etag' and not value.isalnum():
@@ -152,3 +175,54 @@
def get_details(self):
return {}
+
+
+class MatchesDictExceptForKeys(object):
+ """Matches two dictionaries. Verifies all items are equals except for those
+ identified by a list of keys.
+ """
+
+ def __init__(self, expected, excluded_keys=None):
+ self.expected = expected
+ self.excluded_keys = excluded_keys if excluded_keys is not None else []
+
+ def match(self, actual):
+ filtered_expected = helpers.dict_subtract(self.expected,
+ self.excluded_keys)
+ filtered_actual = helpers.dict_subtract(actual,
+ self.excluded_keys)
+ if filtered_actual != filtered_expected:
+ return DictMismatch(filtered_expected, filtered_actual)
+
+
+class DictMismatch(object):
+ """Mismatch between two dicts describes deltas"""
+
+ def __init__(self, expected, actual):
+ self.expected = expected
+ self.actual = actual
+ self.intersect = set(self.expected) & set(self.actual)
+ self.symmetric_diff = set(self.expected) ^ set(self.actual)
+
+ def describe(self):
+ msg = ""
+ if self.symmetric_diff:
+ only_expected = helpers.dict_subtract(self.expected, self.actual)
+ only_actual = helpers.dict_subtract(self.actual, self.expected)
+ if only_expected:
+ msg += "Only in expected:\n %s\n" % \
+ util.safe_repr(only_expected)
+ if only_actual:
+ msg += "Only in actual:\n %s\n" % \
+ util.safe_repr(only_actual)
+ diff_set = set(o for o in self.intersect if
+ self.expected[o] != self.actual[o])
+ if diff_set:
+ msg += "Differences:\n"
+ for o in diff_set:
+ msg += " %s: expected %s, actual %s\n" % (
+ o, self.expected[o], self.actual[o])
+ return msg
+
+ def get_details(self):
+ return {}
diff --git a/tempest/common/debug.py b/tempest/common/debug.py
index 228be7a..16e5ffe 100644
--- a/tempest/common/debug.py
+++ b/tempest/common/debug.py
@@ -14,7 +14,6 @@
from tempest.common import commands
from tempest import config
-
from tempest.openstack.common import log as logging
CONF = config.CONF
diff --git a/tempest/common/glance_http.py b/tempest/common/glance_http.py
index 55aca5a..5f35c85 100644
--- a/tempest/common/glance_http.py
+++ b/tempest/common/glance_http.py
@@ -19,15 +19,17 @@
import hashlib
import httplib
import json
-import OpenSSL
import posixpath
import re
-from six import moves
import socket
import StringIO
import struct
import urlparse
+
+import OpenSSL
+from six import moves
+
from tempest import exceptions as exc
from tempest.openstack.common import log as logging
diff --git a/tempest/common/isolated_creds.py b/tempest/common/isolated_creds.py
index 98b0116..f711f2f 100644
--- a/tempest/common/isolated_creds.py
+++ b/tempest/common/isolated_creds.py
@@ -330,7 +330,8 @@
# Maintained until tests are ported
LOG.info("Acquired isolated creds:\n credentials: %s"
% credentials)
- if CONF.service_available.neutron:
+ if (CONF.service_available.neutron and
+ not CONF.baremetal.driver_enabled):
network, subnet, router = self._create_network_resources(
credentials.tenant_id)
self.isolated_net_resources[credential_type] = (
@@ -372,31 +373,6 @@
LOG.warn('network with name: %s not found for delete' %
network_name)
- def _cleanup_ports(self, network_id):
- # TODO(mlavalle) This method will be removed once patch
- # https://review.openstack.org/#/c/46563/ merges in Neutron
- if not self.ports:
- if self.tempest_client:
- resp, resp_body = self.network_admin_client.list_ports()
- else:
- resp_body = self.network_admin_client.list_ports()
- self.ports = resp_body['ports']
- ports_to_delete = [
- port
- for port in self.ports
- if (port['network_id'] == network_id and
- port['device_owner'] != 'network:router_interface' and
- port['device_owner'] != 'network:dhcp')
- ]
- for port in ports_to_delete:
- try:
- LOG.info('Cleaning up port id %s, name %s' %
- (port['id'], port['name']))
- self.network_admin_client.delete_port(port['id'])
- except exceptions.NotFound:
- LOG.warn('Port id: %s, name %s not found for clean-up' %
- (port['id'], port['name']))
-
def _clear_isolated_net_resources(self):
net_client = self.network_admin_client
for cred in self.isolated_net_resources:
@@ -418,11 +394,6 @@
router['name'])
self._clear_isolated_router(router['id'], router['name'])
if (not self.network_resources or
- self.network_resources.get('network')):
- # TODO(mlavalle) This method call will be removed once patch
- # https://review.openstack.org/#/c/46563/ merges in Neutron
- self._cleanup_ports(network['id'])
- if (not self.network_resources or
self.network_resources.get('subnet')):
self._clear_isolated_subnet(subnet['id'], subnet['name'])
if (not self.network_resources or
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 9e0f4d3..ff92b67 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -16,12 +16,12 @@
import collections
import json
-from lxml import etree
import re
import string
import time
import jsonschema
+from lxml import etree
from tempest.common import http
from tempest.common.utils import misc as misc_utils
@@ -209,8 +209,9 @@
pattern = """Unexpected http success status code {0},
The expected status code is {1}"""
if ((not isinstance(expected_code, list) and
- (read_code != expected_code)) or (isinstance(expected_code,
- list) and (read_code not in expected_code))):
+ (read_code != expected_code)) or
+ (isinstance(expected_code, list) and
+ (read_code not in expected_code))):
details = pattern.format(read_code, expected_code)
raise exceptions.InvalidHttpSuccessCode(details)
diff --git a/tempest/common/ssh.py b/tempest/common/ssh.py
index 531887c..c06ce3b 100644
--- a/tempest/common/ssh.py
+++ b/tempest/common/ssh.py
@@ -16,11 +16,12 @@
import cStringIO
import select
-import six
import socket
import time
import warnings
+import six
+
from tempest import exceptions
from tempest.openstack.common import log as logging
diff --git a/tempest/common/utils/data_utils.py b/tempest/common/utils/data_utils.py
index 174e557..5a29ea0 100644
--- a/tempest/common/utils/data_utils.py
+++ b/tempest/common/utils/data_utils.py
@@ -71,3 +71,11 @@
if not base_text:
base_text = 'test'
return ''.join(itertools.islice(itertools.cycle(base_text), size))
+
+
+def random_bytes(size=1024):
+ """
+ Return size randomly selected bytes as a string.
+ """
+ return ''.join([chr(random.randint(0, 255))
+ for i in range(size)])
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 57a14a2..89904b2 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -11,9 +11,10 @@
# under the License.
import re
-import six
import time
+import six
+
from tempest.common import ssh
from tempest import config
from tempest import exceptions
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index d242c14..c4f1214 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -74,7 +74,7 @@
if (server_status == 'ERROR') and raise_on_error:
if 'fault' in body:
raise exceptions.BuildErrorException(body['fault'],
- server_id=server_id)
+ server_id=server_id)
else:
raise exceptions.BuildErrorException(server_id=server_id)
diff --git a/tempest/config.py b/tempest/config.py
index c83f500..4836c63 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -29,6 +29,18 @@
conf.register_opt(opt, group=opt_group.name)
+auth_group = cfg.OptGroup(name='auth',
+ title="Options for authentication and credentials")
+
+
+AuthGroup = [
+ cfg.StrOpt('test_accounts_file',
+ default='etc/accounts.yaml',
+ help="Path to the yaml file that contains the list of "
+ "credentials to use for running tests"),
+]
+
+
identity_group = cfg.OptGroup(name='identity',
title="Keystone Configuration Options")
@@ -261,6 +273,9 @@
cfg.BoolOpt('api_v3',
default=False,
help="If false, skip all nova v3 tests."),
+ cfg.BoolOpt('xml_api_v2',
+ default=True,
+ help="If false skip all v2 api tests with xml"),
cfg.BoolOpt('disk_config',
default=True,
help="If false, skip disk config tests"),
@@ -268,22 +283,31 @@
default=['all'],
help='A list of enabled compute extensions with a special '
'entry all which indicates every extension is enabled. '
- 'Each extension should be specified with alias name'),
+ 'Each extension should be specified with alias name. '
+ 'Empty list indicates all extensions are disabled'),
cfg.ListOpt('api_v3_extensions',
default=['all'],
help='A list of enabled v3 extensions with a special entry all'
' which indicates every extension is enabled. '
- 'Each extension should be specified with alias name'),
+ 'Each extension should be specified with alias name. '
+ 'Empty list indicates all extensions are disabled'),
cfg.BoolOpt('change_password',
default=False,
help="Does the test environment support changing the admin "
"password?"),
+ cfg.BoolOpt('console_output',
+ default=True,
+ help="Does the test environment support obtaining instance "
+ "serial console output?"),
cfg.BoolOpt('resize',
default=False,
help="Does the test environment support resizing?"),
cfg.BoolOpt('pause',
default=True,
help="Does the test environment support pausing?"),
+ cfg.BoolOpt('shelve',
+ default=True,
+ help="Does the test environment support shelving/unshelving?"),
cfg.BoolOpt('suspend',
default=True,
help="Does the test environment support suspend/resume?"),
@@ -314,7 +338,20 @@
cfg.BoolOpt('rescue',
default=True,
help='Does the test environment support instance rescue '
- 'mode?')
+ 'mode?'),
+ cfg.BoolOpt('enable_instance_password',
+ default=True,
+ help='Enables returning of the instance password by the '
+ 'relevant server API calls such as create, rebuild '
+ 'or rescue.'),
+ cfg.BoolOpt('interface_attach',
+ default=True,
+ help='Does the test environment support dynamic network '
+ 'interface attachment?'),
+ cfg.BoolOpt('snapshot',
+ default=True,
+ help='Does the test environment support creating snapshot '
+ 'images of running instances?')
]
@@ -441,7 +478,8 @@
cfg.ListOpt('api_extensions',
default=['all'],
help='A list of enabled network extensions with a special '
- 'entry all which indicates every extension is enabled'),
+ 'entry all which indicates every extension is enabled. '
+ 'Empty list indicates all extensions are disabled'),
cfg.BoolOpt('ipv6_subnet_attributes',
default=False,
help="Allow the execution of IPv6 subnet tests that use "
@@ -546,7 +584,8 @@
cfg.ListOpt('api_extensions',
default=['all'],
help='A list of enabled volume extensions with a special '
- 'entry all which indicates every extension is enabled'),
+ 'entry all which indicates every extension is enabled. '
+ 'Empty list indicates all extensions are disabled'),
cfg.BoolOpt('api_v1',
default=True,
help="Is the v1 volume API enabled"),
@@ -996,6 +1035,7 @@
def register_opts():
+ register_opt_group(cfg.CONF, auth_group, AuthGroup)
register_opt_group(cfg.CONF, compute_group, ComputeGroup)
register_opt_group(cfg.CONF, compute_features_group,
ComputeFeaturesGroup)
@@ -1043,7 +1083,12 @@
DEFAULT_CONFIG_FILE = "tempest.conf"
+ def __getattr__(self, attr):
+ # Handles config options from the default group
+ return getattr(cfg.CONF, attr)
+
def _set_attrs(self):
+ self.auth = cfg.CONF.auth
self.compute = cfg.CONF.compute
self.compute_feature_enabled = cfg.CONF['compute-feature-enabled']
self.identity = cfg.CONF.identity
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index 9d443cc..cc31fad 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -223,5 +223,8 @@
def __str__(self):
return ("Command '%s' returned non-zero exit status %d.\n"
- "stdout:\n%s\n"
- "stderr:\n%s" % (self.cmd, self.returncode, self.stdout, self.stderr))
+ "stdout:\n%s\n"
+ "stderr:\n%s" % (self.cmd,
+ self.returncode,
+ self.stdout,
+ self.stderr))
diff --git a/tempest/scenario/README.rst b/tempest/scenario/README.rst
index 835ba99..5a287d6 100644
--- a/tempest/scenario/README.rst
+++ b/tempest/scenario/README.rst
@@ -1,3 +1,5 @@
+.. _scenario_field_guide:
+
Tempest Field Guide to Scenario tests
=====================================
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index aa24c31..76d82aa 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -17,7 +17,6 @@
import logging
import os
import re
-import six
import subprocess
import time
@@ -27,6 +26,7 @@
import netaddr
from neutronclient.common import exceptions as exc
from novaclient import exceptions as nova_exceptions
+import six
from tempest.api.network import common as net_common
from tempest import auth
@@ -78,6 +78,11 @@
return cls._get_credentials(cls.isolated_creds.get_primary_creds,
'user')
+ @classmethod
+ def admin_credentials(cls):
+ return cls._get_credentials(cls.isolated_creds.get_admin_creds,
+ 'identity_admin')
+
class OfficialClientTest(tempest.test.BaseTestCase):
"""
@@ -113,6 +118,11 @@
cls.ceilometer_client = cls.manager.ceilometer_client
@classmethod
+ def tearDownClass(cls):
+ cls.isolated_creds.clear_isolated_creds()
+ super(OfficialClientTest, cls).tearDownClass()
+
+ @classmethod
def _get_credentials(cls, get_creds, ctype):
if CONF.compute.allow_tenant_isolation:
creds = get_creds()
@@ -350,6 +360,22 @@
return secgroup
+ def rebuild_server(self, server, client=None, image=None,
+ preserve_ephemeral=False, wait=True,
+ rebuild_kwargs=None):
+ if client is None:
+ client = self.compute_client
+ if image is None:
+ image = CONF.compute.image_ref
+ rebuild_kwargs = rebuild_kwargs or {}
+
+ LOG.debug("Rebuilding server (name: %s, image: %s, preserve eph: %s)",
+ server.name, image, preserve_ephemeral)
+ server.rebuild(image, preserve_ephemeral=preserve_ephemeral,
+ **rebuild_kwargs)
+ if wait:
+ self.status_timeout(client.servers, server.id, 'ACTIVE')
+
def create_server(self, client=None, name=None, image=None, flavor=None,
wait_on_boot=True, wait_on_delete=True,
create_kwargs={}):
@@ -485,6 +511,9 @@
return linux_client
def _log_console_output(self, servers=None):
+ if not CONF.compute_feature_enabled.console_output:
+ LOG.debug('Console output not supported, cannot log')
+ return
if not servers:
servers = self.compute_client.servers.list()
for server in servers:
@@ -1080,7 +1109,8 @@
try:
source.ping_host(dest)
except exceptions.SSHExecCommandFailed:
- LOG.exception('Failed to ping host via ssh connection')
+ LOG.warn('Failed to ping IP: %s via a ssh connection from: %s.'
+ % (dest, source.ssh_client.host))
return not should_succeed
return should_succeed
diff --git a/tempest/scenario/orchestration/test_autoscaling.py b/tempest/scenario/orchestration/test_autoscaling.py
index aa7b6f8..8894106 100644
--- a/tempest/scenario/orchestration/test_autoscaling.py
+++ b/tempest/scenario/orchestration/test_autoscaling.py
@@ -10,9 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import heatclient.exc as heat_exceptions
import time
+import heatclient.exc as heat_exceptions
+
from tempest import config
from tempest.scenario import manager
from tempest import test
diff --git a/tempest/scenario/test_aggregates_basic_ops.py b/tempest/scenario/test_aggregates_basic_ops.py
index 0059619..3ad5c69 100644
--- a/tempest/scenario/test_aggregates_basic_ops.py
+++ b/tempest/scenario/test_aggregates_basic_ops.py
@@ -23,7 +23,7 @@
LOG = logging.getLogger(__name__)
-class TestAggregatesBasicOps(manager.OfficialClientTest):
+class TestAggregatesBasicOps(manager.ScenarioTest):
"""
Creates an aggregate within an availability zone
Adds a host to the aggregate
@@ -33,74 +33,67 @@
Deletes aggregate
"""
@classmethod
+ def setUpClass(cls):
+ super(TestAggregatesBasicOps, cls).setUpClass()
+ cls.aggregates_client = cls.manager.aggregates_client
+ cls.hosts_client = cls.manager.hosts_client
+
+ @classmethod
def credentials(cls):
return cls.admin_credentials()
def _create_aggregate(self, **kwargs):
- aggregate = self.compute_client.aggregates.create(**kwargs)
+ _, aggregate = self.aggregates_client.create_aggregate(**kwargs)
+ self.addCleanup(self._delete_aggregate, aggregate)
aggregate_name = kwargs['name']
availability_zone = kwargs['availability_zone']
- self.assertEqual(aggregate.name, aggregate_name)
- self.assertEqual(aggregate.availability_zone, availability_zone)
- self.addCleanup(self._delete_aggregate, aggregate)
- LOG.debug("Aggregate %s created." % (aggregate.name))
+ self.assertEqual(aggregate['name'], aggregate_name)
+ self.assertEqual(aggregate['availability_zone'], availability_zone)
return aggregate
def _delete_aggregate(self, aggregate):
- self.compute_client.aggregates.delete(aggregate.id)
- LOG.debug("Aggregate %s deleted. " % (aggregate.name))
+ self.aggregates_client.delete_aggregate(aggregate['id'])
def _get_host_name(self):
- hosts = self.compute_client.hosts.list()
+ _, hosts = self.hosts_client.list_hosts()
self.assertTrue(len(hosts) >= 1)
- computes = [x for x in hosts if x.service == 'compute']
- return computes[0].host_name
+ computes = [x for x in hosts if x['service'] == 'compute']
+ return computes[0]['host_name']
- def _add_host(self, aggregate_name, host):
- aggregate = self.compute_client.aggregates.add_host(aggregate_name,
- host)
- self.addCleanup(self._remove_host, aggregate, host)
- self.assertIn(host, aggregate.hosts)
- LOG.debug("Host %s added to Aggregate %s." % (host, aggregate.name))
+ def _add_host(self, aggregate_id, host):
+ _, aggregate = self.aggregates_client.add_host(aggregate_id, host)
+ self.addCleanup(self._remove_host, aggregate['id'], host)
+ self.assertIn(host, aggregate['hosts'])
- def _remove_host(self, aggregate_name, host):
- aggregate = self.compute_client.aggregates.remove_host(aggregate_name,
- host)
- self.assertNotIn(host, aggregate.hosts)
- LOG.debug("Host %s removed to Aggregate %s." % (host, aggregate.name))
+ def _remove_host(self, aggregate_id, host):
+ _, aggregate = self.aggregates_client.remove_host(aggregate_id, host)
+ self.assertNotIn(host, aggregate['hosts'])
def _check_aggregate_details(self, aggregate, aggregate_name, azone,
hosts, metadata):
- aggregate = self.compute_client.aggregates.get(aggregate.id)
- self.assertEqual(aggregate_name, aggregate.name)
- self.assertEqual(azone, aggregate.availability_zone)
- self.assertEqual(aggregate.hosts, hosts)
+ _, aggregate = self.aggregates_client.get_aggregate(aggregate['id'])
+ self.assertEqual(aggregate_name, aggregate['name'])
+ self.assertEqual(azone, aggregate['availability_zone'])
+ self.assertEqual(hosts, aggregate['hosts'])
for meta_key in metadata.keys():
- self.assertIn(meta_key, aggregate.metadata)
- self.assertEqual(metadata[meta_key], aggregate.metadata[meta_key])
- LOG.debug("Aggregate %s details match." % aggregate.name)
+ self.assertIn(meta_key, aggregate['metadata'])
+ self.assertEqual(metadata[meta_key],
+ aggregate['metadata'][meta_key])
def _set_aggregate_metadata(self, aggregate, meta):
- aggregate = self.compute_client.aggregates.set_metadata(aggregate.id,
- meta)
+ _, aggregate = self.aggregates_client.set_metadata(aggregate['id'],
+ meta)
for key, value in meta.items():
- self.assertEqual(meta[key], aggregate.metadata[key])
- LOG.debug("Aggregate %s metadata updated successfully." %
- aggregate.name)
+ self.assertEqual(meta[key], aggregate['metadata'][key])
def _update_aggregate(self, aggregate, aggregate_name,
availability_zone):
- values = {}
- if aggregate_name:
- values.update({'name': aggregate_name})
- if availability_zone:
- values.update({'availability_zone': availability_zone})
- if values.keys():
- aggregate = self.compute_client.aggregates.update(aggregate.id,
- values)
- for key, values in values.items():
- self.assertEqual(getattr(aggregate, key), values)
+ _, aggregate = self.aggregates_client.update_aggregate(
+ aggregate['id'], name=aggregate_name,
+ availability_zone=availability_zone)
+ self.assertEqual(aggregate['name'], aggregate_name)
+ self.assertEqual(aggregate['availability_zone'], availability_zone)
return aggregate
@test.services('compute')
@@ -115,16 +108,17 @@
self._set_aggregate_metadata(aggregate, metadata)
host = self._get_host_name()
- self._add_host(aggregate, host)
+ self._add_host(aggregate['id'], host)
self._check_aggregate_details(aggregate, aggregate_name, az, [host],
metadata)
aggregate_name = data_utils.rand_name('renamed-aggregate-scenario')
- aggregate = self._update_aggregate(aggregate, aggregate_name, None)
+ # Updating the name alone. The az must be specified again otherwise
+ # the tempest client would send None in the put body
+ aggregate = self._update_aggregate(aggregate, aggregate_name, az)
- additional_metadata = {'foo': 'bar'}
- self._set_aggregate_metadata(aggregate, additional_metadata)
+ new_metadata = {'foo': 'bar'}
+ self._set_aggregate_metadata(aggregate, new_metadata)
- metadata.update(additional_metadata)
- self._check_aggregate_details(aggregate, aggregate.name, az, [host],
- metadata)
+ self._check_aggregate_details(aggregate, aggregate['name'], az,
+ [host], new_metadata)
diff --git a/tempest/scenario/test_baremetal_basic_ops.py b/tempest/scenario/test_baremetal_basic_ops.py
index f197c15..9ad6bc4 100644
--- a/tempest/scenario/test_baremetal_basic_ops.py
+++ b/tempest/scenario/test_baremetal_basic_ops.py
@@ -23,7 +23,7 @@
LOG = logging.getLogger(__name__)
-class BaremetalBasicOpsPXESSH(manager.BaremetalScenarioTest):
+class BaremetalBasicOps(manager.BaremetalScenarioTest):
"""
This smoke test tests the pxe_ssh Ironic driver. It follows this basic
set of operations:
@@ -35,10 +35,76 @@
* Verifies SSH connectivity using created keypair via fixed IP
* Associates a floating ip
* Verifies SSH connectivity using created keypair via floating IP
+ * Verifies instance rebuild with ephemeral partition preservation
* Deletes instance
* Monitors the associated Ironic node for power and
expected state transitions
"""
+ def rebuild_instance(self, preserve_ephemeral=False):
+ self.rebuild_server(self.instance,
+ preserve_ephemeral=preserve_ephemeral,
+ wait=False)
+
+ node = self.get_node(instance_id=self.instance.id)
+ self.instance = self.compute_client.servers.get(self.instance.id)
+
+ self.addCleanup_with_wait(self.compute_client.servers,
+ self.instance.id,
+ cleanup_callable=self.delete_wrapper,
+ cleanup_args=[self.instance])
+
+ # We should remain on the same node
+ self.assertEqual(self.node.uuid, node.uuid)
+ self.node = node
+
+ self.status_timeout(self.compute_client.servers, self.instance.id,
+ 'REBUILD')
+ self.status_timeout(self.compute_client.servers, self.instance.id,
+ 'ACTIVE')
+
+ def create_remote_file(self, client, filename):
+ """Create a file on the remote client connection.
+
+ After creating the file, force a filesystem sync. Otherwise,
+ if we issue a rebuild too quickly, the file may not exist.
+ """
+ client.exec_command('sudo touch ' + filename)
+ client.exec_command('sync')
+
+ def verify_partition(self, client, label, mount, gib_size):
+ """Verify a labeled partition's mount point and size."""
+ LOG.info("Looking for partition %s mounted on %s" % (label, mount))
+
+ # Validate we have a device with the given partition label
+ cmd = "/sbin/blkid | grep '%s' | cut -d':' -f1" % label
+ device = client.exec_command(cmd).rstrip('\n')
+ LOG.debug("Partition device is %s" % device)
+ self.assertNotEqual('', device)
+
+ # Validate the mount point for the device
+ cmd = "mount | grep '%s' | cut -d' ' -f3" % device
+ actual_mount = client.exec_command(cmd).rstrip('\n')
+ LOG.debug("Partition mount point is %s" % actual_mount)
+ self.assertEqual(actual_mount, mount)
+
+ # Validate the partition size matches what we expect
+ numbers = '0123456789'
+ devnum = device.replace('/dev/', '')
+ cmd = "cat /sys/block/%s/%s/size" % (devnum.rstrip(numbers), devnum)
+ num_bytes = client.exec_command(cmd).rstrip('\n')
+ num_bytes = int(num_bytes) * 512
+ actual_gib_size = num_bytes / (1024 * 1024 * 1024)
+ LOG.debug("Partition size is %d GiB" % actual_gib_size)
+ self.assertEqual(actual_gib_size, gib_size)
+
+ def get_flavor_ephemeral_size(self):
+ """Returns size of the ephemeral partition in GiB."""
+ f_id = self.instance.flavor['id']
+ ephemeral = self.compute_client.flavors.get(f_id).ephemeral
+ if ephemeral != 'N/A':
+ return int(ephemeral)
+ return None
+
def add_floating_ip(self):
floating_ip = self.compute_client.floating_ips.create()
self.instance.add_floating_ip(floating_ip)
@@ -53,10 +119,32 @@
@test.services('baremetal', 'compute', 'image', 'network')
def test_baremetal_server_ops(self):
+ test_filename = '/mnt/rebuild_test.txt'
self.add_keypair()
self.boot_instance()
self.validate_ports()
self.verify_connectivity()
floating_ip = self.add_floating_ip()
self.verify_connectivity(ip=floating_ip)
+
+ vm_client = self.get_remote_client(self.instance)
+
+ # We expect the ephemeral partition to be mounted on /mnt and to have
+ # the same size as our flavor definition.
+ eph_size = self.get_flavor_ephemeral_size()
+ self.assertIsNotNone(eph_size)
+ self.verify_partition(vm_client, 'ephemeral0', '/mnt', eph_size)
+
+ # Create the test file
+ self.create_remote_file(vm_client, test_filename)
+
+ # Rebuild and preserve the ephemeral partition
+ self.rebuild_instance(True)
+ self.verify_connectivity()
+
+ # Check that we maintained our data
+ vm_client = self.get_remote_client(self.instance)
+ self.verify_partition(vm_client, 'ephemeral0', '/mnt', eph_size)
+ vm_client.exec_command('ls ' + test_filename)
+
self.terminate_instance()
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 7dc817d..bba034b 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -14,13 +14,15 @@
# License for the specific language governing permissions and limitations
# under the License.
import collections
-
import re
+import testtools
+
from tempest.api.network import common as net_common
from tempest.common import debug
from tempest.common.utils import data_utils
from tempest import config
+from tempest import exceptions
from tempest.openstack.common import log as logging
from tempest.scenario import manager
from tempest import test
@@ -221,23 +223,31 @@
self.addCleanup(self.delete_wrapper, server)
def check_ports():
- port_list = [port for port in
- self._list_ports(device_id=server.id)
- if port != old_port]
- return len(port_list) == 1
+ self.new_port_list = [port for port in
+ self._list_ports(device_id=server.id)
+ if port != old_port]
+ return len(self.new_port_list) == 1
- test.call_until_true(check_ports, 60, 1)
- new_port_list = [p for p in
- self._list_ports(device_id=server.id)
- if p != old_port]
- self.assertEqual(1, len(new_port_list))
- new_port = new_port_list[0]
+ if not test.call_until_true(check_ports, CONF.network.build_timeout,
+ CONF.network.build_interval):
+ raise exceptions.TimeoutException("No new port attached to the "
+ "server in time (%s sec) !"
+ % CONF.network.build_timeout)
new_port = net_common.DeletablePort(client=self.network_client,
- **new_port)
- new_nic_list = self._get_server_nics(ssh_client)
- diff_list = [n for n in new_nic_list if n not in old_nic_list]
- self.assertEqual(1, len(diff_list))
- num, new_nic = diff_list[0]
+ **self.new_port_list[0])
+
+ def check_new_nic():
+ new_nic_list = self._get_server_nics(ssh_client)
+ self.diff_list = [n for n in new_nic_list if n not in old_nic_list]
+ return len(self.diff_list) == 1
+
+ if not test.call_until_true(check_new_nic, CONF.network.build_timeout,
+ CONF.network.build_interval):
+ raise exceptions.TimeoutException("Interface not visible on the "
+ "guest after %s sec"
+ % CONF.network.build_timeout)
+
+ num, new_nic = self.diff_list[0]
ssh_client.assign_static_ip(nic=new_nic,
addr=new_port.fixed_ips[0]['ip_address'])
ssh_client.turn_nic_on(nic=new_nic)
@@ -347,6 +357,8 @@
msg="after re-associate "
"floating ip")
+ @testtools.skipUnless(CONF.compute_feature_enabled.interface_attach,
+ 'NIC hotplug not available')
@test.attr(type='smoke')
@test.services('compute', 'network')
def test_hotplug_nic(self):
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index 8058b3d..ecb802f 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -213,10 +213,16 @@
myport = (tenant.router.id, tenant.subnet.id)
router_ports = [(i['device_id'], i['fixed_ips'][0]['subnet_id']) for i
in self.network_client.list_ports()['ports']
- if i['device_owner'] == 'network:router_interface']
+ if self._is_router_port(i)]
self.assertIn(myport, router_ports)
+ def _is_router_port(self, port):
+ """Return True if port is a router interface."""
+ # NOTE(armando-migliaccio): match device owner for both centralized
+ # and distributed routers; 'device_owner' is "" by default.
+ return port['device_owner'].startswith('network:router_interface')
+
def _create_server(self, name, tenant, security_groups=None):
"""
creates a server and assigns to security group
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index 7dd662d..ffdd006 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import testtools
+
from tempest import config
from tempest.openstack.common import log
from tempest.scenario import manager
@@ -71,6 +73,8 @@
def _set_floating_ip_to_server(self, server, floating_ip):
server.add_floating_ip(floating_ip)
+ @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
+ 'Snapshotting is not available.')
@test.services('compute', 'network', 'image')
def test_snapshot_pattern(self):
# prepare for booting a instance
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index be27024..4783273 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -16,6 +16,7 @@
import time
from cinderclient import exceptions as cinder_exceptions
+import testtools
from tempest.common.utils import data_utils
from tempest import config
@@ -150,6 +151,8 @@
self.assertEqual(self.timestamp, got_timestamp)
@tempest.test.skip_because(bug="1205344")
+ @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
+ 'Snapshotting is not available.')
@tempest.test.services('compute', 'network', 'volume', 'image')
def test_stamp_pattern(self):
# prepare for booting a instance
diff --git a/tempest/services/compute/json/agents_client.py b/tempest/services/compute/json/agents_client.py
index 4f6602f..5b76a56 100644
--- a/tempest/services/compute/json/agents_client.py
+++ b/tempest/services/compute/json/agents_client.py
@@ -15,8 +15,8 @@
import json
import urllib
-from tempest.api_schema.compute import agents as common_schema
-from tempest.api_schema.compute.v2 import agents as schema
+from tempest.api_schema.response.compute import agents as common_schema
+from tempest.api_schema.response.compute.v2 import agents as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/json/aggregates_client.py b/tempest/services/compute/json/aggregates_client.py
index 71d6f63..1cb010d 100644
--- a/tempest/services/compute/json/aggregates_client.py
+++ b/tempest/services/compute/json/aggregates_client.py
@@ -15,8 +15,8 @@
import json
-from tempest.api_schema.compute import aggregates as schema
-from tempest.api_schema.compute.v2 import aggregates as v2_schema
+from tempest.api_schema.response.compute import aggregates as schema
+from tempest.api_schema.response.compute.v2 import aggregates as v2_schema
from tempest.common import rest_client
from tempest import config
from tempest import exceptions
diff --git a/tempest/services/compute/json/availability_zone_client.py b/tempest/services/compute/json/availability_zone_client.py
index 1c067e8..00f8330 100644
--- a/tempest/services/compute/json/availability_zone_client.py
+++ b/tempest/services/compute/json/availability_zone_client.py
@@ -15,7 +15,7 @@
import json
-from tempest.api_schema.compute.v2 import availability_zone as schema
+from tempest.api_schema.response.compute.v2 import availability_zone as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/json/certificates_client.py b/tempest/services/compute/json/certificates_client.py
index 1d04628..356ded2 100644
--- a/tempest/services/compute/json/certificates_client.py
+++ b/tempest/services/compute/json/certificates_client.py
@@ -15,8 +15,8 @@
import json
-from tempest.api_schema.compute import certificates as schema
-from tempest.api_schema.compute.v2 import certificates as v2schema
+from tempest.api_schema.response.compute import certificates as schema
+from tempest.api_schema.response.compute.v2 import certificates as v2schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/json/extensions_client.py b/tempest/services/compute/json/extensions_client.py
index ed2b14d..41d1c4e 100644
--- a/tempest/services/compute/json/extensions_client.py
+++ b/tempest/services/compute/json/extensions_client.py
@@ -15,7 +15,7 @@
import json
-from tempest.api_schema.compute.v2 import extensions as schema
+from tempest.api_schema.response.compute.v2 import extensions as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/json/fixed_ips_client.py b/tempest/services/compute/json/fixed_ips_client.py
index f2d5cbe..5903334 100644
--- a/tempest/services/compute/json/fixed_ips_client.py
+++ b/tempest/services/compute/json/fixed_ips_client.py
@@ -15,7 +15,7 @@
import json
-from tempest.api_schema.compute.v2 import fixed_ips as schema
+from tempest.api_schema.response.compute.v2 import fixed_ips as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/json/flavors_client.py b/tempest/services/compute/json/flavors_client.py
index 89cbe1d..5452f3a 100644
--- a/tempest/services/compute/json/flavors_client.py
+++ b/tempest/services/compute/json/flavors_client.py
@@ -16,11 +16,11 @@
import json
import urllib
-from tempest.api_schema.compute import flavors as common_schema
-from tempest.api_schema.compute import flavors_access as schema_access
-from tempest.api_schema.compute import flavors_extra_specs \
+from tempest.api_schema.response.compute import flavors as common_schema
+from tempest.api_schema.response.compute import flavors_access as schema_access
+from tempest.api_schema.response.compute import flavors_extra_specs \
as schema_extra_specs
-from tempest.api_schema.compute.v2 import flavors as v2schema
+from tempest.api_schema.response.compute.v2 import flavors as v2schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/json/floating_ips_client.py b/tempest/services/compute/json/floating_ips_client.py
index cd195f4..8b020d0 100644
--- a/tempest/services/compute/json/floating_ips_client.py
+++ b/tempest/services/compute/json/floating_ips_client.py
@@ -16,7 +16,7 @@
import json
import urllib
-from tempest.api_schema.compute.v2 import floating_ips as schema
+from tempest.api_schema.response.compute.v2 import floating_ips as schema
from tempest.common import rest_client
from tempest import config
from tempest import exceptions
diff --git a/tempest/services/compute/json/hosts_client.py b/tempest/services/compute/json/hosts_client.py
index 342f946..8644173 100644
--- a/tempest/services/compute/json/hosts_client.py
+++ b/tempest/services/compute/json/hosts_client.py
@@ -15,8 +15,8 @@
import json
import urllib
-from tempest.api_schema.compute import hosts as schema
-from tempest.api_schema.compute.v2 import hosts as v2_schema
+from tempest.api_schema.response.compute import hosts as schema
+from tempest.api_schema.response.compute.v2 import hosts as v2_schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/json/hypervisor_client.py b/tempest/services/compute/json/hypervisor_client.py
index 30228b3..8eacf61 100644
--- a/tempest/services/compute/json/hypervisor_client.py
+++ b/tempest/services/compute/json/hypervisor_client.py
@@ -15,8 +15,8 @@
import json
-from tempest.api_schema.compute import hypervisors as common_schema
-from tempest.api_schema.compute.v2 import hypervisors as v2schema
+from tempest.api_schema.response.compute import hypervisors as common_schema
+from tempest.api_schema.response.compute.v2 import hypervisors as v2schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/json/images_client.py b/tempest/services/compute/json/images_client.py
index af7752a..9877391 100644
--- a/tempest/services/compute/json/images_client.py
+++ b/tempest/services/compute/json/images_client.py
@@ -16,7 +16,7 @@
import json
import urllib
-from tempest.api_schema.compute.v2 import images as schema
+from tempest.api_schema.response.compute.v2 import images as schema
from tempest.common import rest_client
from tempest.common import waiters
from tempest import config
diff --git a/tempest/services/compute/json/instance_usage_audit_log_client.py b/tempest/services/compute/json/instance_usage_audit_log_client.py
index 4700ca7..4b0362b 100644
--- a/tempest/services/compute/json/instance_usage_audit_log_client.py
+++ b/tempest/services/compute/json/instance_usage_audit_log_client.py
@@ -15,7 +15,7 @@
import json
-from tempest.api_schema.compute.v2 import instance_usage_audit_logs \
+from tempest.api_schema.response.compute.v2 import instance_usage_audit_logs \
as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/json/interfaces_client.py b/tempest/services/compute/json/interfaces_client.py
index cdac8b7..83c253a 100644
--- a/tempest/services/compute/json/interfaces_client.py
+++ b/tempest/services/compute/json/interfaces_client.py
@@ -16,9 +16,9 @@
import json
import time
-from tempest.api_schema.compute import interfaces as common_schema
-from tempest.api_schema.compute import servers as servers_schema
-from tempest.api_schema.compute.v2 import interfaces as schema
+from tempest.api_schema.response.compute import interfaces as common_schema
+from tempest.api_schema.response.compute import servers as servers_schema
+from tempest.api_schema.response.compute.v2 import interfaces as schema
from tempest.common import rest_client
from tempest import config
from tempest import exceptions
diff --git a/tempest/services/compute/json/keypairs_client.py b/tempest/services/compute/json/keypairs_client.py
index be93789..31c42a5 100644
--- a/tempest/services/compute/json/keypairs_client.py
+++ b/tempest/services/compute/json/keypairs_client.py
@@ -15,8 +15,8 @@
import json
-from tempest.api_schema.compute import keypairs as common_schema
-from tempest.api_schema.compute.v2 import keypairs as schema
+from tempest.api_schema.response.compute import keypairs as common_schema
+from tempest.api_schema.response.compute.v2 import keypairs as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/json/limits_client.py b/tempest/services/compute/json/limits_client.py
index e503bef..81c602b 100644
--- a/tempest/services/compute/json/limits_client.py
+++ b/tempest/services/compute/json/limits_client.py
@@ -15,7 +15,7 @@
import json
-from tempest.api_schema.compute.v2 import limits as schema
+from tempest.api_schema.response.compute.v2 import limits as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/json/migrations_client.py b/tempest/services/compute/json/migrations_client.py
index beef5d2..f4abbb2 100644
--- a/tempest/services/compute/json/migrations_client.py
+++ b/tempest/services/compute/json/migrations_client.py
@@ -15,7 +15,7 @@
import json
import urllib
-from tempest.api_schema.compute import migrations as schema
+from tempest.api_schema.response.compute import migrations as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/json/networks_client.py b/tempest/services/compute/json/networks_client.py
new file mode 100644
index 0000000..40eb1a6
--- /dev/null
+++ b/tempest/services/compute/json/networks_client.py
@@ -0,0 +1,40 @@
+# 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 json
+
+from tempest.common import rest_client
+from tempest import config
+
+CONF = config.CONF
+
+
+class NetworksClientJSON(rest_client.RestClient):
+
+ def __init__(self, auth_provider):
+ super(NetworksClientJSON, self).__init__(auth_provider)
+ self.service = CONF.compute.catalog_type
+
+ def list_networks(self):
+ resp, body = self.get("os-networks")
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return resp, body['networks']
+
+ def get_network(self, network_id):
+ resp, body = self.get("os-networks/%s" % str(network_id))
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return resp, body['network']
diff --git a/tempest/services/compute/json/quotas_client.py b/tempest/services/compute/json/quotas_client.py
index 14b7100..b691529 100644
--- a/tempest/services/compute/json/quotas_client.py
+++ b/tempest/services/compute/json/quotas_client.py
@@ -15,8 +15,9 @@
import json
-from tempest.api_schema.compute.v2 import quota_classes as classes_schema
-from tempest.api_schema.compute.v2 import quotas as schema
+from tempest.api_schema.response.compute.v2\
+ import quota_classes as classes_schema
+from tempest.api_schema.response.compute.v2 import quotas as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/json/security_group_default_rules_client.py b/tempest/services/compute/json/security_group_default_rules_client.py
new file mode 100644
index 0000000..6d29837
--- /dev/null
+++ b/tempest/services/compute/json/security_group_default_rules_client.py
@@ -0,0 +1,74 @@
+# Copyright 2014 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.common import rest_client
+from tempest import config
+
+CONF = config.CONF
+
+
+class SecurityGroupDefaultRulesClientJSON(rest_client.RestClient):
+
+ def __init__(self, auth_provider):
+ super(SecurityGroupDefaultRulesClientJSON,
+ self).__init__(auth_provider)
+ self.service = CONF.compute.catalog_type
+
+ def create_security_default_group_rule(self, ip_protocol, from_port,
+ to_port, **kwargs):
+ """
+ Creating security group default rules.
+ ip_protocol : ip_protocol (icmp, tcp, udp).
+ from_port: Port at start of range.
+ to_port : Port at end of range.
+ cidr : CIDR for address range.
+ """
+ post_body = {
+ 'ip_protocol': ip_protocol,
+ 'from_port': from_port,
+ 'to_port': to_port,
+ 'cidr': kwargs.get('cidr'),
+ }
+ post_body = json.dumps({'security_group_default_rule': post_body})
+ url = 'os-security-group-default-rules'
+ resp, body = self.post(url, post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return resp, body['security_group_default_rule']
+
+ def delete_security_group_default_rule(self,
+ security_group_default_rule_id):
+ """Deletes the provided Security Group default rule."""
+ resp, body = self.delete('os-security-group-default-rules/%s' % str(
+ security_group_default_rule_id))
+ self.expected_success(204, resp.status)
+ return resp, body
+
+ def list_security_group_default_rules(self):
+ """List all Security Group default rules."""
+ resp, body = self.get('os-security-group-default-rules')
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return resp, body['security_group_default_rules']
+
+ def get_security_group_default_rule(self, security_group_default_rule_id):
+ """Return the details of provided Security Group default rule."""
+ resp, body = self.get('os-security-group-default-rules/%s' % str(
+ security_group_default_rule_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return resp, body['security_group_default_rule']
diff --git a/tempest/services/compute/json/security_groups_client.py b/tempest/services/compute/json/security_groups_client.py
index a86f3df..29859a9 100644
--- a/tempest/services/compute/json/security_groups_client.py
+++ b/tempest/services/compute/json/security_groups_client.py
@@ -16,7 +16,7 @@
import json
import urllib
-from tempest.api_schema.compute.v2 import security_groups as schema
+from tempest.api_schema.response.compute.v2 import security_groups as schema
from tempest.common import rest_client
from tempest import config
from tempest import exceptions
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 80bb711..f44be29 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -18,8 +18,8 @@
import time
import urllib
-from tempest.api_schema.compute import servers as common_schema
-from tempest.api_schema.compute.v2 import servers as schema
+from tempest.api_schema.response.compute import servers as common_schema
+from tempest.api_schema.response.compute.v2 import servers as schema
from tempest.common import rest_client
from tempest.common import waiters
from tempest import config
@@ -93,7 +93,11 @@
# with return reservation id set True
if 'reservation_id' in body:
return resp, body
- self.validate_response(schema.create_server, resp, body)
+ if CONF.compute_feature_enabled.enable_instance_password:
+ create_schema = schema.create_server_with_admin_pass
+ else:
+ create_schema = schema.create_server
+ self.validate_response(create_schema, resp, body)
return resp, body['server']
def update_server(self, server_id, name=None, meta=None, accessIPv4=None,
@@ -269,7 +273,12 @@
if 'disk_config' in kwargs:
kwargs['OS-DCF:diskConfig'] = kwargs['disk_config']
del kwargs['disk_config']
- return self.action(server_id, 'rebuild', 'server', None, **kwargs)
+ if CONF.compute_feature_enabled.enable_instance_password:
+ rebuild_schema = schema.rebuild_server_with_admin_pass
+ else:
+ rebuild_schema = schema.rebuild_server
+ return self.action(server_id, 'rebuild', 'server',
+ rebuild_schema, **kwargs)
def resize(self, server_id, flavor_ref, **kwargs):
"""Changes the flavor of a server."""
@@ -450,7 +459,8 @@
def rescue_server(self, server_id, **kwargs):
"""Rescue the provided server."""
- return self.action(server_id, 'rescue', 'adminPass', None, **kwargs)
+ return self.action(server_id, 'rescue', 'adminPass',
+ schema.rescue_server, **kwargs)
def unrescue_server(self, server_id):
"""Unrescue the provided server."""
diff --git a/tempest/services/compute/json/services_client.py b/tempest/services/compute/json/services_client.py
index d58ca6f..e56263c 100644
--- a/tempest/services/compute/json/services_client.py
+++ b/tempest/services/compute/json/services_client.py
@@ -17,7 +17,7 @@
import json
import urllib
-from tempest.api_schema.compute import services as schema
+from tempest.api_schema.response.compute import services as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/json/tenant_usages_client.py b/tempest/services/compute/json/tenant_usages_client.py
index f8adae7..a0b9b4a 100644
--- a/tempest/services/compute/json/tenant_usages_client.py
+++ b/tempest/services/compute/json/tenant_usages_client.py
@@ -16,7 +16,7 @@
import json
import urllib
-from tempest.api_schema.compute.v2 import tenant_usages as schema
+from tempest.api_schema.response.compute.v2 import tenant_usages as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/json/volumes_extensions_client.py b/tempest/services/compute/json/volumes_extensions_client.py
index d1014af..673e365 100644
--- a/tempest/services/compute/json/volumes_extensions_client.py
+++ b/tempest/services/compute/json/volumes_extensions_client.py
@@ -17,7 +17,7 @@
import time
import urllib
-from tempest.api_schema.compute.v2 import volumes as schema
+from tempest.api_schema.response.compute.v2 import volumes as schema
from tempest.common import rest_client
from tempest import config
from tempest import exceptions
diff --git a/tempest/services/compute/v3/json/agents_client.py b/tempest/services/compute/v3/json/agents_client.py
index 31314b7..ffca142 100644
--- a/tempest/services/compute/v3/json/agents_client.py
+++ b/tempest/services/compute/v3/json/agents_client.py
@@ -15,8 +15,8 @@
import json
import urllib
-from tempest.api_schema.compute import agents as common_schema
-from tempest.api_schema.compute.v3 import agents as schema
+from tempest.api_schema.response.compute import agents as common_schema
+from tempest.api_schema.response.compute.v3 import agents as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/v3/json/aggregates_client.py b/tempest/services/compute/v3/json/aggregates_client.py
index d9b7930..960fe05 100644
--- a/tempest/services/compute/v3/json/aggregates_client.py
+++ b/tempest/services/compute/v3/json/aggregates_client.py
@@ -15,8 +15,8 @@
import json
-from tempest.api_schema.compute import aggregates as schema
-from tempest.api_schema.compute.v3 import aggregates as v3_schema
+from tempest.api_schema.response.compute import aggregates as schema
+from tempest.api_schema.response.compute.v3 import aggregates as v3_schema
from tempest.common import rest_client
from tempest import config
from tempest import exceptions
diff --git a/tempest/services/compute/v3/json/availability_zone_client.py b/tempest/services/compute/v3/json/availability_zone_client.py
index bf74e68..0da78da 100644
--- a/tempest/services/compute/v3/json/availability_zone_client.py
+++ b/tempest/services/compute/v3/json/availability_zone_client.py
@@ -15,7 +15,7 @@
import json
-from tempest.api_schema.compute.v3 import availability_zone as schema
+from tempest.api_schema.response.compute.v3 import availability_zone as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/v3/json/certificates_client.py b/tempest/services/compute/v3/json/certificates_client.py
index be9b3c3..42e9d5a 100644
--- a/tempest/services/compute/v3/json/certificates_client.py
+++ b/tempest/services/compute/v3/json/certificates_client.py
@@ -15,8 +15,8 @@
import json
-from tempest.api_schema.compute import certificates as schema
-from tempest.api_schema.compute.v3 import certificates as v3schema
+from tempest.api_schema.response.compute import certificates as schema
+from tempest.api_schema.response.compute.v3 import certificates as v3schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/v3/json/extensions_client.py b/tempest/services/compute/v3/json/extensions_client.py
index 13292db..f172efd 100644
--- a/tempest/services/compute/v3/json/extensions_client.py
+++ b/tempest/services/compute/v3/json/extensions_client.py
@@ -15,7 +15,7 @@
import json
-from tempest.api_schema.compute.v3 import extensions as schema
+from tempest.api_schema.response.compute.v3 import extensions as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/v3/json/flavors_client.py b/tempest/services/compute/v3/json/flavors_client.py
index 5afab5a..d1eee5b 100644
--- a/tempest/services/compute/v3/json/flavors_client.py
+++ b/tempest/services/compute/v3/json/flavors_client.py
@@ -16,11 +16,11 @@
import json
import urllib
-from tempest.api_schema.compute import flavors as common_schema
-from tempest.api_schema.compute import flavors_access as schema_access
-from tempest.api_schema.compute import flavors_extra_specs \
+from tempest.api_schema.response.compute import flavors as common_schema
+from tempest.api_schema.response.compute import flavors_access as schema_access
+from tempest.api_schema.response.compute import flavors_extra_specs \
as schema_extra_specs
-from tempest.api_schema.compute.v3 import flavors as v3schema
+from tempest.api_schema.response.compute.v3 import flavors as v3schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/v3/json/hosts_client.py b/tempest/services/compute/v3/json/hosts_client.py
index d2eb43d..476afad 100644
--- a/tempest/services/compute/v3/json/hosts_client.py
+++ b/tempest/services/compute/v3/json/hosts_client.py
@@ -15,8 +15,8 @@
import json
import urllib
-from tempest.api_schema.compute import hosts as schema
-from tempest.api_schema.compute.v3 import hosts as v3_schema
+from tempest.api_schema.response.compute import hosts as schema
+from tempest.api_schema.response.compute.v3 import hosts as v3_schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/v3/json/hypervisor_client.py b/tempest/services/compute/v3/json/hypervisor_client.py
index 51468c9..507157a 100644
--- a/tempest/services/compute/v3/json/hypervisor_client.py
+++ b/tempest/services/compute/v3/json/hypervisor_client.py
@@ -15,8 +15,8 @@
import json
-from tempest.api_schema.compute import hypervisors as common_schema
-from tempest.api_schema.compute.v3 import hypervisors as v3schema
+from tempest.api_schema.response.compute import hypervisors as common_schema
+from tempest.api_schema.response.compute.v3 import hypervisors as v3schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/v3/json/interfaces_client.py b/tempest/services/compute/v3/json/interfaces_client.py
index e66ccaa..e99c124 100644
--- a/tempest/services/compute/v3/json/interfaces_client.py
+++ b/tempest/services/compute/v3/json/interfaces_client.py
@@ -16,9 +16,9 @@
import json
import time
-from tempest.api_schema.compute import interfaces as common_schema
-from tempest.api_schema.compute import servers as servers_schema
-from tempest.api_schema.compute.v3 import interfaces as schema
+from tempest.api_schema.response.compute import interfaces as common_schema
+from tempest.api_schema.response.compute import servers as servers_schema
+from tempest.api_schema.response.compute.v3 import interfaces as schema
from tempest.common import rest_client
from tempest import config
from tempest import exceptions
diff --git a/tempest/services/compute/v3/json/keypairs_client.py b/tempest/services/compute/v3/json/keypairs_client.py
index f090d7d..a290acb 100644
--- a/tempest/services/compute/v3/json/keypairs_client.py
+++ b/tempest/services/compute/v3/json/keypairs_client.py
@@ -15,8 +15,8 @@
import json
-from tempest.api_schema.compute import keypairs as common_schema
-from tempest.api_schema.compute.v3 import keypairs as schema
+from tempest.api_schema.response.compute import keypairs as common_schema
+from tempest.api_schema.response.compute.v3 import keypairs as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/v3/json/migration_client.py b/tempest/services/compute/v3/json/migration_client.py
index c821567..bf1ae85 100644
--- a/tempest/services/compute/v3/json/migration_client.py
+++ b/tempest/services/compute/v3/json/migration_client.py
@@ -15,7 +15,7 @@
import json
import urllib
-from tempest.api_schema.compute import migrations as schema
+from tempest.api_schema.response.compute import migrations as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/v3/json/quotas_client.py b/tempest/services/compute/v3/json/quotas_client.py
index 37a8906..f9aa9e9 100644
--- a/tempest/services/compute/v3/json/quotas_client.py
+++ b/tempest/services/compute/v3/json/quotas_client.py
@@ -15,7 +15,7 @@
import json
-from tempest.api_schema.compute.v3 import quotas as schema
+from tempest.api_schema.response.compute.v3 import quotas as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
index a5b31d3..89e282d 100644
--- a/tempest/services/compute/v3/json/servers_client.py
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -19,8 +19,8 @@
import time
import urllib
-from tempest.api_schema.compute import servers as common_schema
-from tempest.api_schema.compute.v3 import servers as schema
+from tempest.api_schema.response.compute import servers as common_schema
+from tempest.api_schema.response.compute.v3 import servers as schema
from tempest.common import rest_client
from tempest.common import waiters
from tempest import config
@@ -96,7 +96,11 @@
# with return reservation id set True
if 'servers_reservation' in body:
return resp, body['servers_reservation']
- self.validate_response(schema.create_server, resp, body)
+ if CONF.compute_feature_enabled.enable_instance_password:
+ create_schema = schema.create_server_with_admin_pass
+ else:
+ create_schema = schema.create_server
+ self.validate_response(create_schema, resp, body)
return resp, body['server']
def update_server(self, server_id, name=None, meta=None, access_ip_v4=None,
@@ -272,7 +276,12 @@
if 'disk_config' in kwargs:
kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
del kwargs['disk_config']
- return self.action(server_id, 'rebuild', 'server', None, **kwargs)
+ if CONF.compute_feature_enabled.enable_instance_password:
+ rebuild_schema = schema.rebuild_server_with_admin_pass
+ else:
+ rebuild_schema = schema.rebuild_server
+ return self.action(server_id, 'rebuild', 'server',
+ rebuild_schema, **kwargs)
def resize(self, server_id, flavor_ref, **kwargs):
"""Changes the flavor of a server."""
@@ -445,8 +454,16 @@
def rescue_server(self, server_id, **kwargs):
"""Rescue the provided server."""
- return self.action(server_id, 'rescue', 'admin_password',
- None, **kwargs)
+ post_body = json.dumps({'rescue': kwargs})
+ resp, body = self.post('servers/%s/action' % str(server_id),
+ post_body)
+ if CONF.compute_feature_enabled.enable_instance_password:
+ rescue_schema = schema.rescue_server_with_admin_pass
+ else:
+ rescue_schema = schema.rescue_server
+ body = json.loads(body)
+ self.validate_response(rescue_schema, resp, body)
+ return resp, body
def unrescue_server(self, server_id):
"""Unrescue the provided server."""
diff --git a/tempest/services/compute/v3/json/services_client.py b/tempest/services/compute/v3/json/services_client.py
index 96ff580..0645287 100644
--- a/tempest/services/compute/v3/json/services_client.py
+++ b/tempest/services/compute/v3/json/services_client.py
@@ -17,7 +17,7 @@
import json
import urllib
-from tempest.api_schema.compute import services as schema
+from tempest.api_schema.response.compute import services as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/v3/json/version_client.py b/tempest/services/compute/v3/json/version_client.py
index 568678d..bc4f58c 100644
--- a/tempest/services/compute/v3/json/version_client.py
+++ b/tempest/services/compute/v3/json/version_client.py
@@ -15,7 +15,7 @@
import json
-from tempest.api_schema.compute import version as schema
+from tempest.api_schema.response.compute import version as schema
from tempest.common import rest_client
from tempest import config
diff --git a/tempest/services/compute/xml/floating_ips_client.py b/tempest/services/compute/xml/floating_ips_client.py
index fa4aa07..730e870 100644
--- a/tempest/services/compute/xml/floating_ips_client.py
+++ b/tempest/services/compute/xml/floating_ips_client.py
@@ -13,9 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from lxml import etree
import urllib
+from lxml import etree
+
from tempest.common import rest_client
from tempest.common import xml_utils
from tempest import config
diff --git a/tempest/services/compute/xml/hosts_client.py b/tempest/services/compute/xml/hosts_client.py
index 23a7dd6..ddb92b6 100644
--- a/tempest/services/compute/xml/hosts_client.py
+++ b/tempest/services/compute/xml/hosts_client.py
@@ -15,6 +15,7 @@
import urllib
from lxml import etree
+
from tempest.common import rest_client
from tempest.common import xml_utils
from tempest import config
diff --git a/tempest/services/compute/xml/security_groups_client.py b/tempest/services/compute/xml/security_groups_client.py
index 9eccb90..56ac7ba 100644
--- a/tempest/services/compute/xml/security_groups_client.py
+++ b/tempest/services/compute/xml/security_groups_client.py
@@ -13,9 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from lxml import etree
import urllib
+from lxml import etree
+
from tempest.common import rest_client
from tempest.common import xml_utils
from tempest import config
diff --git a/tempest/services/data_processing/v1_1/client.py b/tempest/services/data_processing/v1_1/client.py
index 1fe0cf1..7acbae7 100644
--- a/tempest/services/data_processing/v1_1/client.py
+++ b/tempest/services/data_processing/v1_1/client.py
@@ -258,3 +258,38 @@
uri = 'job-binaries/%s/data' % job_binary_id
return self._request_and_check_resp(self.get, uri, 200)
+
+ def list_jobs(self):
+ """List all jobs for a user."""
+
+ uri = 'jobs'
+ return self._request_check_and_parse_resp(self.get, uri, 200, 'jobs')
+
+ def get_job(self, job_id):
+ """Returns the details of a single job."""
+
+ uri = 'jobs/%s' % job_id
+ return self._request_check_and_parse_resp(self.get, uri, 200, 'job')
+
+ def create_job(self, name, job_type, mains, libs=None, **kwargs):
+ """Creates job with specified params.
+
+ It supports passing additional params using kwargs and returns created
+ object.
+ """
+ uri = 'jobs'
+ body = kwargs.copy()
+ body.update({
+ 'name': name,
+ 'type': job_type,
+ 'mains': mains,
+ 'libs': libs or list(),
+ })
+ return self._request_check_and_parse_resp(self.post, uri, 202,
+ 'job', body=json.dumps(body))
+
+ def delete_job(self, job_id):
+ """Deletes the specified job by id."""
+
+ uri = 'jobs/%s' % job_id
+ return self._request_and_check_resp(self.delete, uri, 204)
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
index 0188c2a..d57b931 100644
--- a/tempest/services/identity/v3/json/identity_client.py
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -135,8 +135,11 @@
body = json.loads(body)
return resp, body['project']
- def list_projects(self):
- resp, body = self.get("projects")
+ 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 resp, body['projects']
diff --git a/tempest/services/identity/v3/xml/identity_client.py b/tempest/services/identity/v3/xml/identity_client.py
index f3e084e..c2bd77e 100644
--- a/tempest/services/identity/v3/xml/identity_client.py
+++ b/tempest/services/identity/v3/xml/identity_client.py
@@ -197,9 +197,12 @@
body = self._parse_body(etree.fromstring(body))
return resp, body
- def list_projects(self):
+ def list_projects(self, params=None):
"""Get the list of projects."""
- resp, body = self.get("projects")
+ url = 'projects'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
self.expected_success(200, resp.status)
body = self._parse_projects(etree.fromstring(body))
return resp, body
diff --git a/tempest/services/image/v1/json/image_client.py b/tempest/services/image/v1/json/image_client.py
index 4a7c163..bc5e04a 100644
--- a/tempest/services/image/v1/json/image_client.py
+++ b/tempest/services/image/v1/json/image_client.py
@@ -235,7 +235,7 @@
def is_resource_deleted(self, id):
try:
- self.get_image(id)
+ self.get_image_meta(id)
except exceptions.NotFound:
return True
return False
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index 8e53b8d..2e28bfe 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -110,7 +110,7 @@
def add_router_interface_with_subnet_id(self, router_id, subnet_id):
uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix,
- router_id)
+ router_id)
update_body = {"subnet_id": subnet_id}
update_body = json.dumps(update_body)
resp, body = self.put(uri, update_body)
@@ -119,7 +119,7 @@
def add_router_interface_with_port_id(self, router_id, port_id):
uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix,
- router_id)
+ router_id)
update_body = {"port_id": port_id}
update_body = json.dumps(update_body)
resp, body = self.put(uri, update_body)
@@ -128,7 +128,7 @@
def remove_router_interface_with_subnet_id(self, router_id, subnet_id):
uri = '%s/routers/%s/remove_router_interface' % (self.uri_prefix,
- router_id)
+ router_id)
update_body = {"subnet_id": subnet_id}
update_body = json.dumps(update_body)
resp, body = self.put(uri, update_body)
@@ -137,7 +137,7 @@
def remove_router_interface_with_port_id(self, router_id, port_id):
uri = '%s/routers/%s/remove_router_interface' % (self.uri_prefix,
- router_id)
+ router_id)
update_body = {"port_id": port_id}
update_body = json.dumps(update_body)
resp, body = self.put(uri, update_body)
diff --git a/tempest/services/network/xml/network_client.py b/tempest/services/network/xml/network_client.py
index 22cc948..ea9dc77 100644
--- a/tempest/services/network/xml/network_client.py
+++ b/tempest/services/network/xml/network_client.py
@@ -10,9 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from lxml import etree
import xml.etree.ElementTree as ET
+from lxml import etree
+
from tempest.common import rest_client
from tempest.common import xml_utils as common
from tempest.services.network import network_client_base as client_base
@@ -136,7 +137,7 @@
def add_router_interface_with_subnet_id(self, router_id, subnet_id):
uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix,
- router_id)
+ router_id)
subnet = common.Element("subnet_id", subnet_id)
resp, body = self.put(uri, str(common.Document(subnet)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
@@ -144,7 +145,7 @@
def add_router_interface_with_port_id(self, router_id, port_id):
uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix,
- router_id)
+ router_id)
port = common.Element("port_id", port_id)
resp, body = self.put(uri, str(common.Document(port)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
@@ -152,7 +153,7 @@
def remove_router_interface_with_subnet_id(self, router_id, subnet_id):
uri = '%s/routers/%s/remove_router_interface' % (self.uri_prefix,
- router_id)
+ router_id)
subnet = common.Element("subnet_id", subnet_id)
resp, body = self.put(uri, str(common.Document(subnet)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
@@ -160,7 +161,7 @@
def remove_router_interface_with_port_id(self, router_id, port_id):
uri = '%s/routers/%s/remove_router_interface' % (self.uri_prefix,
- router_id)
+ router_id)
port = common.Element("port_id", port_id)
resp, body = self.put(uri, str(common.Document(port)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
diff --git a/tempest/services/object_storage/account_client.py b/tempest/services/object_storage/account_client.py
index a0506f2..be0f888 100644
--- a/tempest/services/object_storage/account_client.py
+++ b/tempest/services/object_storage/account_client.py
@@ -15,12 +15,12 @@
import json
import urllib
+from xml.etree import ElementTree as etree
from tempest.common import http
from tempest.common import rest_client
from tempest import config
from tempest import exceptions
-from xml.etree import ElementTree as etree
CONF = config.CONF
diff --git a/tempest/services/object_storage/container_client.py b/tempest/services/object_storage/container_client.py
index 546b557..ffc1326 100644
--- a/tempest/services/object_storage/container_client.py
+++ b/tempest/services/object_storage/container_client.py
@@ -15,10 +15,10 @@
import json
import urllib
+from xml.etree import ElementTree as etree
from tempest.common import rest_client
from tempest import config
-from xml.etree import ElementTree as etree
CONF = config.CONF
diff --git a/tempest/services/queuing/json/queuing_client.py b/tempest/services/queuing/json/queuing_client.py
index 031c9c6..14960ad 100644
--- a/tempest/services/queuing/json/queuing_client.py
+++ b/tempest/services/queuing/json/queuing_client.py
@@ -16,7 +16,7 @@
import json
import urllib
-from tempest.api_schema.queuing.v1 import queues as queues_schema
+from tempest.api_schema.response.queuing.v1 import queues as queues_schema
from tempest.common import rest_client
from tempest.common.utils import data_utils
from tempest import config
diff --git a/tempest/services/telemetry/telemetry_client_base.py b/tempest/services/telemetry/telemetry_client_base.py
index b45c239..a184a77 100644
--- a/tempest/services/telemetry/telemetry_client_base.py
+++ b/tempest/services/telemetry/telemetry_client_base.py
@@ -14,9 +14,10 @@
# under the License.
import abc
-import six
import urllib
+import six
+
from tempest import config
CONF = config.CONF
diff --git a/tempest/services/volume/json/admin/volume_hosts_client.py b/tempest/services/volume/json/admin/volume_hosts_client.py
index 84e4705..b3a22b5 100644
--- a/tempest/services/volume/json/admin/volume_hosts_client.py
+++ b/tempest/services/volume/json/admin/volume_hosts_client.py
@@ -43,4 +43,5 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['hosts']
diff --git a/tempest/services/volume/json/admin/volume_quotas_client.py b/tempest/services/volume/json/admin/volume_quotas_client.py
index 961c7da..90790e3 100644
--- a/tempest/services/volume/json/admin/volume_quotas_client.py
+++ b/tempest/services/volume/json/admin/volume_quotas_client.py
@@ -16,10 +16,9 @@
import urllib
-from tempest.openstack.common import jsonutils
-
from tempest.common import rest_client
from tempest import config
+from tempest.openstack.common import jsonutils
CONF = config.CONF
@@ -43,6 +42,7 @@
url = 'os-quota-sets/%s/defaults' % tenant_id
resp, body = self.get(url)
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def get_quota_set(self, tenant_id, params=None):
@@ -53,12 +53,14 @@
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def get_quota_usage(self, tenant_id):
"""List the quota set for a tenant."""
resp, body = self.get_quota_set(tenant_id, params={'usage': True})
+ self.expected_success(200, resp.status)
return resp, body
def update_quota_set(self, tenant_id, gigabytes=None, volumes=None,
@@ -76,8 +78,10 @@
post_body = jsonutils.dumps({'quota_set': post_body})
resp, body = self.put('os-quota-sets/%s' % tenant_id, post_body)
+ self.expected_success(200, resp.status)
return resp, self._parse_resp(body)
def delete_quota_set(self, tenant_id):
"""Delete the tenant's quota set."""
- return self.delete('os-quota-sets/%s' % tenant_id)
+ resp, body = self.delete('os-quota-sets/%s' % tenant_id)
+ self.expected_success(200, resp.status)
diff --git a/tempest/services/volume/json/admin/volume_services_client.py b/tempest/services/volume/json/admin/volume_services_client.py
index d43c04a..c9b8bcc 100644
--- a/tempest/services/volume/json/admin/volume_services_client.py
+++ b/tempest/services/volume/json/admin/volume_services_client.py
@@ -35,4 +35,5 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['services']
diff --git a/tempest/services/volume/json/admin/volume_types_client.py b/tempest/services/volume/json/admin/volume_types_client.py
index 65ecc67..44ef9fe 100644
--- a/tempest/services/volume/json/admin/volume_types_client.py
+++ b/tempest/services/volume/json/admin/volume_types_client.py
@@ -63,6 +63,7 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['volume_types']
def get_volume_type(self, volume_id):
@@ -70,6 +71,7 @@
url = "types/%s" % str(volume_id)
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['volume_type']
def create_volume_type(self, name, **kwargs):
@@ -87,11 +89,13 @@
post_body = json.dumps({'volume_type': post_body})
resp, body = self.post('types', post_body)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['volume_type']
def delete_volume_type(self, volume_id):
"""Deletes the Specified Volume_type."""
- return self.delete("types/%s" % str(volume_id))
+ resp, body = self.delete("types/%s" % str(volume_id))
+ self.expected_success(202, resp.status)
def list_volume_types_extra_specs(self, vol_type_id, params=None):
"""List all the volume_types extra specs created."""
@@ -101,6 +105,7 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['extra_specs']
def get_volume_type_extra_specs(self, vol_type_id, extra_spec_name):
@@ -109,6 +114,7 @@
str(extra_spec_name))
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body
def create_volume_type_extra_specs(self, vol_type_id, extra_spec):
@@ -121,12 +127,14 @@
post_body = json.dumps({'extra_specs': extra_spec})
resp, body = self.post(url, post_body)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['extra_specs']
def delete_volume_type_extra_specs(self, vol_id, extra_spec_name):
"""Deletes the Specified Volume_type extra spec."""
- return self.delete("types/%s/extra_specs/%s" % ((str(vol_id)),
- str(extra_spec_name)))
+ resp, body = self.delete("types/%s/extra_specs/%s" % (
+ (str(vol_id)), str(extra_spec_name)))
+ self.expected_success(202, resp.status)
def update_volume_type_extra_specs(self, vol_type_id, extra_spec_name,
extra_spec):
@@ -142,6 +150,7 @@
put_body = json.dumps(extra_spec)
resp, body = self.put(url, put_body)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body
def get_encryption_type(self, vol_type_id):
@@ -152,6 +161,7 @@
url = "/types/%s/encryption" % str(vol_type_id)
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body
def create_encryption_type(self, vol_type_id, **kwargs):
@@ -170,8 +180,11 @@
post_body = json.dumps({'encryption': post_body})
resp, body = self.post(url, post_body)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['encryption']
def delete_encryption_type(self, vol_type_id):
"""Delete the encryption type for the specified volume-type."""
- return self.delete("/types/%s/encryption/provider" % str(vol_type_id))
+ resp, body = self.delete(
+ "/types/%s/encryption/provider" % str(vol_type_id))
+ self.expected_success(202, resp.status)
diff --git a/tempest/services/volume/json/availability_zone_client.py b/tempest/services/volume/json/availability_zone_client.py
index 6839d3a..5ad2287 100644
--- a/tempest/services/volume/json/availability_zone_client.py
+++ b/tempest/services/volume/json/availability_zone_client.py
@@ -21,14 +21,21 @@
CONF = config.CONF
-class VolumeAvailabilityZoneClientJSON(rest_client.RestClient):
+class BaseVolumeAvailabilityZoneClientJSON(rest_client.RestClient):
def __init__(self, auth_provider):
- super(VolumeAvailabilityZoneClientJSON, self).__init__(
+ super(BaseVolumeAvailabilityZoneClientJSON, self).__init__(
auth_provider)
self.service = CONF.volume.catalog_type
def get_availability_zone_list(self):
resp, body = self.get('os-availability-zone')
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['availabilityZoneInfo']
+
+
+class VolumeAvailabilityZoneClientJSON(BaseVolumeAvailabilityZoneClientJSON):
+ """
+ Volume V1 availability zone client.
+ """
diff --git a/tempest/services/volume/json/backups_client.py b/tempest/services/volume/json/backups_client.py
index 183d06b..63fc646 100644
--- a/tempest/services/volume/json/backups_client.py
+++ b/tempest/services/volume/json/backups_client.py
@@ -47,6 +47,7 @@
post_body = json.dumps({'backup': post_body})
resp, body = self.post('backups', post_body)
body = json.loads(body)
+ self.expected_success(202, resp.status)
return resp, body['backup']
def restore_backup(self, backup_id, volume_id=None):
@@ -55,11 +56,13 @@
post_body = json.dumps({'restore': post_body})
resp, body = self.post('backups/%s/restore' % (backup_id), post_body)
body = json.loads(body)
+ self.expected_success(202, resp.status)
return resp, body['restore']
def delete_backup(self, backup_id):
"""Delete a backup of volume."""
resp, body = self.delete('backups/%s' % (str(backup_id)))
+ self.expected_success(202, resp.status)
return resp, body
def get_backup(self, backup_id):
@@ -67,6 +70,7 @@
url = "backups/%s" % str(backup_id)
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['backup']
def list_backups_with_detail(self):
@@ -74,6 +78,7 @@
url = "backups/detail"
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['backups']
def wait_for_backup_status(self, backup_id, status):
diff --git a/tempest/services/volume/json/extensions_client.py b/tempest/services/volume/json/extensions_client.py
index 9e182ea..c84b186 100644
--- a/tempest/services/volume/json/extensions_client.py
+++ b/tempest/services/volume/json/extensions_client.py
@@ -21,14 +21,21 @@
CONF = config.CONF
-class ExtensionsClientJSON(rest_client.RestClient):
+class BaseExtensionsClientJSON(rest_client.RestClient):
def __init__(self, auth_provider):
- super(ExtensionsClientJSON, self).__init__(auth_provider)
+ super(BaseExtensionsClientJSON, self).__init__(auth_provider)
self.service = CONF.volume.catalog_type
def list_extensions(self):
url = 'extensions'
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['extensions']
+
+
+class ExtensionsClientJSON(BaseExtensionsClientJSON):
+ """
+ Volume V1 extensions client.
+ """
diff --git a/tempest/services/volume/json/snapshots_client.py b/tempest/services/volume/json/snapshots_client.py
index 2dff63d..f50ba2f 100644
--- a/tempest/services/volume/json/snapshots_client.py
+++ b/tempest/services/volume/json/snapshots_client.py
@@ -42,6 +42,7 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['snapshots']
def list_snapshots_with_detail(self, params=None):
@@ -52,6 +53,7 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['snapshots']
def get_snapshot(self, snapshot_id):
@@ -59,6 +61,7 @@
url = "snapshots/%s" % str(snapshot_id)
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['snapshot']
def create_snapshot(self, volume_id, **kwargs):
@@ -74,6 +77,7 @@
post_body = json.dumps({'snapshot': post_body})
resp, body = self.post('snapshots', post_body)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['snapshot']
def update_snapshot(self, snapshot_id, **kwargs):
@@ -81,6 +85,7 @@
put_body = json.dumps({'snapshot': kwargs})
resp, body = self.put('snapshots/%s' % snapshot_id, put_body)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['snapshot']
# NOTE(afazekas): just for the wait function
@@ -122,7 +127,8 @@
def delete_snapshot(self, snapshot_id):
"""Delete Snapshot."""
- return self.delete("snapshots/%s" % str(snapshot_id))
+ resp, body = self.delete("snapshots/%s" % str(snapshot_id))
+ self.expected_success(202, resp.status)
def is_resource_deleted(self, id):
try:
@@ -135,6 +141,7 @@
"""Reset the specified snapshot's status."""
post_body = json.dumps({'os-reset_status': {"status": status}})
resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
+ self.expected_success(202, resp.status)
return resp, body
def update_snapshot_status(self, snapshot_id, status, progress):
@@ -146,6 +153,7 @@
post_body = json.dumps({'os-update_snapshot_status': post_body})
url = 'snapshots/%s/action' % str(snapshot_id)
resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
return resp, body
def create_snapshot_metadata(self, snapshot_id, metadata):
@@ -154,6 +162,7 @@
url = "snapshots/%s/metadata" % str(snapshot_id)
resp, body = self.post(url, put_body)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['metadata']
def get_snapshot_metadata(self, snapshot_id):
@@ -161,6 +170,7 @@
url = "snapshots/%s/metadata" % str(snapshot_id)
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['metadata']
def update_snapshot_metadata(self, snapshot_id, metadata):
@@ -169,6 +179,7 @@
url = "snapshots/%s/metadata" % str(snapshot_id)
resp, body = self.put(url, put_body)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['metadata']
def update_snapshot_metadata_item(self, snapshot_id, id, meta_item):
@@ -177,16 +188,18 @@
url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id))
resp, body = self.put(url, put_body)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['meta']
def delete_snapshot_metadata_item(self, snapshot_id, id):
"""Delete metadata item for the snapshot."""
url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id))
resp, body = self.delete(url)
- return resp, body
+ self.expected_success(200, resp.status)
def force_delete_snapshot(self, snapshot_id):
"""Force Delete Snapshot."""
post_body = json.dumps({'os-force_delete': {}})
resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
+ self.expected_success(202, resp.status)
return resp, body
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index 6c97497..c3a9269 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -35,6 +35,7 @@
self.service = CONF.volume.catalog_type
self.build_interval = CONF.volume.build_interval
self.build_timeout = CONF.volume.build_timeout
+ self.create_resp = 200
def get_attachment_from_volume(self, volume):
"""Return the element 'attachment' from input volumes."""
@@ -48,6 +49,7 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['volumes']
def list_volumes_with_detail(self, params=None):
@@ -58,6 +60,7 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['volumes']
def get_volume(self, volume_id):
@@ -65,6 +68,7 @@
url = "volumes/%s" % str(volume_id)
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['volume']
def create_volume(self, size=None, **kwargs):
@@ -88,6 +92,7 @@
post_body = json.dumps({'volume': post_body})
resp, body = self.post('volumes', post_body)
body = json.loads(body)
+ self.expected_success(self.create_resp, resp.status)
return resp, body['volume']
def update_volume(self, volume_id, **kwargs):
@@ -95,11 +100,13 @@
put_body = json.dumps({'volume': kwargs})
resp, body = self.put('volumes/%s' % volume_id, put_body)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['volume']
def delete_volume(self, volume_id):
"""Deletes the Specified Volume."""
- return self.delete("volumes/%s" % str(volume_id))
+ resp, body = self.delete("volumes/%s" % str(volume_id))
+ self.expected_success(202, resp.status)
def upload_volume(self, volume_id, image_name, disk_format):
"""Uploads a volume in Glance."""
@@ -111,6 +118,7 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
body = json.loads(body)
+ self.expected_success(202, resp.status)
return resp, body['os-volume_upload_image']
def attach_volume(self, volume_id, instance_uuid, mountpoint):
@@ -122,6 +130,7 @@
post_body = json.dumps({'os-attach': post_body})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
return resp, body
def detach_volume(self, volume_id):
@@ -130,6 +139,7 @@
post_body = json.dumps({'os-detach': post_body})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
return resp, body
def reserve_volume(self, volume_id):
@@ -138,6 +148,7 @@
post_body = json.dumps({'os-reserve': post_body})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
return resp, body
def unreserve_volume(self, volume_id):
@@ -146,6 +157,7 @@
post_body = json.dumps({'os-unreserve': post_body})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
return resp, body
def wait_for_volume_status(self, volume_id, status):
@@ -183,24 +195,30 @@
post_body = json.dumps({'os-extend': post_body})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
return resp, body
def reset_volume_status(self, volume_id, status):
"""Reset the Specified Volume's Status."""
post_body = json.dumps({'os-reset_status': {"status": status}})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
+ self.expected_success(202, resp.status)
return resp, body
def volume_begin_detaching(self, volume_id):
"""Volume Begin Detaching."""
+ # ref cinder/api/contrib/volume_actions.py#L158
post_body = json.dumps({'os-begin_detaching': {}})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
+ self.expected_success(202, resp.status)
return resp, body
def volume_roll_detaching(self, volume_id):
"""Volume Roll Detaching."""
+ # cinder/api/contrib/volume_actions.py#L170
post_body = json.dumps({'os-roll_detaching': {}})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
+ self.expected_success(202, resp.status)
return resp, body
def create_volume_transfer(self, vol_id, display_name=None):
@@ -213,6 +231,7 @@
post_body = json.dumps({'transfer': post_body})
resp, body = self.post('os-volume-transfer', post_body)
body = json.loads(body)
+ self.expected_success(202, resp.status)
return resp, body['transfer']
def get_volume_transfer(self, transfer_id):
@@ -220,6 +239,7 @@
url = "os-volume-transfer/%s" % str(transfer_id)
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['transfer']
def list_volume_transfers(self, params=None):
@@ -229,11 +249,13 @@
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['transfers']
def delete_volume_transfer(self, transfer_id):
"""Delete a volume transfer."""
- return self.delete("os-volume-transfer/%s" % str(transfer_id))
+ resp, body = self.delete("os-volume-transfer/%s" % str(transfer_id))
+ self.expected_success(202, resp.status)
def accept_volume_transfer(self, transfer_id, transfer_auth_key):
"""Accept a volume transfer."""
@@ -244,6 +266,7 @@
post_body = json.dumps({'accept': post_body})
resp, body = self.post(url, post_body)
body = json.loads(body)
+ self.expected_success(202, resp.status)
return resp, body['transfer']
def update_volume_readonly(self, volume_id, readonly):
@@ -254,12 +277,14 @@
post_body = json.dumps({'os-update_readonly_flag': post_body})
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
return resp, body
def force_delete_volume(self, volume_id):
"""Force Delete Volume."""
post_body = json.dumps({'os-force_delete': {}})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
+ self.expected_success(202, resp.status)
return resp, body
def create_volume_metadata(self, volume_id, metadata):
@@ -268,6 +293,7 @@
url = "volumes/%s/metadata" % str(volume_id)
resp, body = self.post(url, put_body)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['metadata']
def get_volume_metadata(self, volume_id):
@@ -275,6 +301,7 @@
url = "volumes/%s/metadata" % str(volume_id)
resp, body = self.get(url)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['metadata']
def update_volume_metadata(self, volume_id, metadata):
@@ -283,6 +310,7 @@
url = "volumes/%s/metadata" % str(volume_id)
resp, body = self.put(url, put_body)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['metadata']
def update_volume_metadata_item(self, volume_id, id, meta_item):
@@ -291,13 +319,14 @@
url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
resp, body = self.put(url, put_body)
body = json.loads(body)
+ self.expected_success(200, resp.status)
return resp, body['meta']
def delete_volume_metadata_item(self, volume_id, id):
"""Delete metadata item for the volume."""
url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
resp, body = self.delete(url)
- return resp, body
+ self.expected_success(200, resp.status)
class VolumesClientJSON(BaseVolumesClientJSON):
diff --git a/tempest/services/volume/v2/json/availability_zone_client.py b/tempest/services/volume/v2/json/availability_zone_client.py
new file mode 100644
index 0000000..047ba1b
--- /dev/null
+++ b/tempest/services/volume/v2/json/availability_zone_client.py
@@ -0,0 +1,26 @@
+# Copyright 2014 IBM Corp.
+# 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.services.volume.json import availability_zone_client
+
+
+class VolumeV2AvailabilityZoneClientJSON(
+ availability_zone_client.BaseVolumeAvailabilityZoneClientJSON):
+
+ def __init__(self, auth_provider):
+ super(VolumeV2AvailabilityZoneClientJSON, self).__init__(
+ auth_provider)
+
+ self.api_version = "v2"
diff --git a/tempest/api_schema/compute/v2/agents.py b/tempest/services/volume/v2/json/extensions_client.py
similarity index 63%
copy from tempest/api_schema/compute/v2/agents.py
copy to tempest/services/volume/v2/json/extensions_client.py
index 30f999f..cc5244c 100644
--- a/tempest/api_schema/compute/v2/agents.py
+++ b/tempest/services/volume/v2/json/extensions_client.py
@@ -1,4 +1,5 @@
-# Copyright 2014 NEC Corporation. All rights reserved.
+# Copyright 2014 IBM Corp.
+# 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
@@ -12,13 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api_schema.compute import agents
+from tempest.services.volume.json import extensions_client
-create_agent = {
- 'status_code': [200],
- 'response_body': agents.common_create_agent
-}
-delete_agent = {
- 'status_code': [200]
-}
+class ExtensionsV2ClientJSON(extensions_client.BaseExtensionsClientJSON):
+
+ def __init__(self, auth_provider):
+ super(ExtensionsV2ClientJSON, self).__init__(auth_provider)
+
+ self.api_version = "v2"
diff --git a/tempest/services/volume/v2/json/volumes_client.py b/tempest/services/volume/v2/json/volumes_client.py
index 1f16ead..ac4342e 100644
--- a/tempest/services/volume/v2/json/volumes_client.py
+++ b/tempest/services/volume/v2/json/volumes_client.py
@@ -25,3 +25,4 @@
super(VolumesV2ClientJSON, self).__init__(auth_provider)
self.api_version = "v2"
+ self.create_resp = 202
diff --git a/tempest/services/volume/v2/xml/availability_zone_client.py b/tempest/services/volume/v2/xml/availability_zone_client.py
new file mode 100644
index 0000000..68ca39b
--- /dev/null
+++ b/tempest/services/volume/v2/xml/availability_zone_client.py
@@ -0,0 +1,26 @@
+# Copyright 2014 IBM Corp.
+# 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.services.volume.xml import availability_zone_client
+
+
+class VolumeV2AvailabilityZoneClientXML(
+ availability_zone_client.BaseVolumeAvailabilityZoneClientXML):
+
+ def __init__(self, auth_provider):
+ super(VolumeV2AvailabilityZoneClientXML, self).__init__(
+ auth_provider)
+
+ self.api_version = "v2"
diff --git a/tempest/api_schema/compute/v2/agents.py b/tempest/services/volume/v2/xml/extensions_client.py
similarity index 63%
copy from tempest/api_schema/compute/v2/agents.py
copy to tempest/services/volume/v2/xml/extensions_client.py
index 30f999f..13f333c 100644
--- a/tempest/api_schema/compute/v2/agents.py
+++ b/tempest/services/volume/v2/xml/extensions_client.py
@@ -1,4 +1,5 @@
-# Copyright 2014 NEC Corporation. All rights reserved.
+# Copyright 2014 IBM Corp.
+# 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
@@ -12,13 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api_schema.compute import agents
+from tempest.services.volume.xml import extensions_client
-create_agent = {
- 'status_code': [200],
- 'response_body': agents.common_create_agent
-}
-delete_agent = {
- 'status_code': [200]
-}
+class ExtensionsV2ClientXML(extensions_client.BaseExtensionsClientXML):
+
+ def __init__(self, auth_provider):
+ super(ExtensionsV2ClientXML, self).__init__(auth_provider)
+
+ self.api_version = "v2"
diff --git a/tempest/services/volume/v2/xml/volumes_client.py b/tempest/services/volume/v2/xml/volumes_client.py
index c1bcf6e..b3133af 100644
--- a/tempest/services/volume/v2/xml/volumes_client.py
+++ b/tempest/services/volume/v2/xml/volumes_client.py
@@ -30,6 +30,7 @@
super(VolumesV2ClientXML, self).__init__(auth_provider)
self.api_version = "v2"
+ self.create_resp = 202
def _parse_volume(self, body):
vol = dict((attr, body.get(attr)) for attr in body.keys())
@@ -61,6 +62,7 @@
volumes += [self._parse_volume(vol) for vol in list(body)]
for v in volumes:
v = self._check_if_bootable(v)
+ self.expected_success(200, resp.status)
return resp, volumes
def get_volume(self, volume_id):
@@ -69,4 +71,5 @@
resp, body = self.get(url)
body = self._parse_volume(etree.fromstring(body))
body = self._check_if_bootable(body)
+ self.expected_success(200, resp.status)
return resp, body
diff --git a/tempest/services/volume/xml/admin/volume_hosts_client.py b/tempest/services/volume/xml/admin/volume_hosts_client.py
index 967c7c2..98a7c58 100644
--- a/tempest/services/volume/xml/admin/volume_hosts_client.py
+++ b/tempest/services/volume/xml/admin/volume_hosts_client.py
@@ -69,5 +69,6 @@
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
+ self.expected_success(200, resp.status)
body = self._parse_array(etree.fromstring(body))
return resp, body
diff --git a/tempest/services/volume/xml/admin/volume_quotas_client.py b/tempest/services/volume/xml/admin/volume_quotas_client.py
index a38410b..acf9102 100644
--- a/tempest/services/volume/xml/admin/volume_quotas_client.py
+++ b/tempest/services/volume/xml/admin/volume_quotas_client.py
@@ -15,6 +15,7 @@
# under the License.
import ast
+
from lxml import etree
from tempest.common import xml_utils as xml
@@ -47,6 +48,7 @@
"""List the quota set for a tenant."""
resp, body = self.get_quota_set(tenant_id, params={'usage': True})
+ self.expected_success(200, resp.status)
return resp, self._format_quota(body)
def update_quota_set(self, tenant_id, gigabytes=None, volumes=None,
@@ -67,8 +69,10 @@
resp, body = self.put('os-quota-sets/%s' % tenant_id,
str(xml.Document(element)))
body = xml.xml_to_json(etree.fromstring(body))
+ self.expected_success(200, resp.status)
return resp, self._format_quota(body)
def delete_quota_set(self, tenant_id):
"""Delete the tenant's quota set."""
- return self.delete('os-quota-sets/%s' % tenant_id)
+ resp, body = self.delete('os-quota-sets/%s' % tenant_id)
+ self.expected_success(200, resp.status)
diff --git a/tempest/services/volume/xml/admin/volume_services_client.py b/tempest/services/volume/xml/admin/volume_services_client.py
index 7bad16d..2ecb590 100644
--- a/tempest/services/volume/xml/admin/volume_services_client.py
+++ b/tempest/services/volume/xml/admin/volume_services_client.py
@@ -39,4 +39,5 @@
resp, body = self.get(url)
node = etree.fromstring(body)
body = [xml_utils.xml_to_json(x) for x in node.getchildren()]
+ self.expected_success(200, resp.status)
return resp, body
diff --git a/tempest/services/volume/xml/admin/volume_types_client.py b/tempest/services/volume/xml/admin/volume_types_client.py
index 90897ee..679d097 100644
--- a/tempest/services/volume/xml/admin/volume_types_client.py
+++ b/tempest/services/volume/xml/admin/volume_types_client.py
@@ -75,6 +75,7 @@
if body is not None:
volume_types += [self._parse_volume_type(vol)
for vol in list(body)]
+ self.expected_success(200, resp.status)
return resp, volume_types
def get_volume_type(self, type_id):
@@ -82,6 +83,7 @@
url = "types/%s" % str(type_id)
resp, body = self.get(url)
body = etree.fromstring(body)
+ self.expected_success(200, resp.status)
return resp, self._parse_volume_type(body)
def create_volume_type(self, name, **kwargs):
@@ -107,11 +109,13 @@
resp, body = self.post('types', str(common.Document(vol_type)))
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(200, resp.status)
return resp, body
def delete_volume_type(self, type_id):
"""Deletes the Specified Volume_type."""
- return self.delete("types/%s" % str(type_id))
+ resp, body = self.delete("types/%s" % str(type_id))
+ self.expected_success(202, resp.status)
def list_volume_types_extra_specs(self, vol_type_id, params=None):
"""List all the volume_types extra specs created."""
@@ -126,6 +130,7 @@
if body is not None:
extra_specs += [self._parse_volume_type_extra_specs(spec)
for spec in list(body)]
+ self.expected_success(200, resp.status)
return resp, extra_specs
def get_volume_type_extra_specs(self, vol_type_id, extra_spec_name):
@@ -134,6 +139,7 @@
str(extra_spec_name))
resp, body = self.get(url)
body = etree.fromstring(body)
+ self.expected_success(200, resp.status)
return resp, self._parse_volume_type_extra_specs(body)
def create_volume_type_extra_specs(self, vol_type_id, extra_spec):
@@ -158,12 +164,15 @@
resp, body = self.post(url, str(common.Document(extra_specs)))
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(200, resp.status)
return resp, body
def delete_volume_type_extra_specs(self, vol_id, extra_spec_name):
"""Deletes the Specified Volume_type extra spec."""
- return self.delete("types/%s/extra_specs/%s" % ((str(vol_id)),
- str(extra_spec_name)))
+ resp, body = self.delete("types/%s/extra_specs/%s" % (
+ (str(vol_id)), str(extra_spec_name)))
+ self.expected_success(202, resp.status)
+ return resp, body
def update_volume_type_extra_specs(self, vol_type_id, extra_spec_name,
extra_spec):
@@ -187,6 +196,7 @@
resp, body = self.put(url, str(common.Document(extra_specs)))
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(200, resp.status)
return resp, body
def is_resource_deleted(self, id):
diff --git a/tempest/services/volume/xml/availability_zone_client.py b/tempest/services/volume/xml/availability_zone_client.py
index e4a004a..b956d3f 100644
--- a/tempest/services/volume/xml/availability_zone_client.py
+++ b/tempest/services/volume/xml/availability_zone_client.py
@@ -22,11 +22,11 @@
CONF = config.CONF
-class VolumeAvailabilityZoneClientXML(rest_client.RestClient):
+class BaseVolumeAvailabilityZoneClientXML(rest_client.RestClient):
TYPE = "xml"
def __init__(self, auth_provider):
- super(VolumeAvailabilityZoneClientXML, self).__init__(
+ super(BaseVolumeAvailabilityZoneClientXML, self).__init__(
auth_provider)
self.service = CONF.volume.catalog_type
@@ -36,4 +36,11 @@
def get_availability_zone_list(self):
resp, body = self.get('os-availability-zone')
availability_zone = self._parse_array(etree.fromstring(body))
+ self.expected_success(200, resp.status)
return resp, availability_zone
+
+
+class VolumeAvailabilityZoneClientXML(BaseVolumeAvailabilityZoneClientXML):
+ """
+ Volume V1 availability zone client.
+ """
diff --git a/tempest/services/volume/xml/extensions_client.py b/tempest/services/volume/xml/extensions_client.py
index 2986fcd..f2b2e02 100644
--- a/tempest/services/volume/xml/extensions_client.py
+++ b/tempest/services/volume/xml/extensions_client.py
@@ -22,11 +22,11 @@
CONF = config.CONF
-class ExtensionsClientXML(rest_client.RestClient):
+class BaseExtensionsClientXML(rest_client.RestClient):
TYPE = "xml"
def __init__(self, auth_provider):
- super(ExtensionsClientXML, self).__init__(auth_provider)
+ super(BaseExtensionsClientXML, self).__init__(auth_provider)
self.service = CONF.volume.catalog_type
def _parse_array(self, node):
@@ -39,4 +39,11 @@
url = 'extensions'
resp, body = self.get(url)
body = self._parse_array(etree.fromstring(body))
+ self.expected_success(200, resp.status)
return resp, body
+
+
+class ExtensionsClientXML(BaseExtensionsClientXML):
+ """
+ Volume V1 extensions client.
+ """
diff --git a/tempest/services/volume/xml/snapshots_client.py b/tempest/services/volume/xml/snapshots_client.py
index 4b1ba25..7636707 100644
--- a/tempest/services/volume/xml/snapshots_client.py
+++ b/tempest/services/volume/xml/snapshots_client.py
@@ -49,6 +49,7 @@
snapshots = []
for snap in body:
snapshots.append(common.xml_to_json(snap))
+ self.expected_success(200, resp.status)
return resp, snapshots
def list_snapshots_with_detail(self, params=None):
@@ -63,6 +64,7 @@
snapshots = []
for snap in body:
snapshots.append(common.xml_to_json(snap))
+ self.expected_success(200, resp.status)
return resp, snapshots
def get_snapshot(self, snapshot_id):
@@ -70,6 +72,7 @@
url = "snapshots/%s" % str(snapshot_id)
resp, body = self.get(url)
body = etree.fromstring(body)
+ self.expected_success(200, resp.status)
return resp, common.xml_to_json(body)
def create_snapshot(self, volume_id, **kwargs):
@@ -87,6 +90,7 @@
resp, body = self.post('snapshots',
str(common.Document(snapshot)))
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(200, resp.status)
return resp, body
def update_snapshot(self, snapshot_id, **kwargs):
@@ -96,6 +100,7 @@
resp, body = self.put('snapshots/%s' % snapshot_id,
str(common.Document(put_body)))
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(200, resp.status)
return resp, body
# NOTE(afazekas): just for the wait function
@@ -137,7 +142,8 @@
def delete_snapshot(self, snapshot_id):
"""Delete Snapshot."""
- return self.delete("snapshots/%s" % str(snapshot_id))
+ resp, body = self.delete("snapshots/%s" % str(snapshot_id))
+ self.expected_success(202, resp.status)
def is_resource_deleted(self, id):
try:
@@ -153,6 +159,7 @@
resp, body = self.post(url, str(common.Document(post_body)))
if body:
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(202, resp.status)
return resp, body
def update_snapshot_status(self, snapshot_id, status, progress):
@@ -165,6 +172,7 @@
resp, body = self.post(url, str(common.Document(post_body)))
if body:
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(202, resp.status)
return resp, body
def _metadata_body(self, meta):
@@ -188,6 +196,7 @@
resp, body = self.post('snapshots/%s/metadata' % snapshot_id,
str(common.Document(post_body)))
body = self._parse_key_value(etree.fromstring(body))
+ self.expected_success(200, resp.status)
return resp, body
def get_snapshot_metadata(self, snapshot_id):
@@ -195,6 +204,7 @@
url = "snapshots/%s/metadata" % str(snapshot_id)
resp, body = self.get(url)
body = self._parse_key_value(etree.fromstring(body))
+ self.expected_success(200, resp.status)
return resp, body
def update_snapshot_metadata(self, snapshot_id, metadata):
@@ -203,6 +213,7 @@
url = "snapshots/%s/metadata" % str(snapshot_id)
resp, body = self.put(url, str(common.Document(put_body)))
body = self._parse_key_value(etree.fromstring(body))
+ self.expected_success(200, resp.status)
return resp, body
def update_snapshot_metadata_item(self, snapshot_id, id, meta_item):
@@ -213,12 +224,15 @@
url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id))
resp, body = self.put(url, str(common.Document(put_body)))
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(200, resp.status)
return resp, body
def delete_snapshot_metadata_item(self, snapshot_id, id):
"""Delete metadata item for the snapshot."""
url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id))
- return self.delete(url)
+ resp, body = self.delete(url)
+ self.expected_success(200, resp.status)
+ return resp, body
def force_delete_snapshot(self, snapshot_id):
"""Force Delete Snapshot."""
@@ -227,4 +241,5 @@
resp, body = self.post(url, str(common.Document(post_body)))
if body:
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(202, resp.status)
return resp, body
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index 2d4a9e9..a8c1ae5 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -15,9 +15,9 @@
import time
import urllib
+from xml.sax import saxutils
from lxml import etree
-from xml.sax import saxutils
from tempest.common import rest_client
from tempest.common import xml_utils as common
@@ -43,6 +43,7 @@
self.service = CONF.volume.catalog_type
self.build_interval = CONF.compute.build_interval
self.build_timeout = CONF.compute.build_timeout
+ self.create_resp = 200
def _translate_attributes_to_json(self, volume):
volume_host_attr = '{' + VOLUME_HOST_NS + '}host'
@@ -114,6 +115,7 @@
volumes = []
if body is not None:
volumes += [self._parse_volume(vol) for vol in list(body)]
+ self.expected_success(200, resp.status)
return resp, volumes
def list_volumes_with_detail(self, params=None):
@@ -128,6 +130,7 @@
volumes = []
if body is not None:
volumes += [self._parse_volume(vol) for vol in list(body)]
+ self.expected_success(200, resp.status)
return resp, volumes
def get_volume(self, volume_id):
@@ -135,6 +138,7 @@
url = "volumes/%s" % str(volume_id)
resp, body = self.get(url)
body = self._parse_volume(etree.fromstring(body))
+ self.expected_success(200, resp.status)
return resp, body
def create_volume(self, size=None, **kwargs):
@@ -176,6 +180,7 @@
resp, body = self.post('volumes', str(common.Document(volume)))
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(self.create_resp, resp.status)
return resp, body
def update_volume(self, volume_id, **kwargs):
@@ -185,11 +190,14 @@
resp, body = self.put('volumes/%s' % volume_id,
str(common.Document(put_body)))
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(200, resp.status)
return resp, body
def delete_volume(self, volume_id):
"""Deletes the Specified Volume."""
- return self.delete("volumes/%s" % str(volume_id))
+ resp, body = self.delete("volumes/%s" % str(volume_id))
+ self.expected_success(202, resp.status)
+ return resp, body
def wait_for_volume_status(self, volume_id, status):
"""Waits for a Volume to reach a given status."""
@@ -228,6 +236,7 @@
resp, body = self.post(url, str(common.Document(post_body)))
if body:
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(202, resp.status)
return resp, body
def detach_volume(self, volume_id):
@@ -237,6 +246,7 @@
resp, body = self.post(url, str(common.Document(post_body)))
if body:
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(202, resp.status)
return resp, body
def upload_volume(self, volume_id, image_name, disk_format):
@@ -247,6 +257,7 @@
url = 'volumes/%s/action' % str(volume_id)
resp, body = self.post(url, str(common.Document(post_body)))
volume = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(202, resp.status)
return resp, volume
def extend_volume(self, volume_id, extend_size):
@@ -257,6 +268,7 @@
resp, body = self.post(url, str(common.Document(post_body)))
if body:
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(202, resp.status)
return resp, body
def reset_volume_status(self, volume_id, status):
@@ -268,6 +280,7 @@
resp, body = self.post(url, str(common.Document(post_body)))
if body:
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(202, resp.status)
return resp, body
def volume_begin_detaching(self, volume_id):
@@ -295,6 +308,7 @@
resp, body = self.post(url, str(common.Document(post_body)))
if body:
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(202, resp.status)
return resp, body
def unreserve_volume(self, volume_id):
@@ -304,6 +318,7 @@
resp, body = self.post(url, str(common.Document(post_body)))
if body:
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(202, resp.status)
return resp, body
def create_volume_transfer(self, vol_id, display_name=None):
@@ -315,6 +330,7 @@
resp, body = self.post('os-volume-transfer',
str(common.Document(post_body)))
volume = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(202, resp.status)
return resp, volume
def get_volume_transfer(self, transfer_id):
@@ -322,6 +338,7 @@
url = "os-volume-transfer/%s" % str(transfer_id)
resp, body = self.get(url)
volume = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(200, resp.status)
return resp, volume
def list_volume_transfers(self, params=None):
@@ -335,6 +352,7 @@
volumes = []
if body is not None:
volumes += [self._parse_volume_transfer(vol) for vol in list(body)]
+ self.expected_success(200, resp.status)
return resp, volumes
def _parse_volume_transfer(self, body):
@@ -348,7 +366,9 @@
def delete_volume_transfer(self, transfer_id):
"""Delete a volume transfer."""
- return self.delete("os-volume-transfer/%s" % str(transfer_id))
+ resp, body = self.delete("os-volume-transfer/%s" % str(transfer_id))
+ self.expected_success(202, resp.status)
+ return resp, body
def accept_volume_transfer(self, transfer_id, transfer_auth_key):
"""Accept a volume transfer."""
@@ -356,6 +376,7 @@
url = 'os-volume-transfer/%s/accept' % transfer_id
resp, body = self.post(url, str(common.Document(post_body)))
volume = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(202, resp.status)
return resp, volume
def update_volume_readonly(self, volume_id, readonly):
@@ -366,6 +387,7 @@
resp, body = self.post(url, str(common.Document(post_body)))
if body:
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(202, resp.status)
return resp, body
def force_delete_volume(self, volume_id):
@@ -375,6 +397,7 @@
resp, body = self.post(url, str(common.Document(post_body)))
if body:
body = common.xml_to_json(etree.fromstring(body))
+ self.expected_success(202, resp.status)
return resp, body
def _metadata_body(self, meta):
@@ -399,6 +422,7 @@
resp, body = self.post('volumes/%s/metadata' % volume_id,
str(common.Document(post_body)))
body = self._parse_key_value(etree.fromstring(body))
+ self.expected_success(200, resp.status)
return resp, body
def get_volume_metadata(self, volume_id):
@@ -406,6 +430,7 @@
url = "volumes/%s/metadata" % str(volume_id)
resp, body = self.get(url)
body = self._parse_key_value(etree.fromstring(body))
+ self.expected_success(200, resp.status)
return resp, body
def update_volume_metadata(self, volume_id, metadata):
@@ -414,6 +439,7 @@
url = "volumes/%s/metadata" % str(volume_id)
resp, body = self.put(url, str(common.Document(put_body)))
body = self._parse_key_value(etree.fromstring(body))
+ self.expected_success(200, resp.status)
return resp, body
def update_volume_metadata_item(self, volume_id, id, meta_item):
@@ -423,13 +449,16 @@
put_body.append(common.Text(v))
url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
resp, body = self.put(url, str(common.Document(put_body)))
+ self.expected_success(200, resp.status)
body = common.xml_to_json(etree.fromstring(body))
return resp, body
def delete_volume_metadata_item(self, volume_id, id):
"""Delete metadata item for the volume."""
url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
- return self.delete(url)
+ resp, body = self.delete(url)
+ self.expected_success(200, resp.status)
+ return resp, body
class VolumesClientXML(BaseVolumesClientXML):
diff --git a/tempest/stress/README.rst b/tempest/stress/README.rst
index 0a63679..4f1f56c 100644
--- a/tempest/stress/README.rst
+++ b/tempest/stress/README.rst
@@ -1,3 +1,5 @@
+.. _stress_field_guide:
+
Tempest Field Guide to Stress Tests
===================================
diff --git a/tempest/stress/actions/volume_attach_delete.py b/tempest/stress/actions/volume_attach_delete.py
index b438f52..e0238d3 100644
--- a/tempest/stress/actions/volume_attach_delete.py
+++ b/tempest/stress/actions/volume_attach_delete.py
@@ -48,7 +48,7 @@
# Step 3: attach volume to vm
self.logger.info("attach volume (%s) to vm %s" %
- (volume['id'], server_id))
+ (volume['id'], server_id))
resp, body = self.manager.servers_client.attach_volume(server_id,
volume['id'],
'/dev/vdc')
diff --git a/tempest/stress/actions/volume_attach_verify.py b/tempest/stress/actions/volume_attach_verify.py
index a3ca0b7..0d3cb23 100644
--- a/tempest/stress/actions/volume_attach_verify.py
+++ b/tempest/stress/actions/volume_attach_verify.py
@@ -192,7 +192,7 @@
self._create_volume()
servers_client = self.manager.servers_client
self.logger.info("attach volume (%s) to vm %s" %
- (self.volume['id'], self.server_id))
+ (self.volume['id'], self.server_id))
resp, body = servers_client.attach_volume(self.server_id,
self.volume['id'],
self.part_name)
diff --git a/tempest/test.py b/tempest/test.py
index afe7a96..1e67d18 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -215,6 +215,8 @@
'network': CONF.network_feature_enabled.api_extensions,
'object': CONF.object_storage_feature_enabled.discoverable_apis,
}
+ if len(config_dict[service]) == 0:
+ return False
if config_dict[service][0] == 'all':
return True
if extension_name in config_dict[service]:
@@ -397,16 +399,21 @@
cls.admin_client = os_admin.negative_client
@staticmethod
- def load_schema(file):
+ def load_schema(file_or_dict):
"""
- Loads a schema from a file on a specified location.
+ Loads a schema from a file_or_dict on a specified location.
- :param file: the file name
+ :param file_or_dict: just a dict or filename
"""
+ # NOTE(mkoderer): we will get rid of this function when all test are
+ # ported to dicts
+ if isinstance(file_or_dict, dict):
+ return file_or_dict
+
# NOTE(mkoderer): must be extended for xml support
fn = os.path.join(
os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
- "etc", "schemas", file)
+ "etc", "schemas", file_or_dict)
LOG.debug("Open schema file: %s" % (fn))
return json.load(open(fn))
@@ -423,17 +430,21 @@
standard_tests, module, loader = args
for test in testtools.iterate_tests(standard_tests):
schema_file = getattr(test, '_schema_file', None)
+ schema = getattr(test, '_schema', None)
if schema_file is not None:
setattr(test, 'scenarios',
NegativeAutoTest.generate_scenario(schema_file))
+ elif schema is not None:
+ setattr(test, 'scenarios',
+ NegativeAutoTest.generate_scenario(schema))
return testscenarios.load_tests_apply_scenarios(*args)
@staticmethod
- def generate_scenario(description_file):
+ def generate_scenario(description):
"""
Generates the test scenario list for a given description.
- :param description: A dictionary with the following entries:
+ :param description: A file or dictionary with the following entries:
name (required) name for the api
http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
url (required) the url to be appended to the catalog url with '%s'
@@ -449,7 +460,7 @@
the data is used to generate query strings appended to the url,
otherwise for the body of the http call.
"""
- description = NegativeAutoTest.load_schema(description_file)
+ description = NegativeAutoTest.load_schema(description)
LOG.debug(description)
generator = importutils.import_class(
CONF.negative.test_generator)()
@@ -479,13 +490,14 @@
LOG.debug(scenario_list)
return scenario_list
- def execute(self, description_file):
+ def execute(self, description):
"""
Execute a http call on an api that are expected to
result in client errors. First it uses invalid resources that are part
of the url, and then invalid data for queries and http request bodies.
- :param description: A dictionary with the following entries:
+ :param description: A json file or dictionary with the following
+ entries:
name (required) name for the api
http-method (required) one of HEAD,GET,PUT,POST,PATCH,DELETE
url (required) the url to be appended to the catalog url with '%s'
@@ -502,7 +514,7 @@
otherwise for the body of the http call.
"""
- description = NegativeAutoTest.load_schema(description_file)
+ description = NegativeAutoTest.load_schema(description)
LOG.info("Executing %s" % description["name"])
LOG.debug(description)
method = description["http-method"]
@@ -592,7 +604,10 @@
"""
@attr(type=['negative', 'gate'])
def generic_test(self):
- self.execute(self._schema_file)
+ if hasattr(self, '_schema_file'):
+ self.execute(self._schema_file)
+ if hasattr(self, '_schema'):
+ self.execute(self._schema)
cn = klass.__name__
cn = cn.replace('JSON', '')
@@ -621,7 +636,6 @@
while now < timeout:
if func():
return True
- LOG.debug("Sleeping for %d seconds", sleep_for)
time.sleep(sleep_for)
now = time.time()
return False
diff --git a/tempest/tests/README.rst b/tempest/tests/README.rst
index 33d321f..e54d4c0 100644
--- a/tempest/tests/README.rst
+++ b/tempest/tests/README.rst
@@ -1,3 +1,5 @@
+.. _unit_tests_field_guide:
+
Tempest Field Guide to Unit tests
=================================
diff --git a/tempest/tests/base.py b/tempest/tests/base.py
index f4df3b9..27eb2c4 100644
--- a/tempest/tests/base.py
+++ b/tempest/tests/base.py
@@ -13,7 +13,6 @@
# under the License.
import mock
-
from oslotest import base
from oslotest import moxstubout
diff --git a/tempest/tests/cli/test_cli.py b/tempest/tests/cli/test_cli.py
new file mode 100644
index 0000000..1fd5ccb
--- /dev/null
+++ b/tempest/tests/cli/test_cli.py
@@ -0,0 +1,59 @@
+# Copyright 2014 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 mock
+import testtools
+
+from tempest import cli
+from tempest import exceptions
+from tempest.tests import base
+
+
+class TestMinClientVersion(base.TestCase):
+ """Tests for the min_client_version decorator.
+ """
+
+ def _test_min_version(self, required, installed, expect_skip):
+
+ @cli.min_client_version(client='nova', version=required)
+ def fake(self, expect_skip):
+ if expect_skip:
+ # If we got here, the decorator didn't raise a skipException as
+ # expected so we need to fail.
+ self.fail('Should not have gotten past the decorator.')
+
+ with mock.patch.object(cli, 'execute',
+ return_value=installed) as mock_cmd:
+ if expect_skip:
+ self.assertRaises(testtools.TestCase.skipException, fake,
+ self, expect_skip)
+ else:
+ fake(self, expect_skip)
+ mock_cmd.assert_called_once_with('nova', '', params='--version',
+ merge_stderr=True)
+
+ def test_min_client_version(self):
+ # required, installed, expect_skip
+ cases = (('2.17.0', '2.17.0', False),
+ ('2.17.0', '2.18.0', False),
+ ('2.18.0', '2.17.0', True))
+
+ for case in cases:
+ self._test_min_version(*case)
+
+ @mock.patch.object(cli, 'execute', return_value=' ')
+ def test_check_client_version_empty_output(self, mock_execute):
+ # Tests that an exception is raised if the command output is empty.
+ self.assertRaises(exceptions.TempestException,
+ cli.check_client_version, 'nova', '2.18.0')
diff --git a/tempest/tests/common/test_accounts.py b/tempest/tests/common/test_accounts.py
new file mode 100644
index 0000000..c24bfb6
--- /dev/null
+++ b/tempest/tests/common/test_accounts.py
@@ -0,0 +1,187 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+#
+# 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 hashlib
+import os
+import tempfile
+
+import mock
+from oslo.config import cfg
+from oslotest import mockpatch
+
+from tempest import auth
+from tempest.common import accounts
+from tempest.common import http
+from tempest import config
+from tempest import exceptions
+from tempest.tests import base
+from tempest.tests import fake_config
+from tempest.tests import fake_identity
+
+
+class TestAccount(base.TestCase):
+
+ def setUp(self):
+ super(TestAccount, self).setUp()
+ self.useFixture(fake_config.ConfigFixture())
+ self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
+ self.temp_dir = tempfile.mkdtemp()
+ cfg.CONF.set_default('lock_path', self.temp_dir)
+ self.addCleanup(os.rmdir, self.temp_dir)
+ self.test_accounts = [
+ {'username': 'test_user1', 'tenant_name': 'test_tenant1',
+ 'password': 'p'},
+ {'username': 'test_user2', 'tenant_name': 'test_tenant2',
+ 'password': 'p'},
+ {'username': 'test_user3', 'tenant_name': 'test_tenant3',
+ 'password': 'p'},
+ {'username': 'test_user4', 'tenant_name': 'test_tenant4',
+ 'password': 'p'},
+ {'username': 'test_user5', 'tenant_name': 'test_tenant5',
+ 'password': 'p'},
+ {'username': 'test_user6', 'tenant_name': 'test_tenant6',
+ 'password': 'p'},
+ ]
+ self.useFixture(mockpatch.Patch(
+ 'tempest.common.accounts.read_accounts_yaml',
+ return_value=self.test_accounts))
+ cfg.CONF.set_default('test_accounts_file', '', group='auth')
+
+ def _get_hash_list(self, accounts_list):
+ hash_list = []
+ for account in accounts_list:
+ hash = hashlib.md5()
+ hash.update(str(account))
+ hash_list.append(hash.hexdigest())
+ return hash_list
+
+ def test_get_hash(self):
+ self.stubs.Set(http.ClosingHttp, 'request',
+ fake_identity._fake_v2_response)
+ test_account_class = accounts.Accounts('test_name')
+ hash_list = self._get_hash_list(self.test_accounts)
+ test_cred_dict = self.test_accounts[3]
+ test_creds = auth.get_credentials(**test_cred_dict)
+ results = test_account_class.get_hash(test_creds)
+ self.assertEqual(hash_list[3], results)
+
+ def test_get_hash_dict(self):
+ test_account_class = accounts.Accounts('test_name')
+ hash_dict = test_account_class.get_hash_dict(self.test_accounts)
+ hash_list = self._get_hash_list(self.test_accounts)
+ for hash in hash_list:
+ self.assertIn(hash, hash_dict.keys())
+ self.assertIn(hash_dict[hash], self.test_accounts)
+
+ def test_create_hash_file_previous_file(self):
+ # Emulate the lock existing on the filesystem
+ self.useFixture(mockpatch.Patch('os.path.isfile', return_value=True))
+ with mock.patch('__builtin__.open', mock.mock_open(), create=True):
+ test_account_class = accounts.Accounts('test_name')
+ res = test_account_class._create_hash_file('12345')
+ self.assertFalse(res, "_create_hash_file should return False if the "
+ "pseudo-lock file already exists")
+
+ def test_create_hash_file_no_previous_file(self):
+ # Emulate the lock not existing on the filesystem
+ self.useFixture(mockpatch.Patch('os.path.isfile', return_value=False))
+ with mock.patch('__builtin__.open', mock.mock_open(), create=True):
+ test_account_class = accounts.Accounts('test_name')
+ res = test_account_class._create_hash_file('12345')
+ self.assertTrue(res, "_create_hash_file should return True if the "
+ "pseudo-lock doesn't already exist")
+
+ @mock.patch('tempest.openstack.common.lockutils.lock')
+ def test_get_free_hash_no_previous_accounts(self, lock_mock):
+ # Emulate no pre-existing lock
+ self.useFixture(mockpatch.Patch('os.path.isdir', return_value=False))
+ hash_list = self._get_hash_list(self.test_accounts)
+ mkdir_mock = self.useFixture(mockpatch.Patch('os.mkdir'))
+ self.useFixture(mockpatch.Patch('os.path.isfile', return_value=False))
+ test_account_class = accounts.Accounts('test_name')
+ with mock.patch('__builtin__.open', mock.mock_open(),
+ create=True) as open_mock:
+ test_account_class._get_free_hash(hash_list)
+ lock_path = os.path.join(accounts.CONF.lock_path, 'test_accounts',
+ hash_list[0])
+ open_mock.assert_called_once_with(lock_path, 'w')
+ mkdir_path = os.path.join(accounts.CONF.lock_path, 'test_accounts')
+ mkdir_mock.mock.assert_called_once_with(mkdir_path)
+
+ @mock.patch('tempest.openstack.common.lockutils.lock')
+ def test_get_free_hash_no_free_accounts(self, lock_mock):
+ hash_list = self._get_hash_list(self.test_accounts)
+ # Emulate pre-existing lock dir
+ self.useFixture(mockpatch.Patch('os.path.isdir', return_value=True))
+ # Emulate all lcoks in list are in use
+ self.useFixture(mockpatch.Patch('os.path.isfile', return_value=True))
+ test_account_class = accounts.Accounts('test_name')
+ self.assertRaises(exceptions.InvalidConfiguration,
+ test_account_class._get_free_hash, hash_list)
+
+ @mock.patch('tempest.openstack.common.lockutils.lock')
+ def test_get_free_hash_some_in_use_accounts(self, lock_mock):
+ # Emulate no pre-existing lock
+ self.useFixture(mockpatch.Patch('os.path.isdir', return_value=True))
+ hash_list = self._get_hash_list(self.test_accounts)
+ test_account_class = accounts.Accounts('test_name')
+
+ def _fake_is_file(path):
+ # Fake isfile() to return that the path exists unless a specific
+ # hash is in the path
+ if hash_list[3] in path:
+ return False
+ return True
+
+ self.stubs.Set(os.path, 'isfile', _fake_is_file)
+ with mock.patch('__builtin__.open', mock.mock_open(),
+ create=True) as open_mock:
+ test_account_class._get_free_hash(hash_list)
+ lock_path = os.path.join(accounts.CONF.lock_path, 'test_accounts',
+ hash_list[3])
+ open_mock.assert_called_once_with(lock_path, 'w')
+
+ @mock.patch('tempest.openstack.common.lockutils.lock')
+ def test_remove_hash_last_account(self, lock_mock):
+ hash_list = self._get_hash_list(self.test_accounts)
+ # Pretend the pseudo-lock is there
+ self.useFixture(mockpatch.Patch('os.path.isfile', return_value=True))
+ # Pretend the lock dir is empty
+ self.useFixture(mockpatch.Patch('os.listdir', return_value=[]))
+ test_account_class = accounts.Accounts('test_name')
+ remove_mock = self.useFixture(mockpatch.Patch('os.remove'))
+ rmdir_mock = self.useFixture(mockpatch.Patch('os.rmdir'))
+ test_account_class.remove_hash(hash_list[2])
+ hash_path = os.path.join(accounts.CONF.lock_path, 'test_accounts',
+ hash_list[2])
+ lock_path = os.path.join(accounts.CONF.lock_path, 'test_accounts')
+ remove_mock.mock.assert_called_once_with(hash_path)
+ rmdir_mock.mock.assert_called_once_with(lock_path)
+
+ @mock.patch('tempest.openstack.common.lockutils.lock')
+ def test_remove_hash_not_last_account(self, lock_mock):
+ hash_list = self._get_hash_list(self.test_accounts)
+ # Pretend the pseudo-lock is there
+ self.useFixture(mockpatch.Patch('os.path.isfile', return_value=True))
+ # Pretend the lock dir is empty
+ self.useFixture(mockpatch.Patch('os.listdir', return_value=[
+ hash_list[1], hash_list[4]]))
+ test_account_class = accounts.Accounts('test_name')
+ remove_mock = self.useFixture(mockpatch.Patch('os.remove'))
+ rmdir_mock = self.useFixture(mockpatch.Patch('os.rmdir'))
+ test_account_class.remove_hash(hash_list[2])
+ hash_path = os.path.join(accounts.CONF.lock_path, 'test_accounts',
+ hash_list[2])
+ remove_mock.mock.assert_called_once_with(hash_path)
+ rmdir_mock.mock.assert_not_called()
diff --git a/tempest/tests/common/test_custom_matchers.py b/tempest/tests/common/test_custom_matchers.py
new file mode 100644
index 0000000..57217e3
--- /dev/null
+++ b/tempest/tests/common/test_custom_matchers.py
@@ -0,0 +1,66 @@
+# 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.
+
+from tempest.common import custom_matchers
+from tempest.tests import base
+
+from testtools.tests.matchers import helpers
+
+
+class TestMatchesDictExceptForKeys(base.TestCase,
+ helpers.TestMatchersInterface):
+
+ matches_matcher = custom_matchers.MatchesDictExceptForKeys(
+ {'a': 1, 'b': 2, 'c': 3, 'd': 4}, ['c', 'd'])
+ matches_matches = [
+ {'a': 1, 'b': 2, 'c': 3, 'd': 4},
+ {'a': 1, 'b': 2, 'c': 5},
+ {'a': 1, 'b': 2},
+ ]
+ matches_mismatches = [
+ {},
+ {'foo': 1},
+ {'a': 1, 'b': 3},
+ {'a': 1, 'b': 2, 'foo': 1},
+ {'a': 1, 'b': None, 'foo': 1},
+ ]
+
+ str_examples = []
+ describe_examples = [
+ ("Only in expected:\n"
+ " {'a': 1, 'b': 2}\n",
+ {},
+ matches_matcher),
+ ("Only in expected:\n"
+ " {'a': 1, 'b': 2}\n"
+ "Only in actual:\n"
+ " {'foo': 1}\n",
+ {'foo': 1},
+ matches_matcher),
+ ("Differences:\n"
+ " b: expected 2, actual 3\n",
+ {'a': 1, 'b': 3},
+ matches_matcher),
+ ("Only in actual:\n"
+ " {'foo': 1}\n",
+ {'a': 1, 'b': 2, 'foo': 1},
+ matches_matcher),
+ ("Only in actual:\n"
+ " {'foo': 1}\n"
+ "Differences:\n"
+ " b: expected 2, actual None\n",
+ {'a': 1, 'b': None, 'foo': 1},
+ matches_matcher)
+ ]
\ No newline at end of file
diff --git a/tempest/tests/fake_config.py b/tempest/tests/fake_config.py
index 536cbcf..9e56916 100644
--- a/tempest/tests/fake_config.py
+++ b/tempest/tests/fake_config.py
@@ -61,3 +61,4 @@
def __init__(self, parse_conf=True, config_path=None):
cfg.CONF([], default_config_files=[])
self._set_attrs()
+ self.lock_path = cfg.CONF.lock_path
diff --git a/tempest/tests/fake_http.py b/tempest/tests/fake_http.py
index ce2b2c0..7d77484 100644
--- a/tempest/tests/fake_http.py
+++ b/tempest/tests/fake_http.py
@@ -13,6 +13,7 @@
# under the License.
import copy
+
import httplib2
diff --git a/tempest/tests/fake_identity.py b/tempest/tests/fake_identity.py
index 1900fc9..97098e1 100644
--- a/tempest/tests/fake_identity.py
+++ b/tempest/tests/fake_identity.py
@@ -13,9 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import httplib2
-import json
TOKEN = "fake_token"
diff --git a/tempest/tests/negative/test_negative_auto_test.py b/tempest/tests/negative/test_negative_auto_test.py
index 7a1909a..edff3a8 100644
--- a/tempest/tests/negative/test_negative_auto_test.py
+++ b/tempest/tests/negative/test_negative_auto_test.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
+
import mock
from tempest import config
@@ -30,9 +32,9 @@
"http-method": "GET",
"url": "flavors/detail",
"json-schema": {"type": "object",
- "properties":
- {"minRam": {"type": "integer"},
- "minDisk": {"type": "integer"}}
+ "properties":
+ {"minRam": {"type": "integer"},
+ "minDisk": {"type": "integer"}}
},
"resources": ["flavor", "volume", "image"]
}
@@ -70,3 +72,13 @@
self._check_prop_entries(scenarios, "prop_minRam")
self._check_prop_entries(scenarios, "prop_minDisk")
self._check_resource_entries(scenarios, "inv_res")
+
+ def test_load_schema(self):
+ json_schema = json.dumps(self.fake_input_desc)
+ with mock.patch('tempest.test.open',
+ mock.mock_open(read_data=json_schema),
+ create=True):
+ return_file = test.NegativeAutoTest.load_schema('filename')
+ self.assertEqual(return_file, self.fake_input_desc)
+ return_dict = test.NegativeAutoTest.load_schema(self.fake_input_desc)
+ self.assertEqual(return_file, return_dict)
diff --git a/tempest/tests/test_auth.py b/tempest/tests/test_auth.py
index 1dcddad..6a2e335 100644
--- a/tempest/tests/test_auth.py
+++ b/tempest/tests/test_auth.py
@@ -59,12 +59,24 @@
obviously don't test not implemented method or the ones which strongly
depends on them.
"""
- _auth_provider_class = auth.AuthProvider
- def test_check_credentials_class(self):
- self.assertRaises(NotImplementedError,
- self.auth_provider.check_credentials,
- auth.Credentials())
+ class FakeAuthProviderImpl(auth.AuthProvider):
+ def _decorate_request():
+ pass
+
+ def _fill_credentials():
+ pass
+
+ def _get_auth():
+ pass
+
+ def base_url():
+ pass
+
+ def is_expired():
+ pass
+
+ _auth_provider_class = FakeAuthProviderImpl
def test_check_credentials_bad_type(self):
self.assertFalse(self.auth_provider.check_credentials([]))
@@ -74,16 +86,6 @@
auth_provider = self._auth(credentials={})
self.assertIsInstance(auth_provider.credentials, auth.Credentials)
- def test_instantiate_with_bad_credentials_type(self):
- """
- Assure that credentials with bad type fail with TypeError
- """
- self.assertRaises(TypeError, self._auth, [])
-
- def test_auth_data_property(self):
- self.assertRaises(NotImplementedError, getattr, self.auth_provider,
- 'auth_data')
-
def test_auth_data_property_when_cache_exists(self):
self.auth_provider.cache = 'foo'
self.useFixture(mockpatch.PatchObject(self.auth_provider,
@@ -110,9 +112,10 @@
self.assertIsNone(self.auth_provider.alt_part)
self.assertIsNone(self.auth_provider.alt_auth_data)
- def test_fill_credentials(self):
- self.assertRaises(NotImplementedError,
- self.auth_provider.fill_credentials)
+ def test_auth_class(self):
+ self.assertRaises(TypeError,
+ auth.AuthProvider,
+ fake_credentials.FakeCredentials)
class TestKeystoneV2AuthProvider(BaseAuthTestsSetUp):
diff --git a/tempest/tests/test_commands.py b/tempest/tests/test_commands.py
index 1e2925b..2379741 100644
--- a/tempest/tests/test_commands.py
+++ b/tempest/tests/test_commands.py
@@ -12,9 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import mock
import subprocess
+import mock
+
from tempest.common import commands
from tempest.tests import base
diff --git a/tempest/tests/test_glance_http.py b/tempest/tests/test_glance_http.py
index 88b8129..9a6c9de 100644
--- a/tempest/tests/test_glance_http.py
+++ b/tempest/tests/test_glance_http.py
@@ -15,9 +15,10 @@
import httplib
import json
+import socket
+
import mock
import six
-import socket
from tempest.common import glance_http
from tempest import exceptions
@@ -41,17 +42,17 @@
self.fake_auth.base_url = mock.MagicMock(return_value=self.endpoint)
- self.useFixture(mockpatch.PatchObject(httplib.HTTPConnection,
- 'request',
- side_effect=self.fake_http.request(self.endpoint)[1]))
+ self.useFixture(mockpatch.PatchObject(
+ httplib.HTTPConnection,
+ 'request',
+ side_effect=self.fake_http.request(self.endpoint)[1]))
self.client = glance_http.HTTPClient(self.fake_auth, {})
def _set_response_fixture(self, header, status, resp_body):
resp = fake_http.fake_httplib(header, status=status,
body=six.StringIO(resp_body))
self.useFixture(mockpatch.PatchObject(httplib.HTTPConnection,
- 'getresponse',
- return_value=resp))
+ 'getresponse', return_value=resp))
return resp
def test_json_request_without_content_type_header_in_response(self):
diff --git a/tempest/tests/test_rest_client.py b/tempest/tests/test_rest_client.py
index a351bd5..5f55ca2 100644
--- a/tempest/tests/test_rest_client.py
+++ b/tempest/tests/test_rest_client.py
@@ -12,9 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import httplib2
import json
+import httplib2
from oslotest import mockpatch
from tempest.common import rest_client
diff --git a/tempest/tests/test_tenant_isolation.py b/tempest/tests/test_tenant_isolation.py
index bbc3d15..eddbb1d 100644
--- a/tempest/tests/test_tenant_isolation.py
+++ b/tempest/tests/test_tenant_isolation.py
@@ -335,9 +335,11 @@
remove_router_interface_mock = self.patch(
'tempest.services.network.json.network_client.NetworkClientJSON.'
'remove_router_interface_with_subnet_id')
+ return_values = ({'status': 200}, {'ports': []})
port_list_mock = mock.patch.object(iso_creds.network_admin_client,
- 'list_ports', return_value=(
- {'status': 200}, {'ports': []}))
+ 'list_ports',
+ return_value=return_values)
+
port_list_mock.start()
iso_creds.clear_isolated_creds()
# Verify remove router interface calls
diff --git a/tempest/thirdparty/README.rst b/tempest/thirdparty/README.rst
index 53cb54b..b0bfdf7 100644
--- a/tempest/thirdparty/README.rst
+++ b/tempest/thirdparty/README.rst
@@ -1,3 +1,5 @@
+.. _third_party_field_guide:
+
Tempest Field Guide to Third Party API tests
============================================
diff --git a/tempest/thirdparty/boto/test.py b/tempest/thirdparty/boto/test.py
index d3cbc4b..4bf71f3 100644
--- a/tempest/thirdparty/boto/test.py
+++ b/tempest/thirdparty/boto/test.py
@@ -17,7 +17,6 @@
import logging as orig_logging
import os
import re
-import six
import urlparse
import boto
@@ -25,6 +24,7 @@
from boto import exception
from boto import s3
import keystoneclient.exceptions
+import six
import tempest.clients
from tempest.common.utils import file_utils
@@ -187,7 +187,7 @@
if len(args):
string += ", "
string += ", ".join("=".join(map(str, (key, value)))
- for (key, value) in kwargs.items())
+ for (key, value) in kwargs.items())
return string + ")"
@@ -592,83 +592,83 @@
for code in (('AccessDenied', 403),
- ('AccountProblem', 403),
- ('AmbiguousGrantByEmailAddress', 400),
- ('BadDigest', 400),
- ('BucketAlreadyExists', 409),
- ('BucketAlreadyOwnedByYou', 409),
- ('BucketNotEmpty', 409),
- ('CredentialsNotSupported', 400),
- ('CrossLocationLoggingProhibited', 403),
- ('EntityTooSmall', 400),
- ('EntityTooLarge', 400),
- ('ExpiredToken', 400),
- ('IllegalVersioningConfigurationException', 400),
- ('IncompleteBody', 400),
- ('IncorrectNumberOfFilesInPostRequest', 400),
- ('InlineDataTooLarge', 400),
- ('InvalidAccessKeyId', 403),
+ ('AccountProblem', 403),
+ ('AmbiguousGrantByEmailAddress', 400),
+ ('BadDigest', 400),
+ ('BucketAlreadyExists', 409),
+ ('BucketAlreadyOwnedByYou', 409),
+ ('BucketNotEmpty', 409),
+ ('CredentialsNotSupported', 400),
+ ('CrossLocationLoggingProhibited', 403),
+ ('EntityTooSmall', 400),
+ ('EntityTooLarge', 400),
+ ('ExpiredToken', 400),
+ ('IllegalVersioningConfigurationException', 400),
+ ('IncompleteBody', 400),
+ ('IncorrectNumberOfFilesInPostRequest', 400),
+ ('InlineDataTooLarge', 400),
+ ('InvalidAccessKeyId', 403),
'InvalidAddressingHeader',
- ('InvalidArgument', 400),
- ('InvalidBucketName', 400),
- ('InvalidBucketState', 409),
- ('InvalidDigest', 400),
- ('InvalidLocationConstraint', 400),
- ('InvalidPart', 400),
- ('InvalidPartOrder', 400),
- ('InvalidPayer', 403),
- ('InvalidPolicyDocument', 400),
- ('InvalidRange', 416),
- ('InvalidRequest', 400),
- ('InvalidSecurity', 403),
- ('InvalidSOAPRequest', 400),
- ('InvalidStorageClass', 400),
- ('InvalidTargetBucketForLogging', 400),
- ('InvalidToken', 400),
- ('InvalidURI', 400),
- ('KeyTooLong', 400),
- ('MalformedACLError', 400),
- ('MalformedPOSTRequest', 400),
- ('MalformedXML', 400),
- ('MaxMessageLengthExceeded', 400),
- ('MaxPostPreDataLengthExceededError', 400),
- ('MetadataTooLarge', 400),
- ('MethodNotAllowed', 405),
- ('MissingAttachment'),
- ('MissingContentLength', 411),
- ('MissingRequestBodyError', 400),
- ('MissingSecurityElement', 400),
- ('MissingSecurityHeader', 400),
- ('NoLoggingStatusForKey', 400),
- ('NoSuchBucket', 404),
- ('NoSuchKey', 404),
- ('NoSuchLifecycleConfiguration', 404),
- ('NoSuchUpload', 404),
- ('NoSuchVersion', 404),
- ('NotSignedUp', 403),
- ('NotSuchBucketPolicy', 404),
- ('OperationAborted', 409),
- ('PermanentRedirect', 301),
- ('PreconditionFailed', 412),
- ('Redirect', 307),
- ('RequestIsNotMultiPartContent', 400),
- ('RequestTimeout', 400),
- ('RequestTimeTooSkewed', 403),
- ('RequestTorrentOfBucketError', 400),
- ('SignatureDoesNotMatch', 403),
- ('TemporaryRedirect', 307),
- ('TokenRefreshRequired', 400),
- ('TooManyBuckets', 400),
- ('UnexpectedContent', 400),
- ('UnresolvableGrantByEmailAddress', 400),
- ('UserKeyMustBeSpecified', 400)):
+ ('InvalidArgument', 400),
+ ('InvalidBucketName', 400),
+ ('InvalidBucketState', 409),
+ ('InvalidDigest', 400),
+ ('InvalidLocationConstraint', 400),
+ ('InvalidPart', 400),
+ ('InvalidPartOrder', 400),
+ ('InvalidPayer', 403),
+ ('InvalidPolicyDocument', 400),
+ ('InvalidRange', 416),
+ ('InvalidRequest', 400),
+ ('InvalidSecurity', 403),
+ ('InvalidSOAPRequest', 400),
+ ('InvalidStorageClass', 400),
+ ('InvalidTargetBucketForLogging', 400),
+ ('InvalidToken', 400),
+ ('InvalidURI', 400),
+ ('KeyTooLong', 400),
+ ('MalformedACLError', 400),
+ ('MalformedPOSTRequest', 400),
+ ('MalformedXML', 400),
+ ('MaxMessageLengthExceeded', 400),
+ ('MaxPostPreDataLengthExceededError', 400),
+ ('MetadataTooLarge', 400),
+ ('MethodNotAllowed', 405),
+ ('MissingAttachment'),
+ ('MissingContentLength', 411),
+ ('MissingRequestBodyError', 400),
+ ('MissingSecurityElement', 400),
+ ('MissingSecurityHeader', 400),
+ ('NoLoggingStatusForKey', 400),
+ ('NoSuchBucket', 404),
+ ('NoSuchKey', 404),
+ ('NoSuchLifecycleConfiguration', 404),
+ ('NoSuchUpload', 404),
+ ('NoSuchVersion', 404),
+ ('NotSignedUp', 403),
+ ('NotSuchBucketPolicy', 404),
+ ('OperationAborted', 409),
+ ('PermanentRedirect', 301),
+ ('PreconditionFailed', 412),
+ ('Redirect', 307),
+ ('RequestIsNotMultiPartContent', 400),
+ ('RequestTimeout', 400),
+ ('RequestTimeTooSkewed', 403),
+ ('RequestTorrentOfBucketError', 400),
+ ('SignatureDoesNotMatch', 403),
+ ('TemporaryRedirect', 307),
+ ('TokenRefreshRequired', 400),
+ ('TooManyBuckets', 400),
+ ('UnexpectedContent', 400),
+ ('UnresolvableGrantByEmailAddress', 400),
+ ('UserKeyMustBeSpecified', 400)):
_add_matcher_class(BotoTestCase.s3_error_code.client,
code, base=ClientError)
for code in (('InternalError', 500),
- ('NotImplemented', 501),
- ('ServiceUnavailable', 503),
- ('SlowDown', 503)):
+ ('NotImplemented', 501),
+ ('ServiceUnavailable', 503),
+ ('SlowDown', 503)):
_add_matcher_class(BotoTestCase.s3_error_code.server,
code, base=ServerError)
diff --git a/tools/check_logs.py b/tools/check_logs.py
index bc4eaca..eab9f73 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -22,6 +22,7 @@
import StringIO
import sys
import urllib2
+
import yaml
diff --git a/tools/colorizer.py b/tools/colorizer.py
index a3a0616..e7152f2 100755
--- a/tools/colorizer.py
+++ b/tools/colorizer.py
@@ -42,10 +42,10 @@
"""Display a subunit stream through a colorized unittest test runner."""
import heapq
-import subunit
import sys
import unittest
+import subunit
import testtools
diff --git a/tools/find_stack_traces.py b/tools/find_stack_traces.py
index c905976..4862d01 100755
--- a/tools/find_stack_traces.py
+++ b/tools/find_stack_traces.py
@@ -16,12 +16,13 @@
# under the License.
import gzip
+import pprint
import re
import StringIO
import sys
import urllib2
-import pprint
+
pp = pprint.PrettyPrinter()
NOVA_TIMESTAMP = r"\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d"
diff --git a/tox.ini b/tox.ini
index 4f2465a..edcb901 100644
--- a/tox.ini
+++ b/tox.ini
@@ -6,23 +6,28 @@
[testenv]
setenv = VIRTUAL_ENV={envdir}
OS_TEST_PATH=./tempest/test_discover
+ PYTHONHASHSEED=0
usedevelop = True
install_command = pip install -U {opts} {packages}
[testenv:py26]
setenv = OS_TEST_PATH=./tempest/tests
+ PYTHONHASHSEED=0
commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
[testenv:py33]
setenv = OS_TEST_PATH=./tempest/tests
+ PYTHONHASHSEED=0
commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
[testenv:py27]
setenv = OS_TEST_PATH=./tempest/tests
+ PYTHONHASHSEED=0
commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
[testenv:cover]
setenv = OS_TEST_PATH=./tempest/tests
+ PYTHONHASHSEED=0
commands = python setup.py testr --coverage --testr-arg='tempest\.tests {posargs}'
[testenv:all]
@@ -104,7 +109,10 @@
[flake8]
# E125 is a won't fix until https://github.com/jcrocholl/pep8/issues/126 is resolved. For further detail see https://review.openstack.org/#/c/36788/
# H402 skipped because some docstrings aren't sentences
-# Skipped because of new hacking 0.9: H407,H405,H904,H305,E123,H307,E122,E129,E128
-ignore = E125,H402,H404,H407,H405,H904,H305,E123,H307,E122,E129,E128
+# E123 skipped because it is ignored by default in the default pep8
+# E129 skipped because it is too limiting when combined with other rules
+# H305 skipped because it is inconsistent between python versions
+# Skipped because of new hacking 0.9: H405,H904
+ignore = E125,H402,E123,E129,H404,H405,H904,H305
show-source = True
exclude = .git,.venv,.tox,dist,doc,openstack,*egg