Merge "Fix stress runner signal related issues"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 70c791b..4a567e7 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -250,6 +250,19 @@
# Should the tests ssh to instances? (boolean value)
#run_ssh=false
+# Auth method used for authenticate to the instance. Valid
+# choices are: keypair, configured, adminpass. keypair: start
+# the servers with an ssh keypair. configured: use the
+# configured user and password. adminpass: use the injected
+# adminPass. disabled: avoid using ssh when it is an option.
+# (string value)
+#ssh_auth_method=keypair
+
+# How to connect to the instance? fixed: using the first ip
+# belongs the fixed network floating: creating and using a
+# floating ip (string value)
+#ssh_connect_method=fixed
+
# User name used to authenticate to an instance. (string
# value)
#ssh_user=root
diff --git a/requirements.txt b/requirements.txt
index e97eece..fe3e5e5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -20,7 +20,7 @@
keyring>=2.1
testrepository>=0.0.18
oslo.config>=1.2.0
-six>=1.5.2
+six>=1.6.0
iso8601>=0.1.9
fixtures>=0.3.14
testscenarios>=0.4
diff --git a/tempest/api/baremetal/test_nodestates.py b/tempest/api/baremetal/test_nodestates.py
new file mode 100644
index 0000000..c658d7f
--- /dev/null
+++ b/tempest/api/baremetal/test_nodestates.py
@@ -0,0 +1,33 @@
+# 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.
+
+from tempest.api.baremetal import base
+from tempest import test
+
+
+class TestNodeStates(base.BaseBaremetalTest):
+ """Tests for baremetal NodeStates."""
+
+ @classmethod
+ def setUpClass(self):
+ super(TestNodeStates, self).setUpClass()
+ chassis = self.create_chassis()['chassis']
+ self.node = self.create_node(chassis['uuid'])['node']
+
+ @test.attr(type='smoke')
+ def test_list_nodestates(self):
+ resp, nodestates = self.client.list_nodestates(self.node['uuid'])
+ self.assertEqual('200', resp['status'])
+ for key in nodestates:
+ self.assertEqual(nodestates[key], self.node[key])
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index fb249e5..c2376c9 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -16,6 +16,7 @@
from tempest.api.compute import base
from tempest.common import tempest_fixtures as fixtures
from tempest.common.utils import data_utils
+from tempest import exceptions
from tempest import test
@@ -39,11 +40,20 @@
filter(lambda y: y['service'] == 'compute', hosts_all))
cls.host = hosts[0]
+ def _try_delete_aggregate(self, aggregate_id):
+ # delete aggregate, if it exists
+ try:
+ self.client.delete_aggregate(aggregate_id)
+ # if aggregate not found, it depict it was deleted in the test
+ except exceptions.NotFound:
+ pass
+
@test.attr(type='gate')
def test_aggregate_create_delete(self):
# Create and delete an aggregate.
aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
resp, aggregate = self.client.create_aggregate(name=aggregate_name)
+ self.addCleanup(self._try_delete_aggregate, aggregate['id'])
self.assertEqual(200, resp.status)
self.assertEqual(aggregate_name, aggregate['name'])
self.assertIsNone(aggregate['availability_zone'])
@@ -59,6 +69,7 @@
az_name = data_utils.rand_name(self.az_name_prefix)
resp, aggregate = self.client.create_aggregate(
name=aggregate_name, availability_zone=az_name)
+ self.addCleanup(self._try_delete_aggregate, aggregate['id'])
self.assertEqual(200, resp.status)
self.assertEqual(aggregate_name, aggregate['name'])
self.assertEqual(az_name, aggregate['availability_zone'])
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index 1f2ddf4..49af645 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -14,7 +14,6 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
-from tempest import exceptions
from tempest import test
@@ -44,16 +43,6 @@
wait_until='ACTIVE')
cls.s2_id = server['id']
- def _get_unused_flavor_id(self):
- flavor_id = data_utils.rand_int_id(start=1000)
- while True:
- try:
- resp, body = self.flavors_client.get_flavor_details(flavor_id)
- except exceptions.NotFound:
- break
- flavor_id = data_utils.rand_int_id(start=1000)
- return flavor_id
-
@test.attr(type='gate')
def test_list_servers_by_admin(self):
# Listing servers by admin user returns empty list by default
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index d1bda83..a65d7b7 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -203,6 +203,13 @@
LOG.warn("Unable to delete volume '%s' since it was not found. "
"Maybe it was already deleted?" % volume_id)
+ @classmethod
+ def prepare_instance_network(cls):
+ if (CONF.compute.ssh_auth_method != 'disabled' and
+ CONF.compute.ssh_connect_method == 'floating'):
+ cls.set_network_resources(network=True, subnet=True, router=True,
+ dhcp=True)
+
class BaseV2ComputeTest(BaseComputeTest):
@@ -312,11 +319,6 @@
@classmethod
def setUpClass(cls):
# By default compute tests do not create network resources
- if cls._interface == "xml":
- skip_msg = ("XML interface is being removed from Nova v3. "
- "%s will be removed shortly" % cls.__name__)
- raise cls.skipException(skip_msg)
-
if not CONF.compute_feature_enabled.api_v3:
skip_msg = ("%s skipped as nova v3 api is not available" %
cls.__name__)
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions.py b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
index abd8a4c..b3789f8 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
@@ -45,6 +45,14 @@
resp, body = cls.client.delete_floating_ip(cls.floating_ip_id)
super(FloatingIPsTestJSON, cls).tearDownClass()
+ def _try_delete_floating_ip(self, floating_ip_id):
+ # delete floating ip, if it exists
+ try:
+ self.client.delete_floating_ip(floating_ip_id)
+ # if not found, it depicts it was deleted in the test
+ except exceptions.NotFound:
+ pass
+
@test.attr(type='gate')
def test_allocate_floating_ip(self):
# Positive test:Allocation of a new floating IP to a project
@@ -66,6 +74,7 @@
# should be successful
# Creating the floating IP that is to be deleted in this method
resp, floating_ip_body = self.client.create_floating_ip()
+ self.addCleanup(self._try_delete_floating_ip, floating_ip_body['id'])
# Storing the details of floating IP before deleting it
cli_resp = self.client.get_floating_ip_details(floating_ip_body['id'])
resp, floating_ip_details = cli_resp
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index f0a8c8d..68e17ea 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -32,6 +32,7 @@
@classmethod
def setUpClass(cls):
+ cls.prepare_instance_network()
super(ServersTestJSON, cls).setUpClass()
cls.meta = {'hello': 'world'}
cls.accessIPv4 = '1.1.1.1'
@@ -114,26 +115,10 @@
@classmethod
def setUpClass(cls):
+ cls.prepare_instance_network()
super(ServersWithSpecificFlavorTestJSON, cls).setUpClass()
- cls.meta = {'hello': 'world'}
- cls.accessIPv4 = '1.1.1.1'
- cls.accessIPv6 = '0000:0000:0000:0000:0000:babe:220.12.22.2'
- cls.name = data_utils.rand_name('server')
- file_contents = 'This is a test file.'
- personality = [{'path': '/test.txt',
- 'contents': base64.b64encode(file_contents)}]
- cls.client = cls.servers_client
cls.flavor_client = cls.os_adm.flavors_client
- cli_resp = cls.create_test_server(name=cls.name,
- meta=cls.meta,
- accessIPv4=cls.accessIPv4,
- accessIPv6=cls.accessIPv6,
- personality=personality,
- disk_config=cls.disk_config)
- cls.resp, cls.server_initial = cli_resp
- cls.password = cls.server_initial['adminPass']
- cls.client.wait_for_server_status(cls.server_initial['id'], 'ACTIVE')
- resp, cls.server = cls.client.get_server(cls.server_initial['id'])
+ cls.client = cls.servers_client
@testtools.skipUnless(CONF.compute.run_ssh,
'Instance validation tests are disabled.')
@@ -141,7 +126,7 @@
def test_verify_created_server_ephemeral_disk(self):
# Verify that the ephemeral disk is created when creating server
- def create_flavor_with_extra_specs(self):
+ def create_flavor_with_extra_specs():
flavor_with_eph_disk_name = data_utils.rand_name('eph_flavor')
flavor_with_eph_disk_id = data_utils.rand_int_id(start=1000)
ram = 64
@@ -154,12 +139,12 @@
ram, vcpus, disk,
flavor_with_eph_disk_id,
ephemeral=1))
- self.addCleanup(self.flavor_clean_up, flavor['id'])
+ self.addCleanup(flavor_clean_up, flavor['id'])
self.assertEqual(200, resp.status)
return flavor['id']
- def create_flavor_without_extra_specs(self):
+ def create_flavor_without_extra_specs():
flavor_no_eph_disk_name = data_utils.rand_name('no_eph_flavor')
flavor_no_eph_disk_id = data_utils.rand_int_id(start=1000)
@@ -172,18 +157,18 @@
create_flavor(flavor_no_eph_disk_name,
ram, vcpus, disk,
flavor_no_eph_disk_id))
- self.addCleanup(self.flavor_clean_up, flavor['id'])
+ self.addCleanup(flavor_clean_up, flavor['id'])
self.assertEqual(200, resp.status)
return flavor['id']
- def flavor_clean_up(self, flavor_id):
+ def flavor_clean_up(flavor_id):
resp, body = self.flavor_client.delete_flavor(flavor_id)
self.assertEqual(resp.status, 202)
self.flavor_client.wait_for_resource_deletion(flavor_id)
- flavor_with_eph_disk_id = self.create_flavor_with_extra_specs()
- flavor_no_eph_disk_id = self.create_flavor_without_extra_specs()
+ flavor_with_eph_disk_id = create_flavor_with_extra_specs()
+ flavor_no_eph_disk_id = create_flavor_without_extra_specs()
admin_pass = self.image_ssh_password
@@ -196,13 +181,18 @@
adminPass=admin_pass,
flavor=flavor_with_eph_disk_id))
# Get partition number of server without extra specs.
+ _, server_no_eph_disk = self.client.get_server(
+ server_no_eph_disk['id'])
linux_client = remote_client.RemoteClient(server_no_eph_disk,
- self.ssh_user, self.password)
- partition_num = len(linux_client.get_partitions())
+ self.ssh_user, admin_pass)
+ partition_num = len(linux_client.get_partitions().split('\n'))
+ _, server_with_eph_disk = self.client.get_server(
+ server_with_eph_disk['id'])
linux_client = remote_client.RemoteClient(server_with_eph_disk,
- self.ssh_user, self.password)
- self.assertEqual(partition_num + 1, linux_client.get_partitions())
+ self.ssh_user, admin_pass)
+ partition_num_emph = len(linux_client.get_partitions().split('\n'))
+ self.assertEqual(partition_num + 1, partition_num_emph)
class ServersTestManualDisk(ServersTestJSON):
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index f0913f1..f66020c 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -28,6 +28,7 @@
@classmethod
@test.safe_setup
def setUpClass(cls):
+ cls.set_network_resources(network=True, subnet=True, dhcp=True)
super(ListServerFiltersTestJSON, cls).setUpClass()
cls.client = cls.servers_client
@@ -69,8 +70,12 @@
resp, cls.s3 = cls.create_test_server(name=cls.s3_name,
flavor=cls.flavor_ref_alt,
wait_until='ACTIVE')
-
- cls.fixed_network_name = CONF.compute.fixed_network_name
+ if (CONF.service_available.neutron and
+ CONF.compute.allow_tenant_isolation):
+ network = cls.isolated_creds.get_primary_network()
+ cls.fixed_network_name = network['name']
+ else:
+ cls.fixed_network_name = CONF.compute.fixed_network_name
@utils.skip_unless_attr('multiple_images', 'Only one image found')
@test.attr(type='gate')
@@ -226,7 +231,6 @@
self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers))
self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
- @test.skip_because(bug="1170718")
@test.attr(type='gate')
def test_list_servers_filtered_by_ip(self):
# Filter servers by ip
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index cc2d1ee..1f2bca9 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -44,6 +44,7 @@
@classmethod
def setUpClass(cls):
+ cls.prepare_instance_network()
super(ServerActionsTestJSON, cls).setUpClass()
cls.client = cls.servers_client
cls.server_id = cls.rebuild_server(None)
diff --git a/tempest/api/compute/servers/test_server_rescue_negative.py b/tempest/api/compute/servers/test_server_rescue_negative.py
index 65797e9..dae4709 100644
--- a/tempest/api/compute/servers/test_server_rescue_negative.py
+++ b/tempest/api/compute/servers/test_server_rescue_negative.py
@@ -12,12 +12,16 @@
# 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.common.utils import data_utils
+from tempest import config
from tempest import exceptions
from tempest import test
+CONF = config.CONF
+
class ServerRescueNegativeTestJSON(base.BaseV2ComputeTest):
@@ -67,6 +71,8 @@
self.assertEqual(202, resp.status)
self.servers_client.wait_for_server_status(server_id, 'ACTIVE')
+ @testtools.skipUnless(CONF.compute_feature_enabled.pause,
+ 'Pause is not available.')
@test.attr(type=['negative', 'gate'])
def test_rescue_paused_instance(self):
# Rescue a paused server
diff --git a/tempest/api/compute/v3/admin/test_servers.py b/tempest/api/compute/v3/admin/test_servers.py
index 579a535..366cfc6 100644
--- a/tempest/api/compute/v3/admin/test_servers.py
+++ b/tempest/api/compute/v3/admin/test_servers.py
@@ -14,7 +14,6 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
-from tempest import exceptions
from tempest import test
@@ -44,16 +43,6 @@
wait_until='ACTIVE')
cls.s2_id = server['id']
- def _get_unused_flavor_id(self):
- flavor_id = data_utils.rand_int_id(start=1000)
- while True:
- try:
- resp, body = self.flavors_client.get_flavor_details(flavor_id)
- except exceptions.NotFound:
- break
- flavor_id = data_utils.rand_int_id(start=1000)
- return flavor_id
-
@test.attr(type='gate')
def test_list_servers_by_admin(self):
# Listing servers by admin user returns empty list by default
diff --git a/tempest/api/compute/v3/servers/test_attach_volume.py b/tempest/api/compute/v3/servers/test_attach_volume.py
index 28d8517..e994c7f 100644
--- a/tempest/api/compute/v3/servers/test_attach_volume.py
+++ b/tempest/api/compute/v3/servers/test_attach_volume.py
@@ -33,6 +33,7 @@
@classmethod
def setUpClass(cls):
+ cls.prepare_instance_network()
super(AttachVolumeV3Test, cls).setUpClass()
cls.device = CONF.compute.volume_device_name
if not CONF.service_available.cinder:
diff --git a/tempest/api/compute/v3/servers/test_create_server.py b/tempest/api/compute/v3/servers/test_create_server.py
index 80c40a2..c508c52 100644
--- a/tempest/api/compute/v3/servers/test_create_server.py
+++ b/tempest/api/compute/v3/servers/test_create_server.py
@@ -32,6 +32,7 @@
@classmethod
def setUpClass(cls):
+ cls.prepare_instance_network()
super(ServersV3Test, cls).setUpClass()
cls.meta = {'hello': 'world'}
cls.accessIPv4 = '1.1.1.1'
@@ -115,26 +116,10 @@
@classmethod
def setUpClass(cls):
+ cls.prepare_instance_network()
super(ServersWithSpecificFlavorV3Test, cls).setUpClass()
- cls.meta = {'hello': 'world'}
- cls.accessIPv4 = '1.1.1.1'
- cls.accessIPv6 = '0000:0000:0000:0000:0000:babe:220.12.22.2'
- cls.name = data_utils.rand_name('server')
- file_contents = 'This is a test file.'
- personality = [{'path': '/test.txt',
- 'contents': base64.b64encode(file_contents)}]
cls.client = cls.servers_client
cls.flavor_client = cls.flavors_admin_client
- cli_resp = cls.create_test_server(name=cls.name,
- meta=cls.meta,
- access_ip_v4=cls.accessIPv4,
- access_ip_v6=cls.accessIPv6,
- personality=personality,
- disk_config=cls.disk_config)
- cls.resp, cls.server_initial = cli_resp
- cls.password = cls.server_initial['admin_password']
- cls.client.wait_for_server_status(cls.server_initial['id'], 'ACTIVE')
- resp, cls.server = cls.client.get_server(cls.server_initial['id'])
@testtools.skipUnless(CONF.compute.run_ssh,
'Instance validation tests are disabled.')
@@ -142,7 +127,7 @@
def test_verify_created_server_ephemeral_disk(self):
# Verify that the ephemeral disk is created when creating server
- def create_flavor_with_extra_specs(self):
+ def create_flavor_with_extra_specs():
flavor_with_eph_disk_name = data_utils.rand_name('eph_flavor')
flavor_with_eph_disk_id = data_utils.rand_int_id(start=1000)
ram = 512
@@ -154,13 +139,13 @@
create_flavor(flavor_with_eph_disk_name,
ram, vcpus, disk,
flavor_with_eph_disk_id,
- ephemeral=1, swap=1024, rxtx=1))
- self.addCleanup(self.flavor_clean_up, flavor['id'])
- self.assertEqual(200, resp.status)
+ ephemeral=1, rxtx=1))
+ self.addCleanup(flavor_clean_up, flavor['id'])
+ self.assertEqual(201, resp.status)
return flavor['id']
- def create_flavor_without_extra_specs(self):
+ def create_flavor_without_extra_specs():
flavor_no_eph_disk_name = data_utils.rand_name('no_eph_flavor')
flavor_no_eph_disk_id = data_utils.rand_int_id(start=1000)
@@ -173,18 +158,18 @@
create_flavor(flavor_no_eph_disk_name,
ram, vcpus, disk,
flavor_no_eph_disk_id))
- self.addCleanup(self.flavor_clean_up, flavor['id'])
- self.assertEqual(200, resp.status)
+ self.addCleanup(flavor_clean_up, flavor['id'])
+ self.assertEqual(201, resp.status)
return flavor['id']
- def flavor_clean_up(self, flavor_id):
+ def flavor_clean_up(flavor_id):
resp, body = self.flavor_client.delete_flavor(flavor_id)
- self.assertEqual(resp.status, 202)
+ self.assertEqual(resp.status, 204)
self.flavor_client.wait_for_resource_deletion(flavor_id)
- flavor_with_eph_disk_id = self.create_flavor_with_extra_specs()
- flavor_no_eph_disk_id = self.create_flavor_without_extra_specs()
+ flavor_with_eph_disk_id = create_flavor_with_extra_specs()
+ flavor_no_eph_disk_id = create_flavor_without_extra_specs()
admin_pass = self.image_ssh_password
@@ -197,13 +182,17 @@
adminPass=admin_pass,
flavor=flavor_with_eph_disk_id))
# Get partition number of server without extra specs.
+ _, server_no_eph_disk = self.client.get_server(
+ server_no_eph_disk['id'])
linux_client = remote_client.RemoteClient(server_no_eph_disk,
- self.ssh_user, self.password)
- partition_num = len(linux_client.get_partitions())
-
+ self.ssh_user, admin_pass)
+ partition_num = len(linux_client.get_partitions().split('\n'))
+ _, server_with_eph_disk = self.client.get_server(
+ server_with_eph_disk['id'])
linux_client = remote_client.RemoteClient(server_with_eph_disk,
- self.ssh_user, self.password)
- self.assertEqual(partition_num + 1, linux_client.get_partitions())
+ self.ssh_user, admin_pass)
+ partition_num_emph = len(linux_client.get_partitions().split('\n'))
+ self.assertEqual(partition_num + 1, partition_num_emph)
class ServersV3TestManualDisk(ServersV3Test):
diff --git a/tempest/api/compute/v3/servers/test_delete_server.py b/tempest/api/compute/v3/servers/test_delete_server.py
index 8f85557..add69ab 100644
--- a/tempest/api/compute/v3/servers/test_delete_server.py
+++ b/tempest/api/compute/v3/servers/test_delete_server.py
@@ -56,6 +56,8 @@
self.assertEqual('204', resp['status'])
self.client.wait_for_server_termination(server['id'])
+ @testtools.skipUnless(CONF.compute_feature_enabled.pause,
+ 'Pause is not available.')
@test.attr(type='gate')
def test_delete_server_while_in_pause_state(self):
# Delete a server while it's VM state is Pause
diff --git a/tempest/api/compute/v3/servers/test_list_server_filters.py b/tempest/api/compute/v3/servers/test_list_server_filters.py
index 2cb176c..778b033 100644
--- a/tempest/api/compute/v3/servers/test_list_server_filters.py
+++ b/tempest/api/compute/v3/servers/test_list_server_filters.py
@@ -28,6 +28,7 @@
@classmethod
@test.safe_setup
def setUpClass(cls):
+ cls.set_network_resources(network=True, subnet=True, dhcp=True)
super(ListServerFiltersV3Test, cls).setUpClass()
cls.client = cls.servers_client
@@ -70,7 +71,12 @@
flavor=cls.flavor_ref_alt,
wait_until='ACTIVE')
- cls.fixed_network_name = CONF.compute.fixed_network_name
+ if (CONF.service_available.neutron and
+ CONF.compute.allow_tenant_isolation):
+ network = cls.isolated_creds.get_primary_network()
+ cls.fixed_network_name = network['name']
+ else:
+ cls.fixed_network_name = CONF.compute.fixed_network_name
@utils.skip_unless_attr('multiple_images', 'Only one image found')
@test.attr(type='gate')
@@ -226,7 +232,6 @@
self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers))
self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
- @test.skip_because(bug="1170718")
@test.attr(type='gate')
def test_list_servers_filtered_by_ip(self):
# Filter servers by ip
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
index c377c30..1495cb7 100644
--- a/tempest/api/compute/v3/servers/test_server_actions.py
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -41,6 +41,7 @@
@classmethod
def setUpClass(cls):
+ cls.prepare_instance_network()
super(ServerActionsV3Test, cls).setUpClass()
cls.client = cls.servers_client
cls.server_id = cls.rebuild_server(None)
@@ -329,6 +330,8 @@
self.wait_for(self._get_output)
+ @testtools.skipUnless(CONF.compute_feature_enabled.pause,
+ 'Pause is not available.')
@test.attr(type='gate')
def test_pause_unpause_server(self):
resp, server = self.client.pause_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 08fb127..eb6bcdd 100644
--- a/tempest/api/compute/v3/servers/test_server_rescue_negative.py
+++ b/tempest/api/compute/v3/servers/test_server_rescue_negative.py
@@ -12,12 +12,16 @@
# 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.common.utils import data_utils
+from tempest import config
from tempest import exceptions
from tempest import test
+CONF = config.CONF
+
class ServerRescueNegativeV3Test(base.BaseV3ComputeTest):
@@ -66,6 +70,8 @@
self.assertEqual(202, resp.status)
self.servers_client.wait_for_server_status(server_id, 'ACTIVE')
+ @testtools.skipUnless(CONF.compute_feature_enabled.pause,
+ 'Pause is not available.')
@test.attr(type=['negative', 'gate'])
def test_rescue_paused_instance(self):
# Rescue a paused server
diff --git a/tempest/api/compute/v3/servers/test_servers_negative.py b/tempest/api/compute/v3/servers/test_servers_negative.py
index 586a52a..c1d1935 100644
--- a/tempest/api/compute/v3/servers/test_servers_negative.py
+++ b/tempest/api/compute/v3/servers/test_servers_negative.py
@@ -115,6 +115,8 @@
self.assertRaises(exceptions.NotFound, self.client.reboot,
nonexistent_server, 'SOFT')
+ @testtools.skipUnless(CONF.compute_feature_enabled.pause,
+ 'Pause is not available.')
@test.attr(type=['negative', 'gate'])
def test_pause_paused_server(self):
# Pause a paused server.
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index ab9d144..4585912 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -33,6 +33,7 @@
@classmethod
def setUpClass(cls):
+ cls.prepare_instance_network()
super(AttachVolumeTestJSON, cls).setUpClass()
cls.device = CONF.compute.volume_device_name
if not CONF.service_available.cinder:
diff --git a/tempest/api/data_processing/base.py b/tempest/api/data_processing/base.py
index 84d5be6..fc313f2 100644
--- a/tempest/api/data_processing/base.py
+++ b/tempest/api/data_processing/base.py
@@ -14,6 +14,7 @@
# limitations under the License.
from tempest import config
+from tempest import exceptions
import tempest.test
@@ -27,46 +28,35 @@
def setUpClass(cls):
super(BaseDataProcessingTest, cls).setUpClass()
if not CONF.service_available.sahara:
- raise cls.skipException("Sahara support is required")
+ raise cls.skipException('Sahara support is required')
os = cls.get_client_manager()
cls.client = os.data_processing_client
- # set some constants
cls.flavor_ref = CONF.compute.flavor_ref
- cls.simple_node_group_template = {
- 'plugin_name': 'vanilla',
- 'hadoop_version': '1.2.1',
- 'node_processes': [
- "datanode",
- "tasktracker"
- ],
- 'flavor_id': cls.flavor_ref,
- 'node_configs': {
- 'HDFS': {
- 'Data Node Heap Size': 1024
- },
- 'MapReduce': {
- 'Task Tracker Heap Size': 1024
- }
- }
- }
# add lists for watched resources
cls._node_group_templates = []
+ cls._cluster_templates = []
@classmethod
def tearDownClass(cls):
- # cleanup node group templates
- for ngt_id in getattr(cls, '_node_group_templates', []):
- try:
- cls.client.delete_node_group_template(ngt_id)
- except Exception:
- # ignore errors while auto removing created resource
- pass
+ cls.cleanup_resources(getattr(cls, '_cluster_templates', []),
+ cls.client.delete_cluster_template)
+ cls.cleanup_resources(getattr(cls, '_node_group_templates', []),
+ cls.client.delete_node_group_template)
cls.clear_isolated_creds()
super(BaseDataProcessingTest, cls).tearDownClass()
+ @staticmethod
+ def cleanup_resources(resource_id_list, method):
+ for resource_id in resource_id_list:
+ try:
+ method(resource_id)
+ except exceptions.NotFound:
+ # ignore errors while auto removing created resource
+ pass
+
@classmethod
def create_node_group_template(cls, name, plugin_name, hadoop_version,
node_processes, flavor_id,
@@ -77,16 +67,32 @@
object. All resources created in this method will be automatically
removed in tearDownClass method.
"""
-
resp, body = cls.client.create_node_group_template(name, plugin_name,
hadoop_version,
node_processes,
flavor_id,
node_configs,
**kwargs)
-
# store id of created node group template
- template_id = body['id']
- cls._node_group_templates.append(template_id)
+ cls._node_group_templates.append(body['id'])
- return resp, body, template_id
+ return resp, body
+
+ @classmethod
+ def create_cluster_template(cls, name, plugin_name, hadoop_version,
+ node_groups, cluster_configs=None, **kwargs):
+ """Creates watched cluster template 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_cluster_template(name, plugin_name,
+ hadoop_version,
+ node_groups,
+ cluster_configs,
+ **kwargs)
+ # store id of created cluster template
+ cls._cluster_templates.append(body['id'])
+
+ return resp, body
diff --git a/tempest/api/data_processing/test_node_group_templates.py b/tempest/api/data_processing/test_node_group_templates.py
index a64c345..ed4cf1f 100644
--- a/tempest/api/data_processing/test_node_group_templates.py
+++ b/tempest/api/data_processing/test_node_group_templates.py
@@ -19,65 +19,87 @@
class NodeGroupTemplateTest(dp_base.BaseDataProcessingTest):
- def _create_simple_node_group_template(self, template_name=None):
- """Creates simple Node Group Template with optional name specified.
+ @classmethod
+ def setUpClass(cls):
+ super(NodeGroupTemplateTest, cls).setUpClass()
+ cls.node_group_template = {
+ 'description': 'Test node group template',
+ 'plugin_name': 'vanilla',
+ 'hadoop_version': '1.2.1',
+ 'node_processes': [
+ 'datanode',
+ 'tasktracker'
+ ],
+ 'flavor_id': cls.flavor_ref,
+ 'node_configs': {
+ 'HDFS': {
+ 'Data Node Heap Size': 1024
+ },
+ 'MapReduce': {
+ 'Task Tracker Heap Size': 1024
+ }
+ }
+ }
+
+ def _create_node_group_template(self, template_name=None):
+ """Creates Node Group Template with optional name specified.
It creates template and ensures response status and template name.
Returns id and name of created template.
"""
-
if template_name is None:
# generate random name if it's not specified
- template_name = data_utils.rand_name('sahara')
+ template_name = data_utils.rand_name('sahara-ng-template')
- # create simple node group template
- resp, body, template_id = self.create_node_group_template(
- template_name, **self.simple_node_group_template)
+ # create node group template
+ resp, body = self.create_node_group_template(
+ template_name, **self.node_group_template)
# ensure that template created successfully
self.assertEqual(202, resp.status)
self.assertEqual(template_name, body['name'])
- return template_id, template_name
+ return body['id'], template_name
@attr(type='smoke')
def test_node_group_template_create(self):
- # just create and ensure template
- self._create_simple_node_group_template()
+ template_name = data_utils.rand_name('sahara-ng-template')
+ resp, body = self.create_node_group_template(
+ template_name, **self.node_group_template)
+
+ # check that template created successfully
+ self.assertEqual(resp.status, 202)
+ self.assertEqual(template_name, body['name'])
+ self.assertDictContainsSubset(self.node_group_template, body)
@attr(type='smoke')
def test_node_group_template_list(self):
- template_info = self._create_simple_node_group_template()
+ template_info = self._create_node_group_template()
# check for node group template in list
resp, templates = self.client.list_node_group_templates()
self.assertEqual(200, resp.status)
- templates_info = list([(template['id'], template['name'])
- for template in templates])
+ templates_info = [(template['id'], template['name'])
+ for template in templates]
self.assertIn(template_info, templates_info)
@attr(type='smoke')
def test_node_group_template_get(self):
- template_id, template_name = self._create_simple_node_group_template()
+ template_id, template_name = self._create_node_group_template()
# check node group template fetch by id
resp, template = self.client.get_node_group_template(template_id)
self.assertEqual(200, resp.status)
self.assertEqual(template_name, template['name'])
- self.assertEqual(self.simple_node_group_template['plugin_name'],
- template['plugin_name'])
- self.assertEqual(self.simple_node_group_template['node_processes'],
- template['node_processes'])
- self.assertEqual(self.simple_node_group_template['flavor_id'],
- template['flavor_id'])
+ self.assertDictContainsSubset(self.node_group_template, template)
@attr(type='smoke')
def test_node_group_template_delete(self):
- template_id, template_name = self._create_simple_node_group_template()
+ template_id = self._create_node_group_template()[0]
# delete the node group template by id
- resp = self.client.delete_node_group_template(template_id)
+ resp = self.client.delete_node_group_template(template_id)[0]
- self.assertEqual('204', resp[0]['status'])
+ self.assertEqual(204, resp.status)
diff --git a/tempest/api/network/admin/test_load_balancer_admin_actions.py b/tempest/api/network/admin/test_load_balancer_admin_actions.py
index 6bcc118..bc7f1d6 100644
--- a/tempest/api/network/admin/test_load_balancer_admin_actions.py
+++ b/tempest/api/network/admin/test_load_balancer_admin_actions.py
@@ -43,6 +43,8 @@
tenant_name)['id']
cls.network = cls.create_network()
cls.subnet = cls.create_subnet(cls.network)
+ cls.pool = cls.create_pool(data_utils.rand_name('pool-'),
+ "ROUND_ROBIN", "HTTP", cls.subnet)
@test.attr(type='smoke')
def test_create_vip_as_admin_for_another_tenant(self):
@@ -102,6 +104,17 @@
self.assertIsNotNone(pool['id'])
self.assertEqual(self.tenant_id, pool['tenant_id'])
+ @test.attr(type='smoke')
+ def test_create_member_from_admin_user_other_tenant(self):
+ resp, body = self.admin_client.create_member(
+ address="10.0.9.47", protocol_port=80, pool_id=self.pool['id'],
+ tenant_id=self.tenant_id)
+ self.assertEqual('201', resp['status'])
+ member = body['member']
+ self.addCleanup(self.admin_client.delete_member, member['id'])
+ self.assertIsNotNone(member['id'])
+ self.assertEqual(self.tenant_id, member['tenant_id'])
+
class LoadBalancerAdminTestXML(LoadBalancerAdminTestJSON):
_interface = 'xml'
diff --git a/tempest/api/network/test_fwaas_extensions.py b/tempest/api/network/test_fwaas_extensions.py
index 0647069..555cbda 100644
--- a/tempest/api/network/test_fwaas_extensions.py
+++ b/tempest/api/network/test_fwaas_extensions.py
@@ -14,9 +14,12 @@
from tempest.api.network import base
from tempest.common.utils import data_utils
+from tempest import config
from tempest import exceptions
from tempest import test
+CONF = config.CONF
+
class FWaaSExtensionTestJSON(base.BaseNetworkTest):
_interface = 'json'
@@ -67,6 +70,20 @@
except exceptions.NotFound:
pass
+ self.client.wait_for_resource_deletion('firewall', fw_id)
+
+ def _wait_for_active(self, fw_id):
+ def _wait():
+ resp, firewall = self.client.show_firewall(fw_id)
+ self.assertEqual('200', resp['status'])
+ firewall = firewall['firewall']
+ return firewall['status'] == 'ACTIVE'
+
+ if not test.call_until_true(_wait, CONF.network.build_timeout,
+ CONF.network.build_interval):
+ m = 'Timed out waiting for firewall %s to become ACTIVE.' % fw_id
+ raise exceptions.TimeoutException(m)
+
@test.attr(type='smoke')
def test_list_firewall_rules(self):
# List firewall rules
@@ -168,6 +185,15 @@
@test.attr(type='smoke')
def test_create_show_delete_firewall(self):
+ # Create tenant network resources required for an ACTIVE firewall
+ network = self.create_network()
+ subnet = self.create_subnet(network)
+ router = self.create_router(
+ data_utils.rand_name('router-'),
+ admin_state_up=True)
+ self.client.add_router_interface_with_subnet_id(
+ router['id'], subnet['id'])
+
# Create firewall
resp, body = self.client.create_firewall(
name=data_utils.rand_name("firewall"),
@@ -177,11 +203,16 @@
firewall_id = created_firewall['id']
self.addCleanup(self._try_delete_firewall, firewall_id)
+ self._wait_for_active(firewall_id)
+
# show a created firewall
resp, firewall = self.client.show_firewall(firewall_id)
self.assertEqual('200', resp['status'])
firewall = firewall['firewall']
+
for key, value in firewall.iteritems():
+ if key == 'status':
+ continue
self.assertEqual(created_firewall[key], value)
# list firewall
@@ -198,9 +229,6 @@
# Delete firewall
resp, _ = self.client.delete_firewall(firewall_id)
self.assertEqual('204', resp['status'])
- # Confirm deletion
- # TODO(raies): Confirm deletion can be done only when,
- # deleted firewall status is not "PENDING_DELETE".
class FWaaSExtensionTestXML(FWaaSExtensionTestJSON):
diff --git a/tempest/api/network/test_load_balancer.py b/tempest/api/network/test_load_balancer.py
index 3ab015e..673fc47 100644
--- a/tempest/api/network/test_load_balancer.py
+++ b/tempest/api/network/test_load_balancer.py
@@ -434,6 +434,40 @@
self.assertEqual(member_id, body['member']['id'])
self.assertIsNotNone(body['member']['admin_state_up'])
+ @test.attr(type='smoke')
+ def test_update_pool_related_to_member(self):
+ # Create new pool
+ resp, body = self.client.create_pool(
+ name=data_utils.rand_name("pool-"),
+ lb_method='ROUND_ROBIN',
+ protocol='HTTP',
+ subnet_id=self.subnet['id'])
+ self.assertEqual('201', resp['status'])
+ new_pool = body['pool']
+ self.addCleanup(self.client.delete_pool, new_pool['id'])
+ # Update member with new pool's id
+ resp, body = self.client.update_member(self.member['id'],
+ pool_id=new_pool['id'])
+ self.assertEqual('200', resp['status'])
+ # Confirm with show that pool_id change
+ resp, body = self.client.show_member(self.member['id'])
+ member = body['member']
+ self.assertEqual(member['pool_id'], new_pool['id'])
+ # Update member with old pool id, this is needed for clean up
+ resp, body = self.client.update_member(self.member['id'],
+ pool_id=self.pool['id'])
+ self.assertEqual('200', resp['status'])
+
+ @test.attr(type='smoke')
+ def test_update_member_weight(self):
+ resp, _ = self.client.update_member(self.member['id'],
+ weight=2)
+ self.assertEqual('200', resp['status'])
+ resp, body = self.client.show_member(self.member['id'])
+ self.assertEqual('200', resp['status'])
+ member = body['member']
+ self.assertEqual(2, member['weight'])
+
class LoadBalancerTestXML(LoadBalancerTestJSON):
_interface = 'xml'
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index 0175de7..de44f4d 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -37,15 +37,9 @@
create a subnet for a tenant
list tenant's subnets
show a tenant subnet details
- port create
- port delete
- port list
- port show
- port update
network update
subnet update
delete a network also deletes its subnets
- create a port with no IP address associated with it
All subnet tests are run once with ipv4 and once with ipv6.
@@ -115,13 +109,13 @@
@test.attr(type='smoke')
def test_show_network_fields(self):
# Verify specific fields of a network
- field_list = [('fields', 'id'), ('fields', 'name'), ]
+ fields = ['id', 'name']
resp, body = self.client.show_network(self.network['id'],
- field_list=field_list)
+ fields=fields)
self.assertEqual('200', resp['status'])
network = body['network']
- self.assertEqual(len(network), len(field_list))
- for label, field_name in field_list:
+ self.assertEqual(sorted(network.keys()), sorted(fields))
+ for field_name in fields:
self.assertEqual(network[field_name], self.network[field_name])
@test.attr(type='smoke')
@@ -136,13 +130,13 @@
@test.attr(type='smoke')
def test_list_networks_fields(self):
# Verify specific fields of the networks
- resp, body = self.client.list_networks(fields='id')
+ fields = ['id', 'name']
+ resp, body = self.client.list_networks(fields=fields)
self.assertEqual('200', resp['status'])
networks = body['networks']
self.assertNotEmpty(networks, "Network list returned is empty")
for network in networks:
- self.assertEqual(len(network), 1)
- self.assertIn('id', network)
+ self.assertEqual(sorted(network.keys()), sorted(fields))
@test.attr(type='smoke')
def test_show_subnet(self):
@@ -158,13 +152,13 @@
@test.attr(type='smoke')
def test_show_subnet_fields(self):
# Verify specific fields of a subnet
- field_list = [('fields', 'id'), ('fields', 'cidr'), ]
+ fields = ['id', 'network_id']
resp, body = self.client.show_subnet(self.subnet['id'],
- field_list=field_list)
+ fields=fields)
self.assertEqual('200', resp['status'])
subnet = body['subnet']
- self.assertEqual(len(subnet), len(field_list))
- for label, field_name in field_list:
+ self.assertEqual(sorted(subnet.keys()), sorted(fields))
+ for field_name in fields:
self.assertEqual(subnet[field_name], self.subnet[field_name])
@test.attr(type='smoke')
@@ -179,13 +173,13 @@
@test.attr(type='smoke')
def test_list_subnets_fields(self):
# Verify specific fields of subnets
- resp, body = self.client.list_subnets(fields='id')
+ fields = ['id', 'network_id']
+ resp, body = self.client.list_subnets(fields=fields)
self.assertEqual('200', resp['status'])
subnets = body['subnets']
self.assertNotEmpty(subnets, "Subnet list returned is empty")
for subnet in subnets:
- self.assertEqual(len(subnet), 1)
- self.assertIn('id', subnet)
+ self.assertEqual(sorted(subnet.keys()), sorted(fields))
def _try_delete_network(self, net_id):
# delete network, if it exists
@@ -222,32 +216,6 @@
# it from the list.
self.subnets.pop()
- @test.attr(type='smoke')
- def test_create_port_with_no_ip(self):
- # For this test create a new network - do not use any previously
- # created networks.
- name = data_utils.rand_name('network-nosubnet-')
- resp, body = self.client.create_network(name=name)
- self.assertEqual('201', resp['status'])
- network = body['network']
- net_id = network['id']
- self.networks.append(network)
-
- # Now create a port for this network - without creating any
- # subnets for this network - this ensures no IP for the port
- resp, body = self.client.create_port(network_id=net_id)
- self.assertEqual('201', resp['status'])
- port = body['port']
- port_id = port['id']
- self.addCleanup(self.client.delete_port, port_id)
-
- # Verify that the port does not have any IP address
- resp, body = self.client.show_port(port_id)
- self.assertEqual('200', resp['status'])
- port_resp = body['port']
- self.assertEqual(port_id, port_resp['id'])
- self.assertEqual(port_resp['fixed_ips'], [])
-
class NetworksTestXML(NetworksTestJSON):
_interface = 'xml'
diff --git a/tempest/api/network/test_ports.py b/tempest/api/network/test_ports.py
index bda5b2b..68f617b 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -26,6 +26,16 @@
class PortsTestJSON(base.BaseNetworkTest):
_interface = 'json'
+ """
+ Test the following operations for ports:
+
+ port create
+ port delete
+ port list
+ port show
+ port update
+ """
+
@classmethod
@test.safe_setup
def setUpClass(cls):
@@ -80,17 +90,18 @@
self.assertEqual(self.port['network_id'], port['network_id'])
self.assertEqual(self.port['security_groups'],
port['security_groups'])
+ self.assertEqual(port['fixed_ips'], [])
@test.attr(type='smoke')
def test_show_port_fields(self):
# Verify specific fields of a port
- field_list = [('fields', 'id'), ]
+ fields = ['id', 'mac_address']
resp, body = self.client.show_port(self.port['id'],
- field_list=field_list)
+ fields=fields)
self.assertEqual('200', resp['status'])
port = body['port']
- self.assertEqual(len(port), len(field_list))
- for label, field_name in field_list:
+ self.assertEqual(sorted(port.keys()), sorted(fields))
+ for field_name in fields:
self.assertEqual(port[field_name], self.port[field_name])
@test.attr(type='smoke')
@@ -126,14 +137,14 @@
@test.attr(type='smoke')
def test_list_ports_fields(self):
# Verify specific fields of ports
- resp, body = self.client.list_ports(fields='id')
+ fields = ['id', 'mac_address']
+ resp, body = self.client.list_ports(fields=fields)
self.assertEqual('200', resp['status'])
ports = body['ports']
self.assertNotEmpty(ports, "Port list returned is empty")
# Asserting the fields returned are correct
for port in ports:
- self.assertEqual(len(port), 1)
- self.assertIn('id', port)
+ self.assertEqual(sorted(fields), sorted(port.keys()))
class PortsTestXML(PortsTestJSON):
diff --git a/tempest/api/object_storage/test_account_quotas_negative.py b/tempest/api/object_storage/test_account_quotas_negative.py
index 29afebc..5a79529 100644
--- a/tempest/api/object_storage/test_account_quotas_negative.py
+++ b/tempest/api/object_storage/test_account_quotas_negative.py
@@ -122,6 +122,7 @@
{"Quota-Bytes": "100"})
@test.attr(type=["negative", "smoke"])
+ @test.skip_because(bug="1310597")
@test.requires_ext(extension='account_quotas', service='object')
def test_upload_large_object(self):
object_name = data_utils.rand_name(name="TestObject")
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index b057698..06e63a4 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -14,8 +14,10 @@
# under the License.
import hashlib
+import random
import re
from six import moves
+import time
from tempest.api.object_storage import base
from tempest.common import custom_matchers
@@ -308,10 +310,7 @@
# retrieve object's data (in response body)
# create object
- object_name = data_utils.rand_name(name='TestObject')
- data = data_utils.arbitrary_string()
- resp, _ = self.object_client.create_object(self.container_name,
- object_name, data)
+ object_name, data = self._create_object()
# get object
resp, body = self.object_client.get_object(self.container_name,
object_name)
@@ -321,6 +320,183 @@
self.assertEqual(body, data)
@test.attr(type='smoke')
+ def test_get_object_with_metadata(self):
+ # get object with metadata
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
+ metadata = {'X-Object-Meta-test-meta': 'Meta'}
+ self.object_client.create_object(self.container_name,
+ object_name,
+ data,
+ metadata=metadata)
+ resp, body = self.object_client.get_object(
+ self.container_name,
+ object_name,
+ metadata=None)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'GET')
+ self.assertIn('x-object-meta-test-meta', resp)
+ self.assertEqual(resp['x-object-meta-test-meta'], 'Meta')
+ self.assertEqual(body, data)
+
+ @test.attr(type='smoke')
+ def test_get_object_with_range(self):
+ # get object with range
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string(100)
+ self.object_client.create_object(self.container_name,
+ object_name,
+ data,
+ metadata=None)
+ rand_num = random.randint(3, len(data) - 1)
+ metadata = {'Range': 'bytes=%s-%s' % (rand_num - 3, rand_num - 1)}
+ resp, body = self.object_client.get_object(
+ self.container_name,
+ object_name,
+ metadata=metadata)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'GET')
+ self.assertEqual(body, data[rand_num - 3: rand_num])
+
+ @test.attr(type='smoke')
+ def test_get_object_with_x_object_manifest(self):
+ # get object with x_object_manifest
+
+ # uploading segments
+ object_name, data_segments = self._upload_segments()
+ # creating a manifest file
+ object_prefix = '%s/%s' % (self.container_name, object_name)
+ metadata = {'X-Object-Manifest': object_prefix}
+ data_empty = ''
+ resp, body = self.object_client.create_object(
+ self.container_name,
+ object_name,
+ data_empty,
+ metadata=metadata)
+
+ resp, body = self.object_client.get_object(
+ self.container_name,
+ object_name,
+ metadata=None)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+
+ # Check only the existence of common headers with custom matcher
+ self.assertThat(resp, custom_matchers.ExistsAllResponseHeaders(
+ 'Object', 'GET'))
+ self.assertIn('x-object-manifest', resp)
+
+ # Etag value of a large object is enclosed in double-quotations.
+ # This is a special case, therefore the formats of response headers
+ # are checked without a custom matcher.
+ self.assertTrue(resp['etag'].startswith('\"'))
+ self.assertTrue(resp['etag'].endswith('\"'))
+ self.assertTrue(resp['etag'].strip('\"').isalnum())
+ self.assertTrue(re.match("^\d+\.?\d*\Z", resp['x-timestamp']))
+ self.assertNotEqual(len(resp['content-type']), 0)
+ self.assertTrue(re.match("^tx[0-9a-f]*-[0-9a-f]*$",
+ resp['x-trans-id']))
+ self.assertNotEqual(len(resp['date']), 0)
+ self.assertEqual(resp['accept-ranges'], 'bytes')
+ self.assertEqual(resp['x-object-manifest'],
+ '%s/%s' % (self.container_name, object_name))
+
+ self.assertEqual(''.join(data_segments), body)
+
+ @test.attr(type='smoke')
+ def test_get_object_with_if_match(self):
+ # get object with if_match
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string(10)
+ create_md5 = hashlib.md5(data).hexdigest()
+ create_metadata = {'Etag': create_md5}
+ self.object_client.create_object(self.container_name,
+ object_name,
+ data,
+ metadata=create_metadata)
+
+ list_metadata = {'If-Match': create_md5}
+ resp, body = self.object_client.get_object(
+ self.container_name,
+ object_name,
+ metadata=list_metadata)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'GET')
+ self.assertEqual(body, data)
+
+ @test.attr(type='smoke')
+ def test_get_object_with_if_modified_since(self):
+ # get object with if_modified_since
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string()
+ time_now = time.time()
+ self.object_client.create_object(self.container_name,
+ object_name,
+ data,
+ metadata=None)
+
+ http_date = time.ctime(time_now - 86400)
+ list_metadata = {'If-Modified-Since': http_date}
+ resp, body = self.object_client.get_object(
+ self.container_name,
+ object_name,
+ metadata=list_metadata)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'GET')
+ self.assertEqual(body, data)
+
+ def test_get_object_with_if_none_match(self):
+ # get object with if_none_match
+ object_name = data_utils.rand_name(name='TestObject')
+ data = data_utils.arbitrary_string(10)
+ create_md5 = hashlib.md5(data).hexdigest()
+ create_metadata = {'Etag': create_md5}
+ self.object_client.create_object(self.container_name,
+ object_name,
+ data,
+ metadata=create_metadata)
+
+ list_data = data_utils.arbitrary_string(15)
+ list_md5 = hashlib.md5(list_data).hexdigest()
+ list_metadata = {'If-None-Match': list_md5}
+ resp, body = self.object_client.get_object(
+ self.container_name,
+ object_name,
+ metadata=list_metadata)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'GET')
+ self.assertEqual(body, data)
+
+ @test.attr(type='smoke')
+ def test_get_object_with_if_unmodified_since(self):
+ # get object with if_unmodified_since
+ object_name, data = self._create_object()
+
+ time_now = time.time()
+ http_date = time.ctime(time_now + 86400)
+ list_metadata = {'If-Unmodified-Since': http_date}
+ resp, body = self.object_client.get_object(
+ self.container_name,
+ object_name,
+ metadata=list_metadata)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'GET')
+ self.assertEqual(body, data)
+
+ @test.attr(type='smoke')
+ def test_get_object_with_x_newest(self):
+ # get object with x_newest
+ object_name, data = self._create_object()
+
+ list_metadata = {'X-Newest': 'true'}
+ resp, body = self.object_client.get_object(
+ self.container_name,
+ object_name,
+ metadata=list_metadata)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'GET')
+ self.assertEqual(body, data)
+
+ @test.attr(type='smoke')
def test_copy_object_in_same_container(self):
# create source object
src_object_name = data_utils.rand_name(name='SrcObject')
@@ -498,10 +674,7 @@
# Make a conditional request for an object using the If-None-Match
# header, it should get downloaded only if the local file is different,
# otherwise the response code should be 304 Not Modified
- 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()
# local copy is identical, no download
md5 = hashlib.md5(data).hexdigest()
headers = {'If-None-Match': md5}
diff --git a/tempest/api/orchestration/base.py b/tempest/api/orchestration/base.py
index 7656ff3..c27bedf 100644
--- a/tempest/api/orchestration/base.py
+++ b/tempest/api/orchestration/base.py
@@ -101,9 +101,8 @@
@classmethod
def load_template(cls, name, ext='yaml'):
- loc = ["tempest", "api", "orchestration",
- "stacks", "templates", "%s.%s" % (name, ext)]
- fullpath = os.path.join(*loc)
+ loc = ["stacks", "templates", "%s.%s" % (name, ext)]
+ fullpath = os.path.join(os.path.dirname(__file__), *loc)
with open(fullpath, "r") as f:
content = f.read()
diff --git a/tempest/api_schema/compute/agents.py b/tempest/api_schema/compute/agents.py
new file mode 100644
index 0000000..b04cf64
--- /dev/null
+++ b/tempest/api_schema/compute/agents.py
@@ -0,0 +1,40 @@
+# 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.
+
+list_agents = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'agents': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'agent_id': {'type': ['integer', 'string']},
+ 'hypervisor': {'type': 'string'},
+ 'os': {'type': 'string'},
+ 'architecture': {'type': 'string'},
+ 'version': {'type': 'string'},
+ 'url': {'type': 'string', 'format': 'uri'},
+ 'md5hash': {'type': 'string'}
+ },
+ 'required': ['agent_id', 'hypervisor', 'os',
+ 'architecture', 'version', 'url', 'md5hash']
+ }
+ }
+ },
+ 'required': ['agents']
+ }
+}
diff --git a/tempest/api_schema/compute/aggregates.py b/tempest/api_schema/compute/aggregates.py
index a70b356..a3ab3c8 100644
--- a/tempest/api_schema/compute/aggregates.py
+++ b/tempest/api_schema/compute/aggregates.py
@@ -12,9 +12,11 @@
# License for the specific language governing permissions and limitations
# under the License.
+import copy
+
aggregate = {
'type': 'object',
- 'properties:': {
+ 'properties': {
'availability_zone': {'type': ['string', 'null']},
'created_at': {'type': 'string'},
'deleted': {'type': 'boolean'},
@@ -56,3 +58,9 @@
}
aggregate_set_metadata = get_aggregate
+# The 'updated_at' attribute of 'update_aggregate' can't be null.
+update_aggregate = copy.deepcopy(get_aggregate)
+update_aggregate['response_body']['properties']['aggregate']['properties'][
+ 'updated_at'] = {
+ 'type': 'string'
+ }
diff --git a/tempest/api_schema/compute/availability_zone.py b/tempest/api_schema/compute/availability_zone.py
new file mode 100644
index 0000000..c1abc64
--- /dev/null
+++ b/tempest/api_schema/compute/availability_zone.py
@@ -0,0 +1,37 @@
+# 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.
+
+# NOTE: This is the detail information for "get az detail" API.
+# The information is the same between v2 and v3 APIs.
+detail = {
+ 'type': 'object',
+ 'patternProperties': {
+ # NOTE: Here is for a hostname
+ '^[a-zA-Z0-9-_.]+$': {
+ 'type': 'object',
+ 'patternProperties': {
+ # NOTE: Here is for a service name
+ '^.*$': {
+ 'type': 'object',
+ 'properties': {
+ 'available': {'type': 'boolean'},
+ 'active': {'type': 'boolean'},
+ 'updated_at': {'type': 'string'}
+ },
+ 'required': ['available', 'active', 'updated_at']
+ }
+ }
+ }
+ }
+}
diff --git a/tempest/api_schema/compute/flavors_extra_specs.py b/tempest/api_schema/compute/flavors_extra_specs.py
new file mode 100644
index 0000000..4003d36
--- /dev/null
+++ b/tempest/api_schema/compute/flavors_extra_specs.py
@@ -0,0 +1,39 @@
+# 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.
+
+flavor_extra_specs = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'extra_specs': {
+ 'type': 'object',
+ 'patternProperties': {
+ '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+ }
+ }
+ },
+ 'required': ['extra_specs']
+ }
+}
+
+flavor_extra_specs_key = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'patternProperties': {
+ '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+ }
+ }
+}
diff --git a/tempest/api_schema/compute/interfaces.py b/tempest/api_schema/compute/interfaces.py
new file mode 100644
index 0000000..1e15c18
--- /dev/null
+++ b/tempest/api_schema/compute/interfaces.py
@@ -0,0 +1,17 @@
+# 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.
+
+delete_interface = {
+ 'status_code': [202]
+}
diff --git a/tempest/api_schema/compute/servers.py b/tempest/api_schema/compute/servers.py
index 5eb5ecb..31cd56b 100644
--- a/tempest/api_schema/compute/servers.py
+++ b/tempest/api_schema/compute/servers.py
@@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import copy
+
get_password = {
'status_code': [200],
'response_body': {
@@ -43,3 +45,20 @@
'required': ['console']
}
}
+
+delete_server = {
+ 'status_code': [204],
+}
+
+set_server_metadata = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'metadata': {'type': 'object'}
+ },
+ 'required': ['metadata']
+ }
+}
+
+list_server_metadata = copy.deepcopy(set_server_metadata)
diff --git a/tempest/api_schema/compute/v2/availability_zone.py b/tempest/api_schema/compute/v2/availability_zone.py
new file mode 100644
index 0000000..d3d2787
--- /dev/null
+++ b/tempest/api_schema/compute/v2/availability_zone.py
@@ -0,0 +1,54 @@
+# 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 copy
+
+from tempest.api_schema.compute import availability_zone as common
+
+
+base = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'availabilityZoneInfo': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'zoneName': {'type': 'string'},
+ 'zoneState': {
+ 'type': 'object',
+ 'properties': {
+ 'available': {'type': 'boolean'}
+ },
+ 'required': ['available']
+ },
+ # NOTE: Here is the difference between detail and
+ # non-detail.
+ 'hosts': {'type': 'null'}
+ },
+ 'required': ['zoneName', 'zoneState', 'hosts']
+ }
+ }
+ },
+ 'required': ['availabilityZoneInfo']
+ }
+}
+
+get_availability_zone_list = copy.deepcopy(base)
+
+get_availability_zone_list_detail = copy.deepcopy(base)
+get_availability_zone_list_detail['response_body']['properties'][
+ 'availabilityZoneInfo']['items']['properties']['hosts'] = common.detail
diff --git a/tempest/api_schema/compute/v2/flavors.py b/tempest/api_schema/compute/v2/flavors.py
index 999ca19..48e6ceb 100644
--- a/tempest/api_schema/compute/v2/flavors.py
+++ b/tempest/api_schema/compute/v2/flavors.py
@@ -31,3 +31,7 @@
'OS-FLV-EXT-DATA:ephemeral': {'type': 'integer'}})
# 'OS-FLV-DISABLED', 'os-flavor-access', 'rxtx_factor' and 'OS-FLV-EXT-DATA'
# are API extensions. So they are not 'required'.
+
+unset_flavor_extra_specs = {
+ 'status_code': [200]
+}
diff --git a/tempest/api_schema/compute/v2/images.py b/tempest/api_schema/compute/v2/images.py
index fad6b56..e97752d 100644
--- a/tempest/api_schema/compute/v2/images.py
+++ b/tempest/api_schema/compute/v2/images.py
@@ -14,43 +14,46 @@
from tempest.api_schema.compute import parameter_types
+common_image_schema = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'status': {'type': 'string'},
+ 'updated': {'type': 'string'},
+ 'links': parameter_types.links,
+ 'name': {'type': 'string'},
+ 'created': {'type': 'string'},
+ 'minDisk': {'type': 'integer'},
+ 'minRam': {'type': 'integer'},
+ 'progress': {'type': 'integer'},
+ 'metadata': {'type': 'object'},
+ 'server': {
+ 'type': 'object',
+ 'properties': {
+ # NOTE: Now the type of 'id' is integer, but here
+ # allows 'string' also because we will be able to
+ # change it to 'uuid' in the future.
+ 'id': {'type': ['integer', 'string']},
+ 'links': parameter_types.links
+ },
+ 'required': ['id', 'links']
+ },
+ 'OS-EXT-IMG-SIZE:size': {'type': 'integer'}
+ },
+ # 'server' attributes only comes in response body if image is
+ # associated with any server. 'OS-EXT-IMG-SIZE:size' is API
+ # extension, So those are not defined as 'required'.
+ 'required': ['id', 'status', 'updated', 'links', 'name',
+ 'created', 'minDisk', 'minRam', 'progress',
+ 'metadata']
+}
+
get_image = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
- 'image': {
- 'type': 'object',
- 'properties': {
- 'id': {'type': 'string'},
- 'status': {'type': 'string'},
- 'updated': {'type': 'string'},
- 'links': parameter_types.links,
- 'name': {'type': 'string'},
- 'created': {'type': 'string'},
- 'OS-EXT-IMG-SIZE:size': {'type': 'integer'},
- 'minDisk': {'type': 'integer'},
- 'minRam': {'type': 'integer'},
- 'progress': {'type': 'integer'},
- 'metadata': {'type': 'object'},
- 'server': {
- 'type': 'object',
- 'properties': {
- # NOTE: Now the type of 'id' is integer, but here
- # allows 'string' also because we will be able to
- # change it to 'uuid' in the future.
- 'id': {'type': ['integer', 'string']},
- 'links': parameter_types.links
- },
- 'required': ['id', 'links']
- }
- },
- # 'server' attributes only comes in response body if image is
- # associated with any server. So it is not 'required'
- 'required': ['id', 'status', 'updated', 'links', 'name',
- 'created', 'OS-EXT-IMG-SIZE:size', 'minDisk',
- 'minRam', 'progress', 'metadata']
- }
+ 'image': common_image_schema
},
'required': ['image']
}
@@ -67,20 +70,7 @@
'type': 'object',
'properties': {
'id': {'type': 'string'},
- 'links': {
- 'type': 'array',
- 'items': {
- 'type': 'object',
- 'properties': {
- 'href': {
- 'type': 'string',
- 'format': 'uri'
- },
- 'rel': {'type': 'string'}
- },
- 'required': ['href', 'rel']
- }
- },
+ 'links': parameter_types.links,
'name': {'type': 'string'}
},
'required': ['id', 'links', 'name']
@@ -120,3 +110,17 @@
'required': ['meta']
}
}
+
+list_images_details = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'images': {
+ 'type': 'array',
+ 'items': common_image_schema
+ }
+ },
+ 'required': ['images']
+ }
+}
diff --git a/tempest/api_schema/compute/v2/servers.py b/tempest/api_schema/compute/v2/servers.py
index 81d2ef7..eed4589 100644
--- a/tempest/api_schema/compute/v2/servers.py
+++ b/tempest/api_schema/compute/v2/servers.py
@@ -66,3 +66,27 @@
'required': ['virtual_interfaces']
}
}
+
+attach_volume = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'volumeAttachment': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'device': {'type': 'string'},
+ 'volumeId': {'type': 'string'},
+ 'serverId': {'type': ['integer', 'string']}
+ },
+ 'required': ['id', 'device', 'volumeId', 'serverId']
+ }
+ },
+ 'required': ['volumeAttachment']
+ }
+}
+
+detach_volume = {
+ 'status_code': [202]
+}
diff --git a/tempest/api_schema/compute/v3/availability_zone.py b/tempest/api_schema/compute/v3/availability_zone.py
new file mode 100644
index 0000000..5f36c33
--- /dev/null
+++ b/tempest/api_schema/compute/v3/availability_zone.py
@@ -0,0 +1,53 @@
+# 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 copy
+
+from tempest.api_schema.compute import availability_zone as common
+
+
+base = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'availability_zone_info': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'zone_name': {'type': 'string'},
+ 'zone_state': {
+ 'type': 'object',
+ 'properties': {
+ 'available': {'type': 'boolean'}
+ },
+ 'required': ['available']
+ },
+ # NOTE: Here is the difference between detail and
+ # non-detail
+ 'hosts': {'type': 'null'}
+ },
+ 'required': ['zone_name', 'zone_state', 'hosts']
+ }
+ }
+ },
+ 'required': ['availability_zone_info']
+ }
+}
+
+get_availability_zone_list = copy.deepcopy(base)
+get_availability_zone_list_detail = copy.deepcopy(base)
+get_availability_zone_list_detail['response_body']['properties'][
+ 'availability_zone_info']['items']['properties']['hosts'] = common.detail
diff --git a/tempest/api_schema/compute/v3/flavors.py b/tempest/api_schema/compute/v3/flavors.py
index 542d2b1..468658c 100644
--- a/tempest/api_schema/compute/v3/flavors.py
+++ b/tempest/api_schema/compute/v3/flavors.py
@@ -15,6 +15,7 @@
import copy
from tempest.api_schema.compute import flavors
+from tempest.api_schema.compute import flavors_extra_specs
list_flavors_details = copy.deepcopy(flavors.common_flavor_list_details)
@@ -31,3 +32,10 @@
# So they are not 'required'.
list_flavors_details['response_body']['properties']['flavors']['items'][
'required'].extend(['disabled', 'ephemeral'])
+
+set_flavor_extra_specs = copy.deepcopy(flavors_extra_specs.flavor_extra_specs)
+set_flavor_extra_specs['status_code'] = [201]
+
+unset_flavor_extra_specs = {
+ 'status_code': [204]
+}
diff --git a/tempest/api_schema/compute/v3/servers.py b/tempest/api_schema/compute/v3/servers.py
index 390962e..f2a4b78 100644
--- a/tempest/api_schema/compute/v3/servers.py
+++ b/tempest/api_schema/compute/v3/servers.py
@@ -42,3 +42,7 @@
'required': ['server']
}
}
+
+attach_detach_volume = {
+ 'status_code': [202]
+}
diff --git a/tempest/common/generator/base_generator.py b/tempest/common/generator/base_generator.py
index 87d65d0..57b98f7 100644
--- a/tempest/common/generator/base_generator.py
+++ b/tempest/common/generator/base_generator.py
@@ -123,7 +123,7 @@
raise Exception("non-integer list types not supported")
result = []
if schema_type not in self.types_dict:
- raise Exception("generator (%s) doesn't support type: %s"
+ raise TypeError("generator (%s) doesn't support type: %s"
% (self.__class__.__name__, schema_type))
for generator in self.types_dict[schema_type]:
ret = generator(schema)
diff --git a/tempest/config.py b/tempest/config.py
index b9fe572..7084768 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -159,6 +159,19 @@
cfg.BoolOpt('run_ssh',
default=False,
help="Should the tests ssh to instances?"),
+ cfg.StrOpt('ssh_auth_method',
+ default='keypair',
+ help="Auth method used for authenticate to the instance. "
+ "Valid choices are: keypair, configured, adminpass. "
+ "keypair: start the servers with an ssh keypair. "
+ "configured: use the configured user and password. "
+ "adminpass: use the injected adminPass. "
+ "disabled: avoid using ssh when it is an option."),
+ cfg.StrOpt('ssh_connect_method',
+ default='fixed',
+ help="How to connect to the instance? "
+ "fixed: using the first ip belongs the fixed network "
+ "floating: creating and using a floating ip"),
cfg.StrOpt('ssh_user',
default='root',
help="User name used to authenticate to an instance."),
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index 270851d..f297f22 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import os
import re
@@ -22,7 +23,7 @@
PYTHON_CLIENT_RE = re.compile('import (%s)client' % '|'.join(PYTHON_CLIENTS))
TEST_DEFINITION = re.compile(r'^\s*def test.*')
SETUPCLASS_DEFINITION = re.compile(r'^\s*def setUpClass')
-SCENARIO_DECORATOR = re.compile(r'\s*@.*services\(')
+SCENARIO_DECORATOR = re.compile(r'\s*@.*services\((.*)\)')
VI_HEADER_RE = re.compile(r"^#\s+vim?:.+")
@@ -75,8 +76,32 @@
return 0, "T106: Don't put vi configuration in source files"
+def service_tags_not_in_module_path(physical_line, filename):
+ """Check that a service tag isn't in the module path
+
+ A service tag should only be added if the service name isn't already in
+ the module path.
+
+ T107
+ """
+ # NOTE(mtreinish) Scenario tests always need service tags, but subdirs are
+ # created for services like heat which would cause false negatives for
+ # those tests, so just exclude the scenario tests.
+ if 'tempest/scenario' not in filename:
+ matches = SCENARIO_DECORATOR.match(physical_line)
+ if matches:
+ services = matches.group(1).split(',')
+ for service in services:
+ service_name = service.strip().strip("'")
+ modulepath = os.path.split(filename)[0]
+ if service_name in modulepath:
+ return (physical_line.find(service_name),
+ "T107: service tag should not be in path")
+
+
def factory(register):
register(import_no_clients_in_api)
register(scenario_tests_need_service_tags)
register(no_setupclass_for_unit_tests)
register(no_vi_headers)
+ register(service_tags_not_in_module_path)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 5895c37..1e7ddb1 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -19,7 +19,6 @@
import six
import subprocess
-from ironicclient import exc as ironic_exceptions
import netaddr
from neutronclient.common import exceptions as exc
from novaclient import exceptions as nova_exceptions
@@ -490,6 +489,8 @@
def wait_node(self, instance_id):
"""Waits for a node to be associated with instance_id."""
+ from ironicclient import exc as ironic_exceptions
+
def _get_node():
node = None
try:
diff --git a/tempest/scenario/test_large_ops.py b/tempest/scenario/test_large_ops.py
index b7a30f8..0210c56 100644
--- a/tempest/scenario/test_large_ops.py
+++ b/tempest/scenario/test_large_ops.py
@@ -31,7 +31,7 @@
Test large operations.
This test below:
- * Spin up multiple instances in one nova call
+ * Spin up multiple instances in one nova call, and repeat three times
* as a regular user
* TODO: same thing for cinder
@@ -69,3 +69,5 @@
return
self.glance_image_create()
self.nova_boot()
+ self.nova_boot()
+ self.nova_boot()
diff --git a/tempest/scenario/test_load_balancer_basic.py b/tempest/scenario/test_load_balancer_basic.py
index 5f71461..db1badd 100644
--- a/tempest/scenario/test_load_balancer_basic.py
+++ b/tempest/scenario/test_load_balancer_basic.py
@@ -118,7 +118,7 @@
self.floating_ips[floating_ip] = server
self.server_ips[server.id] = floating_ip.floating_ip_address
else:
- self.server_ips[server.id] = server.networks[net.name][0]
+ self.server_ips[server.id] = server.networks[net['name']][0]
self.assertTrue(self.servers_keypairs)
return server
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index b9ee040..b1b06cc 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -422,11 +422,15 @@
access_point_ssh = self._connect_to_access_point(tenant)
mac_addr = access_point_ssh.get_mac_address()
mac_addr = mac_addr.strip().lower()
- port_list = self.network_client.list_ports()['ports']
+ # Get the fixed_ips and mac_address fields of all ports. Select
+ # only those two columns to reduce the size of the response.
+ port_list = self.network_client.list_ports(
+ fields=['fixed_ips', 'mac_address'])['ports']
port_detail_list = [
(port['fixed_ips'][0]['subnet_id'],
port['fixed_ips'][0]['ip_address'],
- port['mac_address'].lower()) for port in port_list
+ port['mac_address'].lower())
+ for port in port_list if port['fixed_ips']
]
server_ip = self._get_server_ip(tenant.access_point)
subnet_id = tenant.subnet.id
diff --git a/tempest/services/baremetal/v1/base_v1.py b/tempest/services/baremetal/v1/base_v1.py
index 2145c88..296a199 100644
--- a/tempest/services/baremetal/v1/base_v1.py
+++ b/tempest/services/baremetal/v1/base_v1.py
@@ -42,6 +42,11 @@
return self._list_request('ports', **kwargs)
@base.handle_errors
+ def list_nodestates(self, uuid):
+ """List all existing states."""
+ return self._list_request('/nodes/%s/states' % uuid)
+
+ @base.handle_errors
def list_ports_detail(self):
"""Details list all existing ports."""
return self._list_request('/ports/detail')
diff --git a/tempest/services/compute/json/agents_client.py b/tempest/services/compute/json/agents_client.py
index 19821e7..98d8896 100644
--- a/tempest/services/compute/json/agents_client.py
+++ b/tempest/services/compute/json/agents_client.py
@@ -15,6 +15,7 @@
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.common import rest_client
from tempest import config
@@ -37,7 +38,9 @@
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
- return resp, json.loads(body).get('agents')
+ body = json.loads(body)
+ self.validate_response(common_schema.list_agents, resp, body)
+ return resp, body['agents']
def create_agent(self, **kwargs):
"""Create an agent build."""
diff --git a/tempest/services/compute/json/aggregates_client.py b/tempest/services/compute/json/aggregates_client.py
index fe67102..54d1252 100644
--- a/tempest/services/compute/json/aggregates_client.py
+++ b/tempest/services/compute/json/aggregates_client.py
@@ -61,6 +61,7 @@
resp, body = self.put('os-aggregates/%s' % str(aggregate_id), put_body)
body = json.loads(body)
+ self.validate_response(schema.update_aggregate, resp, body)
return resp, body['aggregate']
def delete_aggregate(self, aggregate_id):
diff --git a/tempest/services/compute/json/availability_zone_client.py b/tempest/services/compute/json/availability_zone_client.py
index 9278d5b..1c067e8 100644
--- a/tempest/services/compute/json/availability_zone_client.py
+++ b/tempest/services/compute/json/availability_zone_client.py
@@ -15,6 +15,7 @@
import json
+from tempest.api_schema.compute.v2 import availability_zone as schema
from tempest.common import rest_client
from tempest import config
@@ -31,9 +32,12 @@
def get_availability_zone_list(self):
resp, body = self.get('os-availability-zone')
body = json.loads(body)
+ self.validate_response(schema.get_availability_zone_list, resp, body)
return resp, body['availabilityZoneInfo']
def get_availability_zone_list_detail(self):
resp, body = self.get('os-availability-zone/detail')
body = json.loads(body)
+ self.validate_response(schema.get_availability_zone_list_detail, resp,
+ body)
return resp, body['availabilityZoneInfo']
diff --git a/tempest/services/compute/json/flavors_client.py b/tempest/services/compute/json/flavors_client.py
index 0206b82..65d2657 100644
--- a/tempest/services/compute/json/flavors_client.py
+++ b/tempest/services/compute/json/flavors_client.py
@@ -18,6 +18,8 @@
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 \
+ as schema_extra_specs
from tempest.api_schema.compute.v2 import flavors as v2schema
from tempest.common import rest_client
from tempest import config
@@ -99,12 +101,16 @@
resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id,
post_body)
body = json.loads(body)
+ self.validate_response(schema_extra_specs.flavor_extra_specs,
+ resp, body)
return resp, body['extra_specs']
def get_flavor_extra_spec(self, flavor_id):
"""Gets extra Specs details of the mentioned flavor."""
resp, body = self.get('flavors/%s/os-extra_specs' % flavor_id)
body = json.loads(body)
+ self.validate_response(schema_extra_specs.flavor_extra_specs,
+ resp, body)
return resp, body['extra_specs']
def get_flavor_extra_spec_with_key(self, flavor_id, key):
@@ -112,6 +118,8 @@
resp, body = self.get('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
key))
body = json.loads(body)
+ self.validate_response(schema_extra_specs.flavor_extra_specs_key,
+ resp, body)
return resp, body
def update_flavor_extra_spec(self, flavor_id, key, **kwargs):
@@ -119,12 +127,16 @@
resp, body = self.put('flavors/%s/os-extra_specs/%s' %
(flavor_id, key), json.dumps(kwargs))
body = json.loads(body)
+ self.validate_response(schema_extra_specs.flavor_extra_specs_key,
+ resp, body)
return resp, body
def unset_flavor_extra_spec(self, flavor_id, key):
"""Unsets extra Specs from the mentioned flavor."""
- return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
- key))
+ resp, body = self.delete('flavors/%s/os-extra_specs/%s' %
+ (str(flavor_id), key))
+ self.validate_response(v2schema.unset_flavor_extra_specs, resp, body)
+ return resp, body
def list_flavor_access(self, flavor_id):
"""Gets flavor access information given the flavor id."""
diff --git a/tempest/services/compute/json/images_client.py b/tempest/services/compute/json/images_client.py
index bd39a04..af7752a 100644
--- a/tempest/services/compute/json/images_client.py
+++ b/tempest/services/compute/json/images_client.py
@@ -70,6 +70,7 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.list_images_details, resp, body)
return resp, body['images']
def get_image(self, image_id):
diff --git a/tempest/services/compute/json/interfaces_client.py b/tempest/services/compute/json/interfaces_client.py
index 9928b94..2f165a2 100644
--- a/tempest/services/compute/json/interfaces_client.py
+++ b/tempest/services/compute/json/interfaces_client.py
@@ -16,6 +16,7 @@
import json
import time
+from tempest.api_schema.compute import interfaces as common_schema
from tempest.common import rest_client
from tempest import config
from tempest import exceptions
@@ -58,6 +59,7 @@
def delete_interface(self, server, port_id):
resp, body = self.delete('servers/%s/os-interface/%s' % (server,
port_id))
+ self.validate_response(common_schema.delete_interface, resp, body)
return resp, body
def wait_for_interface_status(self, server, port_id, status):
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index cf1dad9..82ded4c 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -136,7 +136,9 @@
def delete_server(self, server_id):
"""Deletes the given server."""
- return self.delete("servers/%s" % str(server_id))
+ resp, body = self.delete("servers/%s" % str(server_id))
+ self.validate_response(common_schema.delete_server, resp, body)
+ return resp, body
def list_servers(self, params=None):
"""Lists all servers for a user."""
@@ -274,6 +276,7 @@
def list_server_metadata(self, server_id):
resp, body = self.get("servers/%s/metadata" % str(server_id))
body = json.loads(body)
+ self.validate_response(common_schema.list_server_metadata, resp, body)
return resp, body['metadata']
def set_server_metadata(self, server_id, meta, no_metadata_field=False):
@@ -284,6 +287,7 @@
resp, body = self.put('servers/%s/metadata' % str(server_id),
post_body)
body = json.loads(body)
+ self.validate_response(common_schema.set_server_metadata, resp, body)
return resp, body['metadata']
def update_server_metadata(self, server_id, meta):
@@ -326,12 +330,15 @@
})
resp, body = self.post('servers/%s/os-volume_attachments' % server_id,
post_body)
+ body = json.loads(body)
+ self.validate_response(schema.attach_volume, resp, body)
return resp, body
def detach_volume(self, server_id, volume_id):
"""Detaches a volume from a server instance."""
resp, body = self.delete('servers/%s/os-volume_attachments/%s' %
(server_id, volume_id))
+ self.validate_response(schema.detach_volume, resp, body)
return resp, body
def add_security_group(self, server_id, name):
diff --git a/tempest/services/compute/v3/json/agents_client.py b/tempest/services/compute/v3/json/agents_client.py
index e1c286c..48be54c 100644
--- a/tempest/services/compute/v3/json/agents_client.py
+++ b/tempest/services/compute/v3/json/agents_client.py
@@ -15,6 +15,7 @@
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.common import rest_client
from tempest import config
@@ -34,7 +35,9 @@
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
- return resp, self._parse_resp(body)
+ body = json.loads(body)
+ self.validate_response(common_schema.list_agents, resp, body)
+ return resp, body['agents']
def create_agent(self, **kwargs):
"""Create an agent build."""
diff --git a/tempest/services/compute/v3/json/aggregates_client.py b/tempest/services/compute/v3/json/aggregates_client.py
index 8d7440e..0fc6af9 100644
--- a/tempest/services/compute/v3/json/aggregates_client.py
+++ b/tempest/services/compute/v3/json/aggregates_client.py
@@ -61,6 +61,7 @@
resp, body = self.put('os-aggregates/%s' % str(aggregate_id), put_body)
body = json.loads(body)
+ self.validate_response(schema.update_aggregate, resp, body)
return resp, body['aggregate']
def delete_aggregate(self, aggregate_id):
diff --git a/tempest/services/compute/v3/json/availability_zone_client.py b/tempest/services/compute/v3/json/availability_zone_client.py
index bad2de9..bf74e68 100644
--- a/tempest/services/compute/v3/json/availability_zone_client.py
+++ b/tempest/services/compute/v3/json/availability_zone_client.py
@@ -15,6 +15,7 @@
import json
+from tempest.api_schema.compute.v3 import availability_zone as schema
from tempest.common import rest_client
from tempest import config
@@ -31,9 +32,12 @@
def get_availability_zone_list(self):
resp, body = self.get('os-availability-zone')
body = json.loads(body)
+ self.validate_response(schema.get_availability_zone_list, resp, body)
return resp, body['availability_zone_info']
def get_availability_zone_list_detail(self):
resp, body = self.get('os-availability-zone/detail')
body = json.loads(body)
+ self.validate_response(schema.get_availability_zone_list_detail, resp,
+ body)
return resp, body['availability_zone_info']
diff --git a/tempest/services/compute/v3/json/flavors_client.py b/tempest/services/compute/v3/json/flavors_client.py
index 189fe3f..602fee2 100644
--- a/tempest/services/compute/v3/json/flavors_client.py
+++ b/tempest/services/compute/v3/json/flavors_client.py
@@ -18,6 +18,8 @@
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 \
+ as schema_extra_specs
from tempest.api_schema.compute.v3 import flavors as v3schema
from tempest.common import rest_client
from tempest import config
@@ -99,12 +101,15 @@
resp, body = self.post('flavors/%s/flavor-extra-specs' % flavor_id,
post_body)
body = json.loads(body)
+ self.validate_response(v3schema.set_flavor_extra_specs, resp, body)
return resp, body['extra_specs']
def get_flavor_extra_spec(self, flavor_id):
"""Gets extra Specs details of the mentioned flavor."""
resp, body = self.get('flavors/%s/flavor-extra-specs' % flavor_id)
body = json.loads(body)
+ self.validate_response(schema_extra_specs.flavor_extra_specs,
+ resp, body)
return resp, body['extra_specs']
def get_flavor_extra_spec_with_key(self, flavor_id, key):
@@ -112,6 +117,8 @@
resp, body = self.get('flavors/%s/flavor-extra-specs/%s' %
(str(flavor_id), key))
body = json.loads(body)
+ self.validate_response(schema_extra_specs.flavor_extra_specs_key,
+ resp, body)
return resp, body
def update_flavor_extra_spec(self, flavor_id, key, **kwargs):
@@ -119,12 +126,16 @@
resp, body = self.put('flavors/%s/flavor-extra-specs/%s' %
(flavor_id, key), json.dumps(kwargs))
body = json.loads(body)
+ self.validate_response(schema_extra_specs.flavor_extra_specs_key,
+ resp, body)
return resp, body
def unset_flavor_extra_spec(self, flavor_id, key):
"""Unsets extra Specs from the mentioned flavor."""
- return self.delete('flavors/%s/flavor-extra-specs/%s' %
- (str(flavor_id), key))
+ resp, body = self.delete('flavors/%s/flavor-extra-specs/%s' %
+ (str(flavor_id), key))
+ self.validate_response(v3schema.unset_flavor_extra_specs, resp, body)
+ return resp, body
def list_flavor_access(self, flavor_id):
"""Gets flavor access information given the flavor id."""
diff --git a/tempest/services/compute/v3/json/interfaces_client.py b/tempest/services/compute/v3/json/interfaces_client.py
index b45426c..25c8db7 100644
--- a/tempest/services/compute/v3/json/interfaces_client.py
+++ b/tempest/services/compute/v3/json/interfaces_client.py
@@ -16,6 +16,7 @@
import json
import time
+from tempest.api_schema.compute import interfaces as common_schema
from tempest.common import rest_client
from tempest import config
from tempest import exceptions
@@ -59,6 +60,7 @@
resp, body =\
self.delete('servers/%s/os-attach-interfaces/%s' % (server,
port_id))
+ self.validate_response(common_schema.delete_interface, resp, body)
return resp, body
def wait_for_interface_status(self, server, port_id, status):
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
index caeb315..67232a8 100644
--- a/tempest/services/compute/v3/json/servers_client.py
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -136,7 +136,9 @@
def delete_server(self, server_id):
"""Deletes the given server."""
- return self.delete("servers/%s" % str(server_id))
+ resp, body = self.delete("servers/%s" % str(server_id))
+ self.validate_response(common_schema.delete_server, resp, body)
+ return resp, body
def list_servers(self, params=None):
"""Lists all servers for a user."""
@@ -282,6 +284,7 @@
def list_server_metadata(self, server_id):
resp, body = self.get("servers/%s/metadata" % str(server_id))
body = json.loads(body)
+ self.validate_response(common_schema.list_server_metadata, resp, body)
return resp, body['metadata']
def set_server_metadata(self, server_id, meta, no_metadata_field=False):
@@ -292,6 +295,7 @@
resp, body = self.put('servers/%s/metadata' % str(server_id),
post_body)
body = json.loads(body)
+ self.validate_response(common_schema.set_server_metadata, resp, body)
return resp, body['metadata']
def update_server_metadata(self, server_id, meta):
@@ -326,12 +330,17 @@
def attach_volume(self, server_id, volume_id, device='/dev/vdz'):
"""Attaches a volume to a server instance."""
- return self.action(server_id, 'attach', None, volume_id=volume_id,
- device=device)
+ resp, body = self.action(server_id, 'attach', None,
+ volume_id=volume_id, device=device)
+ self.validate_response(schema.attach_detach_volume, resp, body)
+ return resp, body
def detach_volume(self, server_id, volume_id):
"""Detaches a volume from a server instance."""
- return self.action(server_id, 'detach', None, volume_id=volume_id)
+ resp, body = self.action(server_id, 'detach', None,
+ volume_id=volume_id)
+ self.validate_response(schema.attach_detach_volume, resp, body)
+ return resp, body
def live_migrate_server(self, server_id, dest_host, use_block_migration):
"""This should be called with administrator privileges ."""
diff --git a/tempest/services/compute/xml/aggregates_client.py b/tempest/services/compute/xml/aggregates_client.py
index b5f7678..9c2d4aa 100644
--- a/tempest/services/compute/xml/aggregates_client.py
+++ b/tempest/services/compute/xml/aggregates_client.py
@@ -61,9 +61,11 @@
def create_aggregate(self, name, availability_zone=None):
"""Creates a new aggregate."""
- post_body = xml_utils.Element("aggregate",
- name=name,
- availability_zone=availability_zone)
+ if availability_zone is not None:
+ post_body = xml_utils.Element("aggregate", name=name,
+ availability_zone=availability_zone)
+ else:
+ post_body = xml_utils.Element("aggregate", name=name)
resp, body = self.post('os-aggregates',
str(xml_utils.Document(post_body)))
aggregate = self._format_aggregate(etree.fromstring(body))
@@ -71,9 +73,11 @@
def update_aggregate(self, aggregate_id, name, availability_zone=None):
"""Update a aggregate."""
- put_body = xml_utils.Element("aggregate",
- name=name,
- availability_zone=availability_zone)
+ if availability_zone is not None:
+ put_body = xml_utils.Element("aggregate", name=name,
+ availability_zone=availability_zone)
+ else:
+ put_body = xml_utils.Element("aggregate", name=name)
resp, body = self.put('os-aggregates/%s' % str(aggregate_id),
str(xml_utils.Document(put_body)))
aggregate = self._format_aggregate(etree.fromstring(body))
diff --git a/tempest/services/data_processing/v1_1/client.py b/tempest/services/data_processing/v1_1/client.py
index e96b44b..c7b5f93 100644
--- a/tempest/services/data_processing/v1_1/client.py
+++ b/tempest/services/data_processing/v1_1/client.py
@@ -32,7 +32,6 @@
It returns pair: resp and parsed resource(s) body.
"""
-
resp, body = req_fun(uri, headers={
'Content-Type': 'application/json'
}, *args, **kwargs)
@@ -48,7 +47,7 @@
def get_node_group_template(self, tmpl_id):
"""Returns the details of a single node group template."""
- uri = "node-group-templates/%s" % tmpl_id
+ uri = 'node-group-templates/%s' % tmpl_id
return self._request_and_parse(self.get, uri, 'node_group_template')
def create_node_group_template(self, name, plugin_name, hadoop_version,
@@ -59,7 +58,7 @@
It supports passing additional params using kwargs and returns created
object.
"""
- uri = "node-group-templates"
+ uri = 'node-group-templates'
body = kwargs.copy()
body.update({
'name': name,
@@ -75,7 +74,7 @@
def delete_node_group_template(self, tmpl_id):
"""Deletes the specified node group template by id."""
- uri = "node-group-templates/%s" % tmpl_id
+ uri = 'node-group-templates/%s' % tmpl_id
return self.delete(uri)
def list_plugins(self):
@@ -87,7 +86,45 @@
def get_plugin(self, plugin_name, plugin_version=None):
"""Returns the details of a single plugin."""
- uri = "plugins/%s" % plugin_name
+ uri = 'plugins/%s' % plugin_name
if plugin_version:
uri += '/%s' % plugin_version
return self._request_and_parse(self.get, uri, 'plugin')
+
+ def list_cluster_templates(self):
+ """List all cluster templates for a user."""
+
+ uri = 'cluster-templates'
+ return self._request_and_parse(self.get, uri, 'cluster_templates')
+
+ def get_cluster_template(self, tmpl_id):
+ """Returns the details of a single cluster template."""
+
+ uri = 'cluster-templates/%s' % tmpl_id
+ return self._request_and_parse(self.get, uri, 'cluster_template')
+
+ def create_cluster_template(self, name, plugin_name, hadoop_version,
+ node_groups, cluster_configs=None,
+ **kwargs):
+ """Creates cluster template with specified params.
+
+ It supports passing additional params using kwargs and returns created
+ object.
+ """
+ uri = 'cluster-templates'
+ body = kwargs.copy()
+ body.update({
+ 'name': name,
+ 'plugin_name': plugin_name,
+ 'hadoop_version': hadoop_version,
+ 'node_groups': node_groups,
+ 'cluster_configs': cluster_configs or dict(),
+ })
+ return self._request_and_parse(self.post, uri, 'cluster_template',
+ body=json.dumps(body))
+
+ def delete_cluster_template(self, tmpl_id):
+ """Deletes the specified cluster template by id."""
+
+ uri = 'cluster-templates/%s' % tmpl_id
+ return self.delete(uri)
diff --git a/tempest/services/network/network_client_base.py b/tempest/services/network/network_client_base.py
index 34c61b0..2a797b2 100644
--- a/tempest/services/network/network_client_base.py
+++ b/tempest/services/network/network_client_base.py
@@ -104,7 +104,7 @@
def _list(**filters):
uri = self.get_uri(plural_name)
if filters:
- uri += '?' + urllib.urlencode(filters)
+ uri += '?' + urllib.urlencode(filters, doseq=1)
resp, body = self.get(uri)
result = {plural_name: self.deserialize_list(body)}
return resp, result
@@ -120,14 +120,14 @@
return _delete
def _shower(self, resource_name):
- def _show(resource_id, field_list=[]):
- # field_list is a sequence of two-element tuples, with the
- # first element being 'fields'. An example:
- # [('fields', 'id'), ('fields', 'name')]
+ def _show(resource_id, **fields):
+ # fields is a dict which key is 'fields' and value is a
+ # list of field's name. An example:
+ # {'fields': ['id', 'name']}
plural = self.pluralize(resource_name)
uri = '%s/%s' % (self.get_uri(plural), resource_id)
- if field_list:
- uri += '?' + urllib.urlencode(field_list)
+ if fields:
+ uri += '?' + urllib.urlencode(fields, doseq=1)
resp, body = self.get(uri)
body = self.deserialize_single(body)
return resp, body
diff --git a/tempest/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
index 53a3325..f3f4eb6 100644
--- a/tempest/services/object_storage/object_client.py
+++ b/tempest/services/object_storage/object_client.py
@@ -77,11 +77,16 @@
resp, body = self.head(url)
return resp, body
- def get_object(self, container, object_name):
+ def get_object(self, container, object_name, metadata=None):
"""Retrieve object's data."""
+ headers = {}
+ if metadata:
+ for key in metadata:
+ headers[str(key)] = metadata[key]
+
url = "{0}/{1}".format(container, object_name)
- resp, body = self.get(url)
+ resp, body = self.get(url, headers=headers)
return resp, body
def copy_object_in_same_container(self, container, src_object_name,
diff --git a/tempest/stress/actions/volume_attach_verify.py b/tempest/stress/actions/volume_attach_verify.py
new file mode 100644
index 0000000..1bc3b06
--- /dev/null
+++ b/tempest/stress/actions/volume_attach_verify.py
@@ -0,0 +1,232 @@
+# 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.utils import data_utils
+from tempest.common.utils.linux import remote_client
+from tempest import config
+import tempest.stress.stressaction as stressaction
+import tempest.test
+
+import re
+CONF = config.CONF
+
+
+class VolumeVerifyStress(stressaction.StressAction):
+
+ def _create_keypair(self):
+ keyname = data_utils.rand_name("key")
+ resp, self.key = self.manager.keypairs_client.create_keypair(keyname)
+ assert(resp.status == 200)
+
+ def _delete_keypair(self):
+ resp, _ = self.manager.keypairs_client.delete_keypair(self.key['name'])
+ assert(resp.status == 202)
+
+ def _create_vm(self):
+ self.name = name = data_utils.rand_name("instance")
+ servers_client = self.manager.servers_client
+ self.logger.info("creating %s" % name)
+ vm_args = self.vm_extra_args.copy()
+ vm_args['security_groups'] = [self.sec_grp]
+ vm_args['key_name'] = self.key['name']
+ resp, server = servers_client.create_server(name, self.image,
+ self.flavor,
+ **vm_args)
+ self.server_id = server['id']
+ assert(resp.status == 202)
+ self.manager.servers_client.wait_for_server_status(self.server_id,
+ 'ACTIVE')
+
+ def _destroy_vm(self):
+ self.logger.info("deleting server: %s" % self.server_id)
+ resp, _ = self.manager.servers_client.delete_server(self.server_id)
+ assert(resp.status == 204) # It cannot be 204 if I had to wait..
+ self.manager.servers_client.wait_for_server_termination(self.server_id)
+ self.logger.info("deleted server: %s" % self.server_id)
+
+ def _create_sec_group(self):
+ sec_grp_cli = self.manager.security_groups_client
+ s_name = data_utils.rand_name('sec_grp-')
+ s_description = data_utils.rand_name('desc-')
+ _, self.sec_grp = sec_grp_cli.create_security_group(s_name,
+ s_description)
+ create_rule = sec_grp_cli.create_security_group_rule
+ create_rule(self.sec_grp['id'], 'tcp', 22, 22)
+ create_rule(self.sec_grp['id'], 'icmp', -1, -1)
+
+ def _destroy_sec_grp(self):
+ sec_grp_cli = self.manager.security_groups_client
+ sec_grp_cli.delete_security_group(self.sec_grp['id'])
+
+ def _create_floating_ip(self):
+ floating_cli = self.manager.floating_ips_client
+ _, self.floating = floating_cli.create_floating_ip(self.floating_pool)
+
+ def _destroy_floating_ip(self):
+ cli = self.manager.floating_ips_client
+ cli.delete_floating_ip(self.floating['id'])
+ cli.wait_for_resource_deletion(self.floating['id'])
+ self.logger.info("Deleted Floating IP %s", str(self.floating['ip']))
+
+ def _create_volume(self):
+ name = data_utils.rand_name("volume")
+ self.logger.info("creating volume: %s" % name)
+ volumes_client = self.manager.volumes_client
+ resp, self.volume = volumes_client.create_volume(size=1,
+ display_name=
+ name)
+ assert(resp.status == 200)
+ volumes_client.wait_for_volume_status(self.volume['id'],
+ 'available')
+ self.logger.info("created volume: %s" % self.volume['id'])
+
+ def _delete_volume(self):
+ self.logger.info("deleting volume: %s" % self.volume['id'])
+ volumes_client = self.manager.volumes_client
+ resp, _ = volumes_client.delete_volume(self.volume['id'])
+ assert(resp.status == 202)
+ volumes_client.wait_for_resource_deletion(self.volume['id'])
+ self.logger.info("deleted volume: %s" % self.volume['id'])
+
+ def _wait_disassociate(self):
+ cli = self.manager.floating_ips_client
+
+ def func():
+ _, floating = cli.get_floating_ip_details(self.floating['id'])
+ return floating['instance_id'] is None
+
+ if not tempest.test.call_until_true(func, CONF.compute.build_timeout,
+ CONF.compute.build_interval):
+ raise RuntimeError("IP disassociate timeout!")
+
+ def new_server_ops(self):
+ self._create_vm()
+ cli = self.manager.floating_ips_client
+ cli.associate_floating_ip_to_server(self.floating['ip'],
+ self.server_id)
+ if self.ssh_test_before_attach and self.enable_ssh_verify:
+ self.logger.info("Scanning for block devices via ssh on %s"
+ % self.server_id)
+ self.part_wait(self.detach_match_count)
+
+ def setUp(self, **kwargs):
+ """Note able configuration combinations:
+ Closest options to the test_stamp_pattern:
+ new_server = True
+ new_volume = True
+ enable_ssh_verify = True
+ ssh_test_before_attach = False
+ Just attaching:
+ new_server = False
+ new_volume = False
+ enable_ssh_verify = True
+ ssh_test_before_attach = True
+ Mostly API load by repeated attachment:
+ new_server = False
+ new_volume = False
+ enable_ssh_verify = False
+ ssh_test_before_attach = False
+ Minimal Nova load, but cinder load not decreased:
+ new_server = False
+ new_volume = True
+ enable_ssh_verify = True
+ ssh_test_before_attach = True
+ """
+ self.image = CONF.compute.image_ref
+ self.flavor = CONF.compute.flavor_ref
+ self.vm_extra_args = kwargs.get('vm_extra_args', {})
+ self.floating_pool = kwargs.get('floating_pool', None)
+ self.new_volume = kwargs.get('new_volume', True)
+ self.new_server = kwargs.get('new_server', False)
+ self.enable_ssh_verify = kwargs.get('enable_ssh_verify', True)
+ self.ssh_test_before_attach = kwargs.get('ssh_test_before_attach',
+ False)
+ self.part_line_re = re.compile(kwargs.get('part_line_re', '.*vd.*'))
+ self.detach_match_count = kwargs.get('detach_match_count', 1)
+ self.attach_match_count = kwargs.get('attach_match_count', 2)
+ self.part_name = kwargs.get('part_name', '/dev/vdc')
+
+ self._create_floating_ip()
+ self._create_sec_group()
+ self._create_keypair()
+ private_key = self.key['private_key']
+ username = CONF.compute.image_ssh_user
+ self.remote_client = remote_client.RemoteClient(self.floating['ip'],
+ username,
+ pkey=private_key)
+ if not self.new_volume:
+ self._create_volume()
+ if not self.new_server:
+ self.new_server_ops()
+
+ # now we just test is number of partition increased or decrised
+ def part_wait(self, num_match):
+ def _part_state():
+ self.partitions = self.remote_client.get_partitions().split('\n')
+ matching = 0
+ for part_line in self.partitions[1:]:
+ if self.part_line_re.match(part_line):
+ matching += 1
+ return matching == num_match
+ if tempest.test.call_until_true(_part_state,
+ CONF.compute.build_timeout,
+ CONF.compute.build_interval):
+ return
+ else:
+ raise RuntimeError("Unexpected partitions: %s",
+ str(self.partitions))
+
+ def run(self):
+ if self.new_server:
+ self.new_server_ops()
+ if self.new_volume:
+ self._create_volume()
+ servers_client = self.manager.servers_client
+ self.logger.info("attach volume (%s) to vm %s" %
+ (self.volume['id'], self.server_id))
+ resp, body = servers_client.attach_volume(self.server_id,
+ self.volume['id'],
+ self.part_name)
+ assert(resp.status == 200)
+ self.manager.volumes_client.wait_for_volume_status(self.volume['id'],
+ 'in-use')
+ if self.enable_ssh_verify:
+ self.logger.info("Scanning for new block device on %s"
+ % self.server_id)
+ self.part_wait(self.attach_match_count)
+
+ resp, body = servers_client.detach_volume(self.server_id,
+ self.volume['id'])
+ assert(resp.status == 202)
+ self.manager.volumes_client.wait_for_volume_status(self.volume['id'],
+ 'available')
+ if self.enable_ssh_verify:
+ self.logger.info("Scanning for block device disapperance on %s"
+ % self.server_id)
+ self.part_wait(self.detach_match_count)
+ if self.new_volume:
+ self._delete_volume()
+ if self.new_server:
+ self._destroy_vm()
+
+ def tearDown(self):
+ cli = self.manager.floating_ips_client
+ cli.disassociate_floating_ip_from_server(self.floating['ip'],
+ self.server_id)
+ self._wait_disassociate()
+ if not self.new_server:
+ self._destroy_vm()
+ self._delete_keypair()
+ self._destroy_floating_ip()
+ self._destroy_sec_grp()
+ if not self.new_volume:
+ self._delete_volume()
diff --git a/tempest/stress/etc/volume-attach-verify.json b/tempest/stress/etc/volume-attach-verify.json
new file mode 100644
index 0000000..731f5ed
--- /dev/null
+++ b/tempest/stress/etc/volume-attach-verify.json
@@ -0,0 +1,11 @@
+[{"action": "tempest.stress.actions.volume_attach_verify.VolumeVerifyStress",
+ "threads": 1,
+ "use_admin": false,
+ "use_isolated_tenants": false,
+ "kwargs": {"vm_extra_args": {},
+ "new_volume": true,
+ "new_server": false,
+ "ssh_test_before_attach": false,
+ "enable_ssh_verify": true}
+}
+]
diff --git a/tempest/test.py b/tempest/test.py
index 8df405c..254fffa 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -75,12 +75,16 @@
try:
f(cls)
except Exception as se:
+ etype, value, trace = sys.exc_info()
LOG.exception("setUpClass failed: %s" % se)
try:
cls.tearDownClass()
except Exception as te:
LOG.exception("tearDownClass failed: %s" % te)
- raise se
+ try:
+ raise etype(value), None, trace
+ finally:
+ del trace # for avoiding circular refs
return decorator
diff --git a/tempest/tests/negative/test_generate_json.py b/tempest/tests/negative/test_generate_json.py
deleted file mode 100644
index e09fcdf..0000000
--- a/tempest/tests/negative/test_generate_json.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright 2014 Deutsche Telekom AG
-# 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.generator import negative_generator
-import tempest.test
-
-
-class TestNegativeGenerator(tempest.test.BaseTestCase):
-
- fake_input_str = {"type": "string",
- "minLength": 2,
- "maxLength": 8,
- 'results': {'gen_number': 404}}
-
- fake_input_int = {"type": "integer",
- "maximum": 255,
- "minimum": 1}
-
- fake_input_obj = {"type": "object",
- "properties": {"minRam": {"type": "integer"},
- "diskName": {"type": "string"},
- "maxRam": {"type": "integer", }
- }
- }
-
- def setUp(self):
- super(TestNegativeGenerator, self).setUp()
- self.negative = negative_generator.NegativeTestGenerator()
-
- def _validate_result(self, data):
- self.assertTrue(isinstance(data, list))
- for t in data:
- self.assertTrue(isinstance(t, tuple))
-
- def test_generate_invalid_string(self):
- result = self.negative.generate(self.fake_input_str)
- self._validate_result(result)
-
- def test_generate_invalid_integer(self):
- result = self.negative.generate(self.fake_input_int)
- self._validate_result(result)
-
- def test_generate_invalid_obj(self):
- result = self.negative.generate(self.fake_input_obj)
- self._validate_result(result)
diff --git a/tempest/tests/negative/test_negative_generators.py b/tempest/tests/negative/test_negative_generators.py
index f2ed999..c77faca 100644
--- a/tempest/tests/negative/test_negative_generators.py
+++ b/tempest/tests/negative/test_negative_generators.py
@@ -16,7 +16,9 @@
import jsonschema
import mock
-import tempest.common.generator.base_generator as base_generator
+from tempest.common.generator import base_generator
+from tempest.common.generator import negative_generator
+from tempest.common.generator import valid_generator
from tempest.tests import base
@@ -79,3 +81,73 @@
self.assertRaises(jsonschema.SchemaError,
self.generator.validate_schema,
self.invalid_json_schema_desc)
+
+
+class BaseNegativeGenerator(object):
+ types = ['string', 'integer', 'object']
+
+ fake_input_str = {"type": "string",
+ "minLength": 2,
+ "maxLength": 8,
+ 'results': {'gen_int': 404}}
+
+ fake_input_int = {"type": "integer",
+ "maximum": 255,
+ "minimum": 1}
+
+ fake_input_obj = {"type": "object",
+ "properties": {"minRam": {"type": "integer"},
+ "diskName": {"type": "string"},
+ "maxRam": {"type": "integer", }
+ }
+ }
+
+ unkown_type_schema = {
+ "type": "not_defined"
+ }
+
+ def _validate_result(self, data):
+ self.assertTrue(isinstance(data, list))
+ for t in data:
+ self.assertIsInstance(t, tuple)
+ self.assertEqual(3, len(t))
+ self.assertIsInstance(t[0], str)
+
+ def test_generate_string(self):
+ result = self.generator.generate(self.fake_input_str)
+ self._validate_result(result)
+
+ def test_generate_integer(self):
+ result = self.generator.generate(self.fake_input_int)
+ self._validate_result(result)
+
+ def test_generate_obj(self):
+ result = self.generator.generate(self.fake_input_obj)
+ self._validate_result(result)
+
+ def test_generator_mandatory_functions(self):
+ for data_type in self.types:
+ self.assertIn(data_type, self.generator.types_dict)
+
+ def test_generate_with_unknown_type(self):
+ self.assertRaises(TypeError, self.generator.generate,
+ self.unkown_type_schema)
+
+
+class TestNegativeValidGenerator(base.TestCase, BaseNegativeGenerator):
+ def setUp(self):
+ super(TestNegativeValidGenerator, self).setUp()
+ self.generator = valid_generator.ValidTestGenerator()
+
+ def test_generate_valid(self):
+ result = self.generator.generate_valid(self.fake_input_obj)
+ self.assertIn("minRam", result)
+ self.assertIsInstance(result["minRam"], int)
+ self.assertIn("diskName", result)
+ self.assertIsInstance(result["diskName"], str)
+
+
+class TestNegativeNegativeGenerator(base.TestCase, BaseNegativeGenerator):
+ def setUp(self):
+ super(TestNegativeNegativeGenerator, self).setUp()
+ self.generator = negative_generator.NegativeTestGenerator()
diff --git a/tempest/thirdparty/boto/test_ec2_instance_run.py b/tempest/thirdparty/boto/test_ec2_instance_run.py
index e8610d3..33b8d6e 100644
--- a/tempest/thirdparty/boto/test_ec2_instance_run.py
+++ b/tempest/thirdparty/boto/test_ec2_instance_run.py
@@ -218,10 +218,8 @@
else:
self.assertNotEqual(instance.state, "running")
- # NOTE(afazekas): doctored test case,
- # with normal validation it would fail
@test.attr(type='smoke')
- def test_integration_1(self):
+ def test_compute_with_volumes(self):
# EC2 1. integration test (not strict)
image_ami = self.ec2_client.get_image(self.images["ami"]["image_id"])
sec_group_name = data_utils.rand_name("securitygroup-")
@@ -249,14 +247,20 @@
instance_type=self.instance_type,
key_name=self.keypair_name,
security_groups=(sec_group_name,))
+
+ LOG.debug("Instance booted - state: %s",
+ reservation.instances[0].state)
+
self.addResourceCleanUp(self.destroy_reservation,
reservation)
volume = self.ec2_client.create_volume(1, self.zone)
+ LOG.debug("Volume created - status: %s", volume.status)
+
self.addResourceCleanUp(self.destroy_volume_wait, volume)
instance = reservation.instances[0]
- LOG.info("state: %s", instance.state)
if instance.state != "running":
self.assertInstanceStateWait(instance, "running")
+ LOG.debug("Instance now running - state: %s", instance.state)
address = self.ec2_client.allocate_address()
rcuk_a = self.addResourceCleanUp(address.delete)
@@ -284,10 +288,21 @@
volume.attach(instance.id, "/dev/vdh")
def _volume_state():
+ """Return volume state realizing that 'in-use' is overloaded."""
volume.update(validate=True)
- return volume.status
+ status = volume.status
+ attached = volume.attach_data.status
+ LOG.debug("Volume %s is in status: %s, attach_status: %s",
+ volume.id, status, attached)
+ # Nova reports 'in-use' on 'attaching' volumes because we
+ # have a single volume status, and EC2 has 2. Ensure that
+ # if we aren't attached yet we return something other than
+ # 'in-use'
+ if status == 'in-use' and attached != 'attached':
+ return 'attaching'
+ else:
+ return status
- self.assertVolumeStatusWait(_volume_state, "in-use")
wait.re_search_wait(_volume_state, "in-use")
# NOTE(afazekas): Different Hypervisor backends names
@@ -296,6 +311,7 @@
def _part_state():
current = ssh.get_partitions().split('\n')
+ LOG.debug("Partition map for instance: %s", current)
if current > part_lines:
return 'INCREASE'
if current < part_lines:
@@ -311,7 +327,6 @@
self.assertVolumeStatusWait(_volume_state, "available")
wait.re_search_wait(_volume_state, "available")
- LOG.info("Volume %s state: %s", volume.id, volume.status)
wait.state_wait(_part_state, 'DECREASE')
@@ -323,7 +338,7 @@
self.assertAddressReleasedWait(address)
self.cancelResourceCleanUp(rcuk_a)
- LOG.info("state: %s", instance.state)
+ LOG.debug("Instance %s state: %s", instance.id, instance.state)
if instance.state != "stopped":
self.assertInstanceStateWait(instance, "stopped")
# TODO(afazekas): move steps from teardown to the test case
diff --git a/tools/verify_tempest_config.py b/tools/verify_tempest_config.py
index 30785c4..1726beb 100755
--- a/tools/verify_tempest_config.py
+++ b/tools/verify_tempest_config.py
@@ -44,10 +44,11 @@
client_dict = {
'nova': os.servers_client,
'keystone': os.identity_client,
+ 'cinder': os.volumes_client,
}
client_dict[service].skip_path()
- endpoint_parts = urlparse.urlparse(client_dict[service])
- endpoint = endpoint_parts.scheme + '//' + endpoint_parts.netloc
+ endpoint_parts = urlparse.urlparse(client_dict[service].base_url)
+ endpoint = endpoint_parts.scheme + '://' + endpoint_parts.netloc
__, body = RAW_HTTP.request(endpoint, 'GET')
client_dict[service].reset_path()
body = json.loads(body)
@@ -76,6 +77,17 @@
not CONF.compute_feature_enabled.api_v3))
+def verify_cinder_api_versions(os):
+ # Check cinder api versions
+ versions = _get_api_versions(os, 'cinder')
+ if CONF.volume_feature_enabled.api_v1 != ('v1.0' in versions):
+ print('Config option volume api_v2 should be change to: %s' % (
+ not CONF.volume_feature_enabled.api_v1))
+ if CONF.volume_feature_enabled.api_v2 != ('v2.0' in versions):
+ print('Config option volume api_v2 should be change to: %s' % (
+ not CONF.volume_feature_enabled.api_v2))
+
+
def get_extension_client(os, service):
extensions_client = {
'nova': os.extensions_client,
@@ -169,10 +181,11 @@
'orchestration': 'heat',
'metering': 'ceilometer',
'telemetry': 'ceilometer',
- 'data_processing': 'savanna',
+ 'data_processing': 'sahara',
'baremetal': 'ironic',
- 'identity': 'keystone'
-
+ 'identity': 'keystone',
+ 'queuing': 'marconi',
+ 'database': 'trove'
}
# Get catalog list for endpoints to use for validation
__, endpoints = os.endpoints_client.list_endpoints()
@@ -219,6 +232,7 @@
verify_keystone_api_versions(os)
verify_glance_api_versions(os)
verify_nova_api_versions(os)
+ verify_cinder_api_versions(os)
display_results(results)