Merge "Refactor network client: add create_ and update_ methods"
diff --git a/.testr.conf b/.testr.conf
index c25ebec..abaf14a 100644
--- a/.testr.conf
+++ b/.testr.conf
@@ -2,8 +2,7 @@
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-500} \
- OS_TEST_PATH=${OS_TEST_PATH:-./tempest} \
- ${PYTHON:-python} -m subunit.run discover -t ./ $OS_TEST_PATH $LISTOPT $IDOPTION
+ ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./tempest/test_discover} $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list
group_regex=([^\.]*\.)*
diff --git a/README.rst b/README.rst
index 96f6e4c..4098e32 100644
--- a/README.rst
+++ b/README.rst
@@ -67,7 +67,7 @@
To run one single test ::
- $> testr run --parallel tempest.api.compute.servers.test_server_actions.ServerActionsTestJSON.test_rebuild_nonexistent_server
+ $> testr run --parallel tempest.api.compute.servers.test_servers_negative.ServersNegativeTestJSON.test_reboot_non_existent_server
Alternatively, you can use the run_tests.sh script which will create a venv
and run the tests or use tox to do the same.
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 607ba8b..e7145e7 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -234,6 +234,9 @@
# indicates every extension is enabled (list value)
#api_extensions=all
+# Is the v1 volume API enabled (boolean value)
+#api_v1=true
+
[image-feature-enabled]
diff --git a/tempest/api/compute/admin/test_hosts_negative.py b/tempest/api/compute/admin/test_hosts_negative.py
index 6b24e72..dbf7967 100644
--- a/tempest/api/compute/admin/test_hosts_negative.py
+++ b/tempest/api/compute/admin/test_hosts_negative.py
@@ -17,7 +17,7 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class HostsAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
@@ -41,18 +41,18 @@
hostname = hosts[0]['host_name']
return hostname
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_list_hosts_with_non_admin_user(self):
self.assertRaises(exceptions.Unauthorized,
self.non_admin_client.list_hosts)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_show_host_detail_with_nonexistent_hostname(self):
nonexitent_hostname = data_utils.rand_name('rand_hostname')
self.assertRaises(exceptions.NotFound,
self.client.show_host_detail, nonexitent_hostname)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_show_host_detail_with_non_admin_user(self):
hostname = self._get_host_name()
@@ -60,7 +60,7 @@
self.non_admin_client.show_host_detail,
hostname)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_update_host_with_non_admin_user(self):
hostname = self._get_host_name()
@@ -68,7 +68,8 @@
self.non_admin_client.update_host,
hostname)
- @attr(type=['negative', 'gate'])
+ @test.skip_because(bug="1261964", interface="xml")
+ @test.attr(type=['negative', 'gate'])
def test_update_host_with_extra_param(self):
# only 'status' and 'maintenance_mode' are the valid params.
hostname = self._get_host_name()
@@ -80,7 +81,7 @@
maintenance_mode='enable',
param='XXX')
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_update_host_with_invalid_status(self):
# 'status' can only be 'enable' or 'disable'
hostname = self._get_host_name()
@@ -91,7 +92,7 @@
status='invalid',
maintenance_mode='enable')
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_update_host_with_invalid_maintenance_mode(self):
# 'maintenance_mode' can only be 'enable' or 'disable'
hostname = self._get_host_name()
@@ -102,7 +103,7 @@
status='enable',
maintenance_mode='invalid')
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_update_host_without_param(self):
# 'status' or 'maintenance_mode' needed for host update
hostname = self._get_host_name()
@@ -111,7 +112,7 @@
self.client.update_host,
hostname)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_update_nonexistent_host(self):
nonexitent_hostname = data_utils.rand_name('rand_hostname')
@@ -121,7 +122,7 @@
status='enable',
maintenance_mode='enable')
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_startup_nonexistent_host(self):
nonexitent_hostname = data_utils.rand_name('rand_hostname')
@@ -129,7 +130,7 @@
self.client.startup_host,
nonexitent_hostname)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_startup_host_with_non_admin_user(self):
hostname = self._get_host_name()
@@ -137,7 +138,7 @@
self.non_admin_client.startup_host,
hostname)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_shutdown_nonexistent_host(self):
nonexitent_hostname = data_utils.rand_name('rand_hostname')
@@ -145,7 +146,7 @@
self.client.shutdown_host,
nonexitent_hostname)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_shutdown_host_with_non_admin_user(self):
hostname = self._get_host_name()
@@ -153,7 +154,7 @@
self.non_admin_client.shutdown_host,
hostname)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_reboot_nonexistent_host(self):
nonexitent_hostname = data_utils.rand_name('rand_hostname')
@@ -161,7 +162,7 @@
self.client.reboot_host,
nonexitent_hostname)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_reboot_host_with_non_admin_user(self):
hostname = self._get_host_name()
diff --git a/tempest/api/compute/admin/test_hypervisor.py b/tempest/api/compute/admin/test_hypervisor.py
index ef4f51f..bd431d4 100644
--- a/tempest/api/compute/admin/test_hypervisor.py
+++ b/tempest/api/compute/admin/test_hypervisor.py
@@ -38,24 +38,27 @@
self.assertEqual(200, resp.status)
return hypers
+ def assertHypervisors(self, hypers):
+ self.assertTrue(len(hypers) > 0, "No hypervisors found: %s" % hypers)
+
@attr(type='gate')
def test_get_hypervisor_list(self):
# List of hypervisor and available hypervisors hostname
hypers = self._list_hypervisors()
- self.assertTrue(len(hypers) > 0)
+ self.assertHypervisors(hypers)
@attr(type='gate')
def test_get_hypervisor_list_details(self):
# Display the details of the all hypervisor
resp, hypers = self.client.get_hypervisor_list_details()
self.assertEqual(200, resp.status)
- self.assertTrue(len(hypers) > 0)
+ self.assertHypervisors(hypers)
@attr(type='gate')
def test_get_hypervisor_show_details(self):
# Display the details of the specified hypervisor
hypers = self._list_hypervisors()
- self.assertTrue(len(hypers) > 0)
+ self.assertHypervisors(hypers)
resp, details = (self.client.
get_hypervisor_show_details(hypers[0]['id']))
@@ -68,7 +71,7 @@
def test_get_hypervisor_show_servers(self):
# Show instances about the specific hypervisors
hypers = self._list_hypervisors()
- self.assertTrue(len(hypers) > 0)
+ self.assertHypervisors(hypers)
hostname = hypers[0]['hypervisor_hostname']
resp, hypervisors = self.client.get_hypervisor_servers(hostname)
@@ -87,18 +90,29 @@
# Verify that GET shows the specified hypervisor uptime
hypers = self._list_hypervisors()
- resp, uptime = self.client.get_hypervisor_uptime(hypers[0]['id'])
- self.assertEqual(200, resp.status)
- self.assertTrue(len(uptime) > 0)
+ has_valid_uptime = False
+ for hyper in hypers:
+ # because hypervisors might be disabled, this loops looking
+ # for any good hit.
+ try:
+ resp, uptime = self.client.get_hypervisor_uptime(hyper['id'])
+ if (resp.status == 200) and (len(uptime) > 0):
+ has_valid_uptime = True
+ break
+ except Exception:
+ pass
+ self.assertTrue(
+ has_valid_uptime,
+ "None of the hypervisors had a valid uptime: %s" % hypers)
@attr(type='gate')
def test_search_hypervisor(self):
hypers = self._list_hypervisors()
- self.assertTrue(len(hypers) > 0)
+ self.assertHypervisors(hypers)
resp, hypers = self.client.search_hypervisor(
hypers[0]['hypervisor_hostname'])
self.assertEqual(200, resp.status)
- self.assertTrue(len(hypers) > 0)
+ self.assertHypervisors(hypers)
class HypervisorAdminTestXML(HypervisorAdminTestJSON):
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 9cb425a..73d274c 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -260,6 +260,7 @@
cls.servers_client = cls.os.servers_v3_client
cls.images_client = cls.os.image_client
+ cls.flavors_client = cls.os.flavors_v3_client
cls.services_client = cls.os.services_v3_client
cls.extensions_client = cls.os.extensions_v3_client
cls.availability_zone_client = cls.os.availability_zone_v3_client
@@ -268,6 +269,7 @@
cls.tenant_usages_client = cls.os.tenant_usages_v3_client
cls.volumes_client = cls.os.volumes_client
cls.certificates_client = cls.os.certificates_v3_client
+ cls.keypairs_client = cls.os.keypairs_v3_client
@classmethod
def create_image_from_server(cls, server_id, **kwargs):
@@ -332,3 +334,4 @@
cls.os_adm.availability_zone_v3_client
cls.hypervisor_admin_client = cls.os_adm.hypervisor_v3_client
cls.tenant_usages_admin_client = cls.os_adm.tenant_usages_v3_client
+ cls.flavors_admin_client = cls.os_adm.flavors_v3_client
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index 618abe2..76e0cae 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -17,7 +17,6 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
-from tempest import exceptions
from tempest.test import attr
@@ -110,49 +109,6 @@
expected = {'key2': 'value2'}
self.assertEqual(expected, resp_metadata)
- @attr(type=['negative', 'gate'])
- def test_list_nonexistant_image_metadata(self):
- # Negative test: List on nonexistant image
- # metadata should not happen
- self.assertRaises(exceptions.NotFound, self.client.list_image_metadata,
- 999)
-
- @attr(type=['negative', 'gate'])
- def test_update_nonexistant_image_metadata(self):
- # Negative test:An update should not happen for a non-existent image
- meta = {'key1': 'alt1', 'key2': 'alt2'}
- self.assertRaises(exceptions.NotFound,
- self.client.update_image_metadata, 999, meta)
-
- @attr(type=['negative', 'gate'])
- def test_get_nonexistant_image_metadata_item(self):
- # Negative test: Get on non-existent image should not happen
- self.assertRaises(exceptions.NotFound,
- self.client.get_image_metadata_item, 999, 'key2')
-
- @attr(type=['negative', 'gate'])
- def test_set_nonexistant_image_metadata(self):
- # Negative test: Metadata should not be set to a non-existent image
- meta = {'key1': 'alt1', 'key2': 'alt2'}
- self.assertRaises(exceptions.NotFound, self.client.set_image_metadata,
- 999, meta)
-
- @attr(type=['negative', 'gate'])
- def test_set_nonexistant_image_metadata_item(self):
- # Negative test: Metadata item should not be set to a
- # nonexistant image
- meta = {'key1': 'alt'}
- self.assertRaises(exceptions.NotFound,
- self.client.set_image_metadata_item, 999, 'key1',
- meta)
-
- @attr(type=['negative', 'gate'])
- def test_delete_nonexistant_image_metadata_item(self):
- # Negative test: Shouldn't be able to delete metadata
- # item from non-existent image
- self.assertRaises(exceptions.NotFound,
- self.client.delete_image_metadata_item, 999, 'key1')
-
class ImagesMetadataTestXML(ImagesMetadataTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/images/test_image_metadata_negative.py b/tempest/api/compute/images/test_image_metadata_negative.py
new file mode 100644
index 0000000..1767e5d
--- /dev/null
+++ b/tempest/api/compute/images/test_image_metadata_negative.py
@@ -0,0 +1,81 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ImagesMetadataTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ImagesMetadataTestJSON, cls).setUpClass()
+ cls.client = cls.images_client
+
+ @attr(type=['negative', 'gate'])
+ def test_list_nonexistent_image_metadata(self):
+ # Negative test: List on nonexistent image
+ # metadata should not happen
+ self.assertRaises(exceptions.NotFound, self.client.list_image_metadata,
+ data_utils.rand_uuid())
+
+ @attr(type=['negative', 'gate'])
+ def test_update_nonexistent_image_metadata(self):
+ # Negative test:An update should not happen for a non-existent image
+ meta = {'key1': 'alt1', 'key2': 'alt2'}
+ self.assertRaises(exceptions.NotFound,
+ self.client.update_image_metadata,
+ data_utils.rand_uuid(), meta)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_nonexistent_image_metadata_item(self):
+ # Negative test: Get on non-existent image should not happen
+ self.assertRaises(exceptions.NotFound,
+ self.client.get_image_metadata_item,
+ data_utils.rand_uuid(), 'key2')
+
+ @attr(type=['negative', 'gate'])
+ def test_set_nonexistent_image_metadata(self):
+ # Negative test: Metadata should not be set to a non-existent image
+ meta = {'key1': 'alt1', 'key2': 'alt2'}
+ self.assertRaises(exceptions.NotFound, self.client.set_image_metadata,
+ data_utils.rand_uuid(), meta)
+
+ @attr(type=['negative', 'gate'])
+ def test_set_nonexistent_image_metadata_item(self):
+ # Negative test: Metadata item should not be set to a
+ # nonexistent image
+ meta = {'key1': 'alt'}
+ self.assertRaises(exceptions.NotFound,
+ self.client.set_image_metadata_item,
+ data_utils.rand_uuid(), 'key1',
+ meta)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_nonexistent_image_metadata_item(self):
+ # Negative test: Shouldn't be able to delete metadata
+ # item from non-existent image
+ self.assertRaises(exceptions.NotFound,
+ self.client.delete_image_metadata_item,
+ data_utils.rand_uuid(), 'key1')
+
+
+class ImagesMetadataTestXML(ImagesMetadataTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/servers/test_virtual_interfaces.py b/tempest/api/compute/servers/test_virtual_interfaces.py
index 164c6df..968ae47 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces.py
@@ -18,11 +18,8 @@
import netaddr
from tempest.api.compute import base
-from tempest.common.utils import data_utils
from tempest import config
-from tempest import exceptions
-from tempest.test import attr
-from tempest.test import skip_because
+from tempest import test
class VirtualInterfacesTestJSON(base.BaseV2ComputeTest):
@@ -37,9 +34,9 @@
resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
- @skip_because(bug="1183436",
- condition=CONF.service_available.neutron)
- @attr(type='gate')
+ @test.skip_because(bug="1183436",
+ condition=CONF.service_available.neutron)
+ @test.attr(type='gate')
def test_list_virtual_interfaces(self):
# Positive test:Should be able to GET the virtual interfaces list
# for a given server_id
@@ -54,15 +51,6 @@
self.assertTrue(netaddr.valid_mac(mac_address),
"Invalid mac address detected.")
- @attr(type=['negative', 'gate'])
- def test_list_virtual_interfaces_invalid_server_id(self):
- # Negative test: Should not be able to GET virtual interfaces
- # for an invalid server_id
- invalid_server_id = data_utils.rand_name('!@#$%^&*()')
- self.assertRaises(exceptions.NotFound,
- self.client.list_virtual_interfaces,
- invalid_server_id)
-
class VirtualInterfacesTestXML(VirtualInterfacesTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/servers/test_virtual_interfaces_negative.py b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
new file mode 100644
index 0000000..a2a6c11
--- /dev/null
+++ b/tempest/api/compute/servers/test_virtual_interfaces_negative.py
@@ -0,0 +1,44 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import uuid
+
+from tempest.api.compute import base
+from tempest import exceptions
+from tempest import test
+
+
+class VirtualInterfacesNegativeTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(VirtualInterfacesNegativeTestJSON, cls).setUpClass()
+ cls.client = cls.servers_client
+
+ @test.attr(type=['negative', 'gate'])
+ def test_list_virtual_interfaces_invalid_server_id(self):
+ # Negative test: Should not be able to GET virtual interfaces
+ # for an invalid server_id
+ invalid_server_id = str(uuid.uuid4())
+ self.assertRaises(exceptions.NotFound,
+ self.client.list_virtual_interfaces,
+ invalid_server_id)
+
+
+class VirtualInterfacesNegativeTestXML(VirtualInterfacesNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_flavors_access.py b/tempest/api/compute/v3/admin/test_flavors_access.py
index 048312b..86194af 100644
--- a/tempest/api/compute/v3/admin/test_flavors_access.py
+++ b/tempest/api/compute/v3/admin/test_flavors_access.py
@@ -20,7 +20,7 @@
from tempest import test
-class FlavorsAccessTestJSON(base.BaseV2ComputeAdminTest):
+class FlavorsAccessV3TestJSON(base.BaseV3ComputeAdminTest):
"""
Tests Flavor Access API extension.
@@ -31,19 +31,15 @@
@classmethod
def setUpClass(cls):
- super(FlavorsAccessTestJSON, cls).setUpClass()
- if not test.is_extension_enabled('FlavorExtraData', 'compute'):
- msg = "FlavorExtraData extension not enabled."
- raise cls.skipException(msg)
+ super(FlavorsAccessV3TestJSON, cls).setUpClass()
- cls.client = cls.os_adm.flavors_client
+ cls.client = cls.flavors_admin_client
admin_client = cls._get_identity_admin_client()
cls.tenant = admin_client.get_tenant_by_name(cls.flavors_client.
tenant_name)
cls.tenant_id = cls.tenant['id']
- cls.adm_tenant = admin_client.get_tenant_by_name(cls.os_adm.
- flavors_client.
- tenant_name)
+ cls.adm_tenant = admin_client.get_tenant_by_name(
+ cls.flavors_admin_client.tenant_name)
cls.adm_tenant_id = cls.adm_tenant['id']
cls.flavor_name_prefix = 'test_flavor_access_'
cls.ram = 512
@@ -61,7 +57,7 @@
new_flavor_id,
is_public='False')
self.addCleanup(self.client.delete_flavor, new_flavor['id'])
- self.assertEqual(resp.status, 200)
+ self.assertEqual(resp.status, 201)
resp, flavor_access = self.client.list_flavor_access(new_flavor_id)
self.assertEqual(resp.status, 200)
self.assertEqual(len(flavor_access), 1, str(flavor_access))
@@ -107,5 +103,5 @@
self.assertNotIn(new_flavor['id'], map(lambda x: x['id'], flavors))
-class FlavorsAdminTestXML(FlavorsAccessTestJSON):
+class FlavorsAdminV3TestXML(FlavorsAccessV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_flavors_access_negative.py b/tempest/api/compute/v3/admin/test_flavors_access_negative.py
index 976124e..df6557e 100644
--- a/tempest/api/compute/v3/admin/test_flavors_access_negative.py
+++ b/tempest/api/compute/v3/admin/test_flavors_access_negative.py
@@ -23,7 +23,7 @@
from tempest import test
-class FlavorsAccessNegativeTestJSON(base.BaseV2ComputeAdminTest):
+class FlavorsAccessNegativeV3TestJSON(base.BaseV3ComputeAdminTest):
"""
Tests Flavor Access API extension.
@@ -34,19 +34,15 @@
@classmethod
def setUpClass(cls):
- super(FlavorsAccessNegativeTestJSON, cls).setUpClass()
- if not test.is_extension_enabled('FlavorExtraData', 'compute'):
- msg = "FlavorExtraData extension not enabled."
- raise cls.skipException(msg)
+ super(FlavorsAccessNegativeV3TestJSON, cls).setUpClass()
- cls.client = cls.os_adm.flavors_client
+ cls.client = cls.flavors_admin_client
admin_client = cls._get_identity_admin_client()
cls.tenant = admin_client.get_tenant_by_name(cls.flavors_client.
tenant_name)
cls.tenant_id = cls.tenant['id']
- cls.adm_tenant = admin_client.get_tenant_by_name(cls.os_adm.
- flavors_client.
- tenant_name)
+ cls.adm_tenant = admin_client.get_tenant_by_name(
+ cls.flavors_admin_client.tenant_name)
cls.adm_tenant_id = cls.adm_tenant['id']
cls.flavor_name_prefix = 'test_flavor_access_'
cls.ram = 512
@@ -64,7 +60,7 @@
new_flavor_id,
is_public='True')
self.addCleanup(self.client.delete_flavor, new_flavor['id'])
- self.assertEqual(resp.status, 200)
+ self.assertEqual(resp.status, 201)
self.assertRaises(exceptions.NotFound,
self.client.list_flavor_access,
new_flavor_id)
@@ -148,5 +144,5 @@
str(uuid.uuid4()))
-class FlavorsAdminNegativeTestXML(FlavorsAccessNegativeTestJSON):
+class FlavorsAdminNegativeV3TestXML(FlavorsAccessNegativeV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_flavors_extra_specs.py b/tempest/api/compute/v3/admin/test_flavors_extra_specs.py
index 875f742..d745829 100644
--- a/tempest/api/compute/v3/admin/test_flavors_extra_specs.py
+++ b/tempest/api/compute/v3/admin/test_flavors_extra_specs.py
@@ -20,7 +20,7 @@
from tempest import test
-class FlavorsExtraSpecsTestJSON(base.BaseV2ComputeAdminTest):
+class FlavorsExtraSpecsV3TestJSON(base.BaseV3ComputeAdminTest):
"""
Tests Flavor Extra Spec API extension.
@@ -32,12 +32,9 @@
@classmethod
def setUpClass(cls):
- super(FlavorsExtraSpecsTestJSON, cls).setUpClass()
- if not test.is_extension_enabled('FlavorExtraData', 'compute'):
- msg = "FlavorExtraData extension not enabled."
- raise cls.skipException(msg)
+ super(FlavorsExtraSpecsV3TestJSON, cls).setUpClass()
- cls.client = cls.os_adm.flavors_client
+ cls.client = cls.flavors_admin_client
flavor_name = data_utils.rand_name('test_flavor')
ram = 512
vcpus = 1
@@ -58,7 +55,7 @@
def tearDownClass(cls):
resp, body = cls.client.delete_flavor(cls.flavor['id'])
cls.client.wait_for_resource_deletion(cls.flavor['id'])
- super(FlavorsExtraSpecsTestJSON, cls).tearDownClass()
+ super(FlavorsExtraSpecsV3TestJSON, cls).tearDownClass()
@test.attr(type='gate')
def test_flavor_set_get_update_show_unset_keys(self):
@@ -69,7 +66,7 @@
# SET extra specs to the flavor created in setUp
set_resp, set_body = \
self.client.set_flavor_extra_spec(self.flavor['id'], specs)
- self.assertEqual(set_resp.status, 200)
+ self.assertEqual(set_resp.status, 201)
self.assertEqual(set_body, specs)
# GET extra specs and verify
get_resp, get_body = \
@@ -95,10 +92,10 @@
# UNSET extra specs that were set in this test
unset_resp, _ = \
self.client.unset_flavor_extra_spec(self.flavor['id'], "key1")
- self.assertEqual(unset_resp.status, 200)
+ self.assertEqual(unset_resp.status, 204)
unset_resp, _ = \
self.client.unset_flavor_extra_spec(self.flavor['id'], "key2")
- self.assertEqual(unset_resp.status, 200)
+ self.assertEqual(unset_resp.status, 204)
@test.attr(type='gate')
def test_flavor_non_admin_get_all_keys(self):
@@ -117,7 +114,7 @@
specs = {"key1": "value1", "key2": "value2"}
resp, body = self.client.set_flavor_extra_spec(
self.flavor['id'], specs)
- self.assertEqual(resp.status, 200)
+ self.assertEqual(resp.status, 201)
self.assertEqual(body['key1'], 'value1')
self.assertIn('key2', body)
resp, body = self.flavors_client.get_flavor_extra_spec_with_key(
@@ -127,5 +124,5 @@
self.assertNotIn('key2', body)
-class FlavorsExtraSpecsTestXML(FlavorsExtraSpecsTestJSON):
+class FlavorsExtraSpecsV3TestXML(FlavorsExtraSpecsV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_flavors_extra_specs_negative.py b/tempest/api/compute/v3/admin/test_flavors_extra_specs_negative.py
index fb09a63..1d5e447 100644
--- a/tempest/api/compute/v3/admin/test_flavors_extra_specs_negative.py
+++ b/tempest/api/compute/v3/admin/test_flavors_extra_specs_negative.py
@@ -22,7 +22,7 @@
from tempest import test
-class FlavorsExtraSpecsNegativeTestJSON(base.BaseV2ComputeAdminTest):
+class FlavorsExtraSpecsNegativeV3TestJSON(base.BaseV3ComputeAdminTest):
"""
Negative Tests Flavor Extra Spec API extension.
@@ -33,12 +33,9 @@
@classmethod
def setUpClass(cls):
- super(FlavorsExtraSpecsNegativeTestJSON, cls).setUpClass()
- if not test.is_extension_enabled('FlavorExtraData', 'compute'):
- msg = "FlavorExtraData extension not enabled."
- raise cls.skipException(msg)
+ super(FlavorsExtraSpecsNegativeV3TestJSON, cls).setUpClass()
- cls.client = cls.os_adm.flavors_client
+ cls.client = cls.flavors_admin_client
flavor_name = data_utils.rand_name('test_flavor')
ram = 512
vcpus = 1
@@ -59,7 +56,7 @@
def tearDownClass(cls):
resp, body = cls.client.delete_flavor(cls.flavor['id'])
cls.client.wait_for_resource_deletion(cls.flavor['id'])
- super(FlavorsExtraSpecsNegativeTestJSON, cls).tearDownClass()
+ super(FlavorsExtraSpecsNegativeV3TestJSON, cls).tearDownClass()
@test.attr(type=['negative', 'gate'])
def test_flavor_non_admin_set_keys(self):
@@ -76,7 +73,7 @@
specs = {"key1": "value1", "key2": "value2"}
resp, body = self.client.set_flavor_extra_spec(
self.flavor['id'], specs)
- self.assertEqual(resp.status, 200)
+ self.assertEqual(resp.status, 201)
self.assertEqual(body['key1'], 'value1')
self.assertRaises(exceptions.Unauthorized,
self.flavors_client.
@@ -131,5 +128,5 @@
key2="value")
-class FlavorsExtraSpecsNegativeTestXML(FlavorsExtraSpecsNegativeTestJSON):
+class FlavorsExtraSpecsNegativeV3TestXML(FlavorsExtraSpecsNegativeV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/v3/keypairs/test_keypairs.py b/tempest/api/compute/v3/keypairs/test_keypairs.py
index b36595c..029633f 100644
--- a/tempest/api/compute/v3/keypairs/test_keypairs.py
+++ b/tempest/api/compute/v3/keypairs/test_keypairs.py
@@ -20,17 +20,17 @@
from tempest import test
-class KeyPairsTestJSON(base.BaseV2ComputeTest):
+class KeyPairsV3TestJSON(base.BaseV3ComputeTest):
_interface = 'json'
@classmethod
def setUpClass(cls):
- super(KeyPairsTestJSON, cls).setUpClass()
+ super(KeyPairsV3TestJSON, cls).setUpClass()
cls.client = cls.keypairs_client
def _delete_keypair(self, keypair_name):
resp, _ = self.client.delete_keypair(keypair_name)
- self.assertEqual(202, resp.status)
+ self.assertEqual(204, resp.status)
def _create_keypair(self, keypair_name, pub_key=None):
resp, body = self.client.create_keypair(keypair_name, pub_key)
@@ -49,7 +49,7 @@
# as the keypair dicts from list API doesn't have them.
keypair.pop('private_key')
keypair.pop('user_id')
- self.assertEqual(200, resp.status)
+ self.assertEqual(201, resp.status)
key_list.append(keypair)
# Fetch all keypairs and verify the list
# has all created keypairs
@@ -72,7 +72,7 @@
# Keypair should be created, verified and deleted
k_name = data_utils.rand_name('keypair-')
resp, keypair = self._create_keypair(k_name)
- self.assertEqual(200, resp.status)
+ self.assertEqual(201, resp.status)
private_key = keypair['private_key']
key_name = keypair['name']
self.assertEqual(key_name, k_name,
@@ -111,7 +111,7 @@
"XcPojYN56tI0OlrGqojbediJYD0rUsJu4weZpbn8vilb3JuDY+jws"
"snSA8wzBx3A/8y9Pp1B nova@ubuntu")
resp, keypair = self._create_keypair(k_name, pub_key)
- self.assertEqual(200, resp.status)
+ self.assertEqual(201, resp.status)
self.assertFalse('private_key' in keypair,
"Field private_key is not empty!")
key_name = keypair['name']
@@ -120,5 +120,5 @@
"to the requested name!")
-class KeyPairsTestXML(KeyPairsTestJSON):
+class KeyPairsV3TestXML(KeyPairsV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/v3/keypairs/test_keypairs_negative.py b/tempest/api/compute/v3/keypairs/test_keypairs_negative.py
index 621487c..edc0c26 100644
--- a/tempest/api/compute/v3/keypairs/test_keypairs_negative.py
+++ b/tempest/api/compute/v3/keypairs/test_keypairs_negative.py
@@ -22,12 +22,12 @@
from tempest import test
-class KeyPairsNegativeTestJSON(base.BaseV2ComputeTest):
+class KeyPairsNegativeV3TestJSON(base.BaseV3ComputeTest):
_interface = 'json'
@classmethod
def setUpClass(cls):
- super(KeyPairsNegativeTestJSON, cls).setUpClass()
+ super(KeyPairsNegativeV3TestJSON, cls).setUpClass()
cls.client = cls.keypairs_client
def _create_keypair(self, keypair_name, pub_key=None):
@@ -70,12 +70,11 @@
# Keypairs with duplicate names should not be created
k_name = data_utils.rand_name('keypair-')
resp, _ = self.client.create_keypair(k_name)
- self.assertEqual(200, resp.status)
+ self.addCleanup(self.client.delete_keypair, k_name)
+ self.assertEqual(201, resp.status)
# Now try the same keyname to create another key
self.assertRaises(exceptions.Conflict, self._create_keypair,
k_name)
- resp, _ = self.client.delete_keypair(k_name)
- self.assertEqual(202, resp.status)
@test.attr(type=['negative', 'gate'])
def test_create_keypair_with_empty_name_string(self):
@@ -98,5 +97,5 @@
k_name)
-class KeyPairsNegativeTestXML(KeyPairsNegativeTestJSON):
+class KeyPairsNegativeV3TestXML(KeyPairsNegativeV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
index 8cd6c11..602bd5b 100644
--- a/tempest/api/compute/v3/servers/test_server_actions.py
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -123,6 +123,7 @@
metadata=meta,
personality=personality,
admin_password=password)
+ self.addCleanup(self.client.rebuild, self.server_id, self.image_ref)
# Verify the properties in the initial response are correct
self.assertEqual(self.server_id, rebuilt_server['id'])
@@ -142,6 +143,35 @@
linux_client = RemoteClient(server, self.ssh_user, password)
linux_client.validate_authentication()
+ @attr(type='gate')
+ def test_rebuild_server_in_stop_state(self):
+ # The server in stop state should be rebuilt using the provided
+ # image and remain in SHUTOFF state
+ resp, server = self.client.get_server(self.server_id)
+ old_image = server['image']['id']
+ new_image = self.image_ref_alt \
+ if old_image == self.image_ref else self.image_ref
+ resp, server = self.client.stop(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'SHUTOFF')
+ self.addCleanup(self.client.start, self.server_id)
+ resp, rebuilt_server = self.client.rebuild(self.server_id, new_image)
+ self.addCleanup(self.client.wait_for_server_status, self.server_id,
+ 'SHUTOFF')
+ self.addCleanup(self.client.rebuild, self.server_id, old_image)
+
+ # Verify the properties in the initial response are correct
+ self.assertEqual(self.server_id, rebuilt_server['id'])
+ rebuilt_image_id = rebuilt_server['image']['id']
+ self.assertEqual(new_image, rebuilt_image_id)
+ self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id'])
+
+ # Verify the server properties after the rebuild completes
+ self.client.wait_for_server_status(rebuilt_server['id'], 'SHUTOFF')
+ resp, server = self.client.get_server(rebuilt_server['id'])
+ rebuilt_image_id = server['image']['id']
+ self.assertEqual(new_image, rebuilt_image_id)
+
def _detect_server_image_flavor(self, server_id):
# Detects the current server image flavor ref.
resp, server = self.client.get_server(self.server_id)
@@ -199,32 +229,115 @@
raise exceptions.TimeoutException(message)
@attr(type='gate')
+ def test_create_backup(self):
+ # Positive test:create backup successfully and rotate backups correctly
+ # create the first and the second backup
+ backup1 = data_utils.rand_name('backup')
+ resp, _ = self.servers_client.create_backup(self.server_id,
+ 'daily',
+ 2,
+ backup1)
+ oldest_backup_exist = True
+
+ # the oldest one should be deleted automatically in this test
+ def _clean_oldest_backup(oldest_backup):
+ if oldest_backup_exist:
+ self.images_client.delete_image(oldest_backup)
+
+ image1_id = data_utils.parse_image_id(resp['location'])
+ self.addCleanup(_clean_oldest_backup, image1_id)
+ self.assertEqual(202, resp.status)
+ self.images_client.wait_for_image_status(image1_id, 'active')
+
+ backup2 = data_utils.rand_name('backup')
+ self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+ resp, _ = self.servers_client.create_backup(self.server_id,
+ 'daily',
+ 2,
+ backup2)
+ image2_id = data_utils.parse_image_id(resp['location'])
+ self.addCleanup(self.images_client.delete_image, image2_id)
+ self.assertEqual(202, resp.status)
+ self.images_client.wait_for_image_status(image2_id, 'active')
+
+ # verify they have been created
+ properties = {
+ 'image_type': 'backup',
+ 'backup_type': "daily",
+ 'instance_uuid': self.server_id,
+ }
+ resp, image_list = self.images_client.image_list_detail(
+ properties,
+ sort_key='created_at',
+ sort_dir='asc')
+ self.assertEqual(200, resp.status)
+ self.assertEqual(2, len(image_list))
+ self.assertEqual((backup1, backup2),
+ (image_list[0]['name'], image_list[1]['name']))
+
+ # create the third one, due to the rotation is 2,
+ # the first one will be deleted
+ backup3 = data_utils.rand_name('backup')
+ self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+ resp, _ = self.servers_client.create_backup(self.server_id,
+ 'daily',
+ 2,
+ backup3)
+ image3_id = data_utils.parse_image_id(resp['location'])
+ self.addCleanup(self.images_client.delete_image, image3_id)
+ self.assertEqual(202, resp.status)
+ # the first back up should be deleted
+ self.images_client.wait_for_resource_deletion(image1_id)
+ oldest_backup_exist = False
+ resp, image_list = self.images_client.image_list_detail(
+ properties,
+ sort_key='created_at',
+ sort_dir='asc')
+ self.assertEqual(200, resp.status)
+ self.assertEqual(2, len(image_list))
+ self.assertEqual((backup2, backup3),
+ (image_list[0]['name'], image_list[1]['name']))
+
+ def _get_output(self):
+ resp, output = self.servers_client.get_console_output(
+ self.server_id, 10)
+ self.assertEqual(200, resp.status)
+ self.assertTrue(output, "Console output was empty.")
+ lines = len(output.split('\n'))
+ self.assertEqual(lines, 10)
+
+ @attr(type='gate')
def test_get_console_output(self):
# Positive test:Should be able to GET the console output
# for a given server_id and number of lines
- def get_output():
- resp, output = self.servers_client.get_console_output(
- self.server_id, 10)
- self.assertEqual(200, resp.status)
- self.assertTrue(output, "Console output was empty.")
- lines = len(output.split('\n'))
- self.assertEqual(lines, 10)
- self.wait_for(get_output)
- @skip_because(bug="1014683")
+ # This reboot is necessary for outputting some console log after
+ # creating a instance backup. If a instance backup, the console
+ # log file is truncated and we cannot get any console log through
+ # "console-log" API.
+ # The detail is https://bugs.launchpad.net/nova/+bug/1251920
+ resp, body = self.servers_client.reboot(self.server_id, 'HARD')
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ self.wait_for(self._get_output)
+
@attr(type='gate')
- def test_get_console_output_server_id_in_reboot_status(self):
+ def test_get_console_output_server_id_in_shutoff_status(self):
# Positive test:Should be able to GET the console output
- # for a given server_id in reboot status
- resp, output = self.servers_client.reboot(self.server_id, 'SOFT')
- self.servers_client.wait_for_server_status(self.server_id,
- 'REBOOT')
- resp, output = self.servers_client.get_console_output(self.server_id,
- 10)
- self.assertEqual(200, resp.status)
- self.assertIsNotNone(output)
- lines = len(output.split('\n'))
- self.assertEqual(lines, 10)
+ # for a given server_id in SHUTOFF status
+
+ # NOTE: SHUTOFF is irregular status. To avoid test instability,
+ # one server is created only for this test without using
+ # the server that was created in setupClass.
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+ temp_server_id = server['id']
+
+ resp, server = self.servers_client.stop(temp_server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(temp_server_id, 'SHUTOFF')
+
+ self.wait_for(self._get_output)
@attr(type='gate')
def test_pause_unpause_server(self):
@@ -245,6 +358,30 @@
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
@attr(type='gate')
+ def test_shelve_unshelve_server(self):
+ resp, server = self.client.shelve_server(self.server_id)
+ self.assertEqual(202, resp.status)
+
+ offload_time = self.config.compute.shelved_offload_time
+ if offload_time >= 0:
+ self.client.wait_for_server_status(self.server_id,
+ 'SHELVED_OFFLOADED',
+ extra_timeout=offload_time)
+ else:
+ self.client.wait_for_server_status(self.server_id,
+ 'SHELVED')
+
+ resp, server = self.client.get_server(self.server_id)
+ image_name = server['name'] + '-shelved'
+ resp, images = self.images_client.image_list(name=image_name)
+ self.assertEqual(1, len(images))
+ self.assertEqual(image_name, images[0]['name'])
+
+ resp, server = self.client.unshelve_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ @attr(type='gate')
def test_stop_start_server(self):
resp, server = self.servers_client.stop(self.server_id)
self.assertEqual(202, resp.status)
diff --git a/tempest/api/compute/v3/servers/test_servers_negative.py b/tempest/api/compute/v3/servers/test_servers_negative.py
index 6532032..85fe47c 100644
--- a/tempest/api/compute/v3/servers/test_servers_negative.py
+++ b/tempest/api/compute/v3/servers/test_servers_negative.py
@@ -25,11 +25,11 @@
from tempest import test
-class ServersNegativeTestJSON(base.BaseV2ComputeTest):
+class ServersNegativeV3TestJSON(base.BaseV3ComputeTest):
_interface = 'json'
def setUp(self):
- super(ServersNegativeTestJSON, self).setUp()
+ super(ServersNegativeV3TestJSON, self).setUp()
try:
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
except Exception:
@@ -37,10 +37,10 @@
@classmethod
def setUpClass(cls):
- super(ServersNegativeTestJSON, cls).setUpClass()
+ super(ServersNegativeV3TestJSON, cls).setUpClass()
cls.client = cls.servers_client
cls.alt_os = clients.AltManager()
- cls.alt_client = cls.alt_os.servers_client
+ cls.alt_client = cls.alt_os.servers_v3_client
resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
@@ -53,18 +53,6 @@
name='')
@test.attr(type=['negative', 'gate'])
- def test_personality_file_contents_not_encoded(self):
- # Use an unencoded file when creating a server with personality
-
- file_contents = 'This is a test file.'
- person = [{'path': '/etc/testfile.txt',
- 'contents': file_contents}]
-
- self.assertRaises(exceptions.BadRequest,
- self.create_test_server,
- personality=person)
-
- @test.attr(type=['negative', 'gate'])
def test_create_with_invalid_image(self):
# Create a server with an unknown image
@@ -86,7 +74,7 @@
IPv4 = '1.1.1.1.1.1'
self.assertRaises(exceptions.BadRequest,
- self.create_test_server, accessIPv4=IPv4)
+ self.create_test_server, access_ip_v4=IPv4)
@test.attr(type=['negative', 'gate'])
def test_invalid_ip_v6_address(self):
@@ -95,7 +83,7 @@
IPv6 = 'notvalid'
self.assertRaises(exceptions.BadRequest,
- self.create_test_server, accessIPv6=IPv6)
+ self.create_test_server, access_ip_v6=IPv6)
@test.attr(type=['negative', 'gate'])
def test_resize_nonexistent_server(self):
@@ -186,12 +174,12 @@
self.create_test_server,
name=server_name)
+ @test.skip_because(bug="1208743")
@test.attr(type=['negative', 'gate'])
def test_create_with_invalid_network_uuid(self):
# Pass invalid network uuid while creating a server
networks = [{'fixed_ip': '10.0.1.1', 'uuid': 'a-b-c-d-e-f-g-h-i-j'}]
-
self.assertRaises(exceptions.BadRequest,
self.create_test_server,
networks=networks)
@@ -421,8 +409,7 @@
resp, server = self.client.get_server(self.server_id)
image_name = server['name'] + '-shelved'
- params = {'name': image_name}
- resp, images = self.images_client.list_images(params)
+ resp, images = self.images_client.image_list(name=image_name)
self.assertEqual(1, len(images))
self.assertEqual(image_name, images[0]['name'])
@@ -445,5 +432,5 @@
self.server_id)
-class ServersNegativeTestXML(ServersNegativeTestJSON):
+class ServersNegativeV3TestXML(ServersNegativeV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index 03e8469..c563259 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -22,7 +22,7 @@
LOG = logging.getLogger(__name__)
-class VolumeMultiBackendTest(base.BaseVolumeAdminTest):
+class VolumeMultiBackendTest(base.BaseVolumeV1AdminTest):
_interface = "json"
@classmethod
diff --git a/tempest/api/volume/admin/test_snapshots_actions.py b/tempest/api/volume/admin/test_snapshots_actions.py
index 5e838e5..03fbd33 100644
--- a/tempest/api/volume/admin/test_snapshots_actions.py
+++ b/tempest/api/volume/admin/test_snapshots_actions.py
@@ -15,12 +15,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api.volume.base import BaseVolumeAdminTest
+from tempest.api.volume import base
from tempest.common.utils import data_utils
from tempest.test import attr
-class SnapshotsActionsTest(BaseVolumeAdminTest):
+class SnapshotsActionsTest(base.BaseVolumeV1AdminTest):
_interface = "json"
@classmethod
diff --git a/tempest/api/volume/admin/test_volume_hosts.py b/tempest/api/volume/admin/test_volume_hosts.py
index e7d8c02..4f40d4a 100644
--- a/tempest/api/volume/admin/test_volume_hosts.py
+++ b/tempest/api/volume/admin/test_volume_hosts.py
@@ -19,7 +19,7 @@
from tempest.test import attr
-class VolumeHostsAdminTestsJSON(base.BaseVolumeAdminTest):
+class VolumeHostsAdminTestsJSON(base.BaseVolumeV1AdminTest):
_interface = "json"
@attr(type='gate')
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 5218f83..3a92e8d 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -15,13 +15,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api.volume.base import BaseVolumeTest
+from tempest.api.volume import base
from tempest.common.utils import data_utils
from tempest.services.volume.json.admin import volume_types_client
from tempest.test import attr
-class VolumeTypesTest(BaseVolumeTest):
+class VolumeTypesTest(base.BaseVolumeV1Test):
_interface = "json"
@classmethod
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs.py b/tempest/api/volume/admin/test_volume_types_extra_specs.py
index dbb75af..f0fba07 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs.py
@@ -20,7 +20,7 @@
from tempest.test import attr
-class VolumeTypesExtraSpecsTest(base.BaseVolumeAdminTest):
+class VolumeTypesExtraSpecsTest(base.BaseVolumeV1AdminTest):
_interface = "json"
@classmethod
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
index 8b5dce2..cf992f2 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
@@ -23,7 +23,7 @@
from tempest.test import attr
-class ExtraSpecsNegativeTest(base.BaseVolumeAdminTest):
+class ExtraSpecsNegativeTest(base.BaseVolumeV1AdminTest):
_interface = 'json'
@classmethod
diff --git a/tempest/api/volume/admin/test_volume_types_negative.py b/tempest/api/volume/admin/test_volume_types_negative.py
index 44725df..3832048 100644
--- a/tempest/api/volume/admin/test_volume_types_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_negative.py
@@ -22,7 +22,7 @@
from tempest.test import attr
-class VolumeTypesNegativeTest(base.BaseVolumeAdminTest):
+class VolumeTypesNegativeTest(base.BaseVolumeV1AdminTest):
_interface = 'json'
@attr(type='gate')
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index cb9ff11..941dc7e 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -15,12 +15,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api.volume.base import BaseVolumeAdminTest
+from tempest.api.volume import base
from tempest.common.utils import data_utils as utils
from tempest.test import attr
-class VolumesActionsTest(BaseVolumeAdminTest):
+class VolumesActionsTest(base.BaseVolumeV1AdminTest):
_interface = "json"
@classmethod
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index ba99309..d63fd8b 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -36,13 +36,12 @@
skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
raise cls.skipException(skip_msg)
- os = cls.get_client_manager()
+ cls.os = cls.get_client_manager()
- cls.os = os
- cls.volumes_client = os.volumes_client
- cls.snapshots_client = os.snapshots_client
- cls.servers_client = os.servers_client
- cls.volumes_extension_client = os.volumes_extension_client
+ cls.volumes_client = cls.os.volumes_client
+ cls.snapshots_client = cls.os.snapshots_client
+ cls.servers_client = cls.os.servers_client
+ cls.volumes_extension_client = cls.os.volumes_extension_client
cls.image_ref = cls.config.compute.image_ref
cls.flavor_ref = cls.config.compute.flavor_ref
cls.build_interval = cls.config.volume.build_interval
@@ -50,12 +49,6 @@
cls.snapshots = []
cls.volumes = []
- cls.volumes_client.keystone_auth(cls.os.username,
- cls.os.password,
- cls.os.auth_url,
- cls.volumes_client.service,
- cls.os.tenant_name)
-
@classmethod
def tearDownClass(cls):
cls.clear_snapshots()
@@ -130,11 +123,26 @@
time.sleep(self.build_interval)
-class BaseVolumeAdminTest(BaseVolumeTest):
+class BaseVolumeV1Test(BaseVolumeTest):
+ @classmethod
+ def setUpClass(cls):
+ if not cls.config.volume_feature_enabled.api_v1:
+ msg = "Volume API v1 not supported"
+ raise cls.skipException(msg)
+ super(BaseVolumeV1Test, cls).setUpClass()
+ cls.volumes_client = cls.os.volumes_client
+ cls.volumes_client.keystone_auth(cls.os.username,
+ cls.os.password,
+ cls.os.auth_url,
+ cls.volumes_client.service,
+ cls.os.tenant_name)
+
+
+class BaseVolumeV1AdminTest(BaseVolumeV1Test):
"""Base test case class for all Volume Admin API tests."""
@classmethod
def setUpClass(cls):
- super(BaseVolumeAdminTest, cls).setUpClass()
+ super(BaseVolumeV1AdminTest, cls).setUpClass()
cls.adm_user = cls.config.identity.admin_username
cls.adm_pass = cls.config.identity.admin_password
cls.adm_tenant = cls.config.identity.admin_tenant_name
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
index dacebf1..71e9f85 100644
--- a/tempest/api/volume/test_volume_transfers.py
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -15,13 +15,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api.volume.base import BaseVolumeTest
+from tempest.api.volume import base
from tempest import clients
from tempest.common.utils.data_utils import rand_name
from tempest.test import attr
-class VolumesTransfersTest(BaseVolumeTest):
+class VolumesTransfersTest(base.BaseVolumeV1Test):
_interface = "json"
@classmethod
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 8581d16..61f1bda 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -15,14 +15,14 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api.volume.base import BaseVolumeTest
+from tempest.api.volume import base
from tempest.common.utils import data_utils
from tempest.test import attr
from tempest.test import services
from tempest.test import stresstest
-class VolumesActionsTest(BaseVolumeTest):
+class VolumesActionsTest(base.BaseVolumeV1Test):
_interface = "json"
@classmethod
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 14120fe..6d1c25a 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -21,7 +21,7 @@
from tempest.test import services
-class VolumesGetTest(base.BaseVolumeTest):
+class VolumesGetTest(base.BaseVolumeV1Test):
_interface = "json"
@classmethod
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index 3c66eb8..c624a3a 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -27,7 +27,7 @@
VOLUME_FIELDS = ('id', 'display_name')
-class VolumesListTest(base.BaseVolumeTest):
+class VolumesListTest(base.BaseVolumeV1Test):
"""
This test creates a number of 1G volumes. To run successfully,
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index 928bd49..869aedb 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -23,7 +23,7 @@
from tempest.test import attr
-class VolumesNegativeTest(base.BaseVolumeTest):
+class VolumesNegativeTest(base.BaseVolumeV1Test):
_interface = 'json'
@classmethod
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 6c45c3d..4e57007 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -20,7 +20,7 @@
LOG = logging.getLogger(__name__)
-class VolumesSnapshotTest(base.BaseVolumeTest):
+class VolumesSnapshotTest(base.BaseVolumeV1Test):
_interface = "json"
@classmethod
diff --git a/tempest/api/volume/test_volumes_snapshots_negative.py b/tempest/api/volume/test_volumes_snapshots_negative.py
index 04a4774..0e4f5dc 100644
--- a/tempest/api/volume/test_volumes_snapshots_negative.py
+++ b/tempest/api/volume/test_volumes_snapshots_negative.py
@@ -20,7 +20,7 @@
from tempest.test import attr
-class VolumesSnapshotNegativeTest(base.BaseVolumeTest):
+class VolumesSnapshotNegativeTest(base.BaseVolumeV1Test):
_interface = "json"
@attr(type=['negative', 'gate'])
diff --git a/tempest/clients.py b/tempest/clients.py
index 3333b9b..1f2e1de 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -56,10 +56,13 @@
CertificatesV3ClientJSON
from tempest.services.compute.v3.json.extensions_client import \
ExtensionsV3ClientJSON
+from tempest.services.compute.v3.json.flavors_client import FlavorsV3ClientJSON
from tempest.services.compute.v3.json.hypervisor_client import \
HypervisorV3ClientJSON
from tempest.services.compute.v3.json.interfaces_client import \
InterfacesV3ClientJSON
+from tempest.services.compute.v3.json.keypairs_client import \
+ KeyPairsV3ClientJSON
from tempest.services.compute.v3.json.servers_client import \
ServersV3ClientJSON
from tempest.services.compute.v3.json.services_client import \
@@ -72,10 +75,12 @@
CertificatesV3ClientXML
from tempest.services.compute.v3.xml.extensions_client import \
ExtensionsV3ClientXML
+from tempest.services.compute.v3.xml.flavors_client import FlavorsV3ClientXML
from tempest.services.compute.v3.xml.hypervisor_client import \
HypervisorV3ClientXML
from tempest.services.compute.v3.xml.interfaces_client import \
InterfacesV3ClientXML
+from tempest.services.compute.v3.xml.keypairs_client import KeyPairsV3ClientXML
from tempest.services.compute.v3.xml.servers_client import ServersV3ClientXML
from tempest.services.compute.v3.xml.services_client import \
ServicesV3ClientXML
@@ -91,6 +96,7 @@
from tempest.services.compute.xml.flavors_client import FlavorsClientXML
from tempest.services.compute.xml.floating_ips_client import \
FloatingIPsClientXML
+from tempest.services.compute.xml.hosts_client import HostsClientXML
from tempest.services.compute.xml.hypervisor_client import HypervisorClientXML
from tempest.services.compute.xml.images_client import ImagesClientXML
from tempest.services.compute.xml.instance_usage_audit_log_client import \
@@ -220,9 +226,11 @@
self.servers_v3_client = ServersV3ClientXML(*client_args)
self.limits_client = LimitsClientXML(*client_args)
self.images_client = ImagesClientXML(*client_args)
+ self.keypairs_v3_client = KeyPairsV3ClientXML(*client_args)
self.keypairs_client = KeyPairsClientXML(*client_args)
self.quotas_client = QuotasClientXML(*client_args)
self.flavors_client = FlavorsClientXML(*client_args)
+ self.flavors_v3_client = FlavorsV3ClientXML(*client_args)
self.extensions_v3_client = ExtensionsV3ClientXML(*client_args)
self.extensions_client = ExtensionsClientXML(*client_args)
self.volumes_extensions_client = VolumesExtensionsClientXML(
@@ -252,6 +260,7 @@
*client_args)
self.tenant_usages_client = TenantUsagesClientXML(*client_args)
self.policy_client = PolicyClientXML(*client_args)
+ self.hosts_client = HostsClientXML(*client_args)
self.hypervisor_v3_client = HypervisorV3ClientXML(*client_args)
self.hypervisor_client = HypervisorClientXML(*client_args)
self.token_v3_client = V3TokenClientXML(*client_args)
@@ -275,9 +284,11 @@
self.servers_v3_client = ServersV3ClientJSON(*client_args)
self.limits_client = LimitsClientJSON(*client_args)
self.images_client = ImagesClientJSON(*client_args)
+ self.keypairs_v3_client = KeyPairsV3ClientJSON(*client_args)
self.keypairs_client = KeyPairsClientJSON(*client_args)
self.quotas_client = QuotasClientJSON(*client_args)
self.flavors_client = FlavorsClientJSON(*client_args)
+ self.flavors_v3_client = FlavorsV3ClientJSON(*client_args)
self.extensions_v3_client = ExtensionsV3ClientJSON(*client_args)
self.extensions_client = ExtensionsClientJSON(*client_args)
self.volumes_extensions_client = VolumesExtensionsClientJSON(
@@ -307,6 +318,7 @@
*client_args)
self.tenant_usages_client = TenantUsagesClientJSON(*client_args)
self.policy_client = PolicyClientJSON(*client_args)
+ self.hosts_client = HostsClientJSON(*client_args)
self.hypervisor_v3_client = HypervisorV3ClientJSON(*client_args)
self.hypervisor_client = HypervisorClientJSON(*client_args)
self.token_v3_client = V3TokenClientJSON(*client_args)
@@ -326,7 +338,6 @@
raise exceptions.InvalidConfiguration(msg)
# common clients
- self.hosts_client = HostsClientJSON(*client_args)
self.account_client = AccountClient(*client_args)
if CONF.service_available.glance:
self.image_client = ImageClientJSON(*client_args)
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index aedba15..ce620a8 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -29,7 +29,10 @@
"""Waits for a server to reach a given status."""
def _get_task_state(body):
- task_state = body.get('OS-EXT-STS:task_state', None)
+ if client.service == CONF.compute.catalog_v3_type:
+ task_state = body.get("os-extended-status:task_state", None)
+ else:
+ task_state = body.get('OS-EXT-STS:task_state', None)
return task_state
# NOTE(afazekas): UNKNOWN status possible on ERROR
diff --git a/tempest/config.py b/tempest/config.py
index 1247a8d..bf45b4b 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -377,6 +377,9 @@
default=['all'],
help='A list of enabled extensions with a special entry all '
'which indicates every extension is enabled'),
+ cfg.BoolOpt('api_v1',
+ default=True,
+ help="Is the v1 volume API enabled"),
]
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index d3d34d0..409fcc2 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -985,7 +985,7 @@
username = cls.config.identity.admin_username
password = cls.config.identity.admin_password
tenant_name = cls.config.identity.tenant_name
- return username, tenant_name, password
+ return username, password, tenant_name
def _load_template(self, base_file, file_name):
filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
diff --git a/tempest/services/compute/v3/json/flavors_client.py b/tempest/services/compute/v3/json/flavors_client.py
index 00d6f8a..e99ac91 100644
--- a/tempest/services/compute/v3/json/flavors_client.py
+++ b/tempest/services/compute/v3/json/flavors_client.py
@@ -21,12 +21,12 @@
from tempest.common.rest_client import RestClient
-class FlavorsClientJSON(RestClient):
+class FlavorsV3ClientJSON(RestClient):
def __init__(self, config, username, password, auth_url, tenant_name=None):
- super(FlavorsClientJSON, self).__init__(config, username, password,
- auth_url, tenant_name)
- self.service = self.config.compute.catalog_type
+ super(FlavorsV3ClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_v3_type
def list_flavors(self, params=None):
url = 'flavors'
@@ -67,7 +67,7 @@
if kwargs.get('rxtx'):
post_body['rxtx_factor'] = kwargs.get('rxtx')
if kwargs.get('is_public'):
- post_body['os-flavor-access:is_public'] = kwargs.get('is_public')
+ post_body['flavor-access:is_public'] = kwargs.get('is_public')
post_body = json.dumps({'flavor': post_body})
resp, body = self.post('flavors', post_body, self.headers)
@@ -91,27 +91,27 @@
def set_flavor_extra_spec(self, flavor_id, specs):
"""Sets extra Specs to the mentioned flavor."""
post_body = json.dumps({'extra_specs': specs})
- resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id,
+ resp, body = self.post('flavors/%s/flavor-extra-specs' % flavor_id,
post_body, self.headers)
body = json.loads(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)
+ resp, body = self.get('flavors/%s/flavor-extra-specs' % flavor_id)
body = json.loads(body)
return resp, body['extra_specs']
def get_flavor_extra_spec_with_key(self, flavor_id, key):
"""Gets extra Specs key-value of the mentioned flavor and key."""
- resp, body = self.get('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
- key))
+ resp, body = self.get('flavors/%s/flavor-extra-specs/%s' %
+ (str(flavor_id), key))
body = json.loads(body)
return resp, body
def update_flavor_extra_spec(self, flavor_id, key, **kwargs):
"""Update specified extra Specs of the mentioned flavor and key."""
- resp, body = self.put('flavors/%s/os-extra_specs/%s' %
+ resp, body = self.put('flavors/%s/flavor-extra-specs/%s' %
(flavor_id, key),
json.dumps(kwargs), self.headers)
body = json.loads(body)
@@ -119,12 +119,12 @@
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))
+ return self.delete('flavors/%s/flavor-extra-specs/%s' %
+ (str(flavor_id), key))
def list_flavor_access(self, flavor_id):
"""Gets flavor access information given the flavor id."""
- resp, body = self.get('flavors/%s/os-flavor-access' % flavor_id,
+ resp, body = self.get('flavors/%s/flavor-access' % flavor_id,
self.headers)
body = json.loads(body)
return resp, body['flavor_access']
@@ -132,8 +132,8 @@
def add_flavor_access(self, flavor_id, tenant_id):
"""Add flavor access for the specified tenant."""
post_body = {
- 'addTenantAccess': {
- 'tenant': tenant_id
+ 'add_tenant_access': {
+ 'tenant_id': tenant_id
}
}
post_body = json.dumps(post_body)
@@ -145,8 +145,8 @@
def remove_flavor_access(self, flavor_id, tenant_id):
"""Remove flavor access from the specified tenant."""
post_body = {
- 'removeTenantAccess': {
- 'tenant': tenant_id
+ 'remove_tenant_access': {
+ 'tenant_id': tenant_id
}
}
post_body = json.dumps(post_body)
diff --git a/tempest/services/compute/v3/json/keypairs_client.py b/tempest/services/compute/v3/json/keypairs_client.py
index 5e1900c..500aa0e 100644
--- a/tempest/services/compute/v3/json/keypairs_client.py
+++ b/tempest/services/compute/v3/json/keypairs_client.py
@@ -20,15 +20,15 @@
from tempest.common.rest_client import RestClient
-class KeyPairsClientJSON(RestClient):
+class KeyPairsV3ClientJSON(RestClient):
def __init__(self, config, username, password, auth_url, tenant_name=None):
- super(KeyPairsClientJSON, self).__init__(config, username, password,
- auth_url, tenant_name)
- self.service = self.config.compute.catalog_type
+ super(KeyPairsV3ClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_v3_type
def list_keypairs(self):
- resp, body = self.get("os-keypairs")
+ resp, body = self.get("keypairs")
body = json.loads(body)
# Each returned keypair is embedded within an unnecessary 'keypair'
# element which is a deviation from other resources like floating-ips,
@@ -38,7 +38,7 @@
return resp, body['keypairs']
def get_keypair(self, key_name):
- resp, body = self.get("os-keypairs/%s" % str(key_name))
+ resp, body = self.get("keypairs/%s" % str(key_name))
body = json.loads(body)
return resp, body['keypair']
@@ -47,10 +47,10 @@
if pub_key:
post_body['keypair']['public_key'] = pub_key
post_body = json.dumps(post_body)
- resp, body = self.post("os-keypairs",
+ resp, body = self.post("keypairs",
headers=self.headers, body=post_body)
body = json.loads(body)
return resp, body['keypair']
def delete_keypair(self, key_name):
- return self.delete("os-keypairs/%s" % str(key_name))
+ return self.delete("keypairs/%s" % str(key_name))
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
index a7fcc6d..a486801 100644
--- a/tempest/services/compute/v3/json/servers_client.py
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -64,12 +64,14 @@
'flavor_ref': flavor_ref
}
- for option in ['personality', 'admin_password', 'key_name',
- 'security_groups', 'networks',
+ for option in ['personality', 'admin_password', 'key_name', 'networks',
+ ('os-security-groups:security_groups',
+ 'security_groups'),
('os-user-data:user_data', 'user_data'),
('os-availability-zone:availability_zone',
'availability_zone'),
- 'access_ip_v4', 'access_ip_v6',
+ ('os-access-ips:access_ip_v4', 'access_ip_v4'),
+ ('os-access-ips:access_ip_v6', 'access_ip_v6'),
('os-multiple-create:min_count', 'min_count'),
('os-multiple-create:max_count', 'max_count'),
('metadata', 'meta'),
@@ -205,6 +207,13 @@
body = json.loads(body)[response_key]
return resp, body
+ def create_backup(self, server_id, backup_type, rotation, name):
+ """Backup a server instance."""
+ return self.action(server_id, "create_backup", None,
+ backup_type=backup_type,
+ rotation=rotation,
+ name=name)
+
def change_password(self, server_id, admin_password):
"""Changes the root password for the server."""
return self.action(server_id, 'change_password', None,
@@ -356,6 +365,14 @@
"""Resets the state of a server to active/error."""
return self.action(server_id, 'reset_state', None, state=state)
+ def shelve_server(self, server_id, **kwargs):
+ """Shelves the provided server."""
+ return self.action(server_id, 'shelve', None, **kwargs)
+
+ def unshelve_server(self, server_id, **kwargs):
+ """Un-shelves the provided server."""
+ return self.action(server_id, 'unshelve', None, **kwargs)
+
def get_console_output(self, server_id, length):
return self.action(server_id, 'get_console_output', 'output',
length=length)
@@ -388,3 +405,11 @@
(str(server_id), str(request_id)))
body = json.loads(body)
return resp, body['instance_action']
+
+ def force_delete_server(self, server_id, **kwargs):
+ """Force delete a server."""
+ return self.action(server_id, 'force_delete', None, **kwargs)
+
+ def restore_soft_deleted_server(self, server_id, **kwargs):
+ """Restore a soft-deleted server."""
+ return self.action(server_id, 'restore', None, **kwargs)
diff --git a/tempest/services/compute/v3/xml/flavors_client.py b/tempest/services/compute/v3/xml/flavors_client.py
index a1c74d9..04c21b4 100644
--- a/tempest/services/compute/v3/xml/flavors_client.py
+++ b/tempest/services/compute/v3/xml/flavors_client.py
@@ -24,21 +24,18 @@
from tempest.services.compute.xml.common import Element
from tempest.services.compute.xml.common import Text
from tempest.services.compute.xml.common import xml_to_json
-from tempest.services.compute.xml.common import XMLNS_11
+from tempest.services.compute.xml.common import XMLNS_V3
-
-XMLNS_OS_FLV_EXT_DATA = \
- "http://docs.openstack.org/compute/ext/flavor_extra_data/api/v1.1"
XMLNS_OS_FLV_ACCESS = \
- "http://docs.openstack.org/compute/ext/flavor_access/api/v2"
+ "http://docs.openstack.org/compute/core/flavor-access/api/v3"
-class FlavorsClientXML(RestClientXML):
+class FlavorsV3ClientXML(RestClientXML):
def __init__(self, config, username, password, auth_url, tenant_name=None):
- super(FlavorsClientXML, self).__init__(config, username, password,
- auth_url, tenant_name)
- self.service = self.config.compute.catalog_type
+ super(FlavorsV3ClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_v3_type
def _format_flavor(self, f):
flavor = {'links': []}
@@ -51,15 +48,12 @@
flavor['links'].append(v)
continue
- if k == '{%s}ephemeral' % XMLNS_OS_FLV_EXT_DATA:
- k = 'OS-FLV-EXT-DATA:ephemeral'
-
if k == '{%s}is_public' % XMLNS_OS_FLV_ACCESS:
- k = 'os-flavor-access:is_public'
+ k = 'flavor-access:is_public'
v = True if v == 'True' else False
if k == 'extra_specs':
- k = 'OS-FLV-WITH-EXT-SPECS:extra_specs'
+ k = 'flavor-extra-specs:extra_specs'
flavor[k] = dict(v)
continue
@@ -103,7 +97,7 @@
def create_flavor(self, name, ram, vcpus, disk, flavor_id, **kwargs):
"""Creates a new flavor or instance type."""
flavor = Element("flavor",
- xmlns=XMLNS_11,
+ xmlns=XMLNS_V3,
ram=ram,
vcpus=vcpus,
disk=disk,
@@ -114,13 +108,11 @@
if kwargs.get('swap'):
flavor.add_attr('swap', kwargs.get('swap'))
if kwargs.get('ephemeral'):
- flavor.add_attr('OS-FLV-EXT-DATA:ephemeral',
- kwargs.get('ephemeral'))
+ flavor.add_attr('ephemeral', kwargs.get('ephemeral'))
if kwargs.get('is_public'):
- flavor.add_attr('os-flavor-access:is_public',
+ flavor.add_attr('flavor-access:is_public',
kwargs.get('is_public'))
- flavor.add_attr('xmlns:OS-FLV-EXT-DATA', XMLNS_OS_FLV_EXT_DATA)
- flavor.add_attr('xmlns:os-flavor-access', XMLNS_OS_FLV_ACCESS)
+ flavor.add_attr('xmlns:flavor-access', XMLNS_OS_FLV_ACCESS)
resp, body = self.post('flavors', str(Document(flavor)), self.headers)
body = xml_to_json(etree.fromstring(body))
flavor = self._format_flavor(body)
@@ -145,21 +137,21 @@
extra_specs = Element("extra_specs")
for key in specs.keys():
extra_specs.add_attr(key, specs[key])
- resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id,
+ resp, body = self.post('flavors/%s/flavor-extra-specs' % flavor_id,
str(Document(extra_specs)), self.headers)
body = xml_to_json(etree.fromstring(body))
return resp, body
def get_flavor_extra_spec(self, flavor_id):
"""Gets extra Specs of the mentioned flavor."""
- resp, body = self.get('flavors/%s/os-extra_specs' % flavor_id,
+ resp, body = self.get('flavors/%s/flavor-extra-specs' % flavor_id,
self.headers)
body = xml_to_json(etree.fromstring(body))
return resp, body
def get_flavor_extra_spec_with_key(self, flavor_id, key):
"""Gets extra Specs key-value of the mentioned flavor and key."""
- resp, xml_body = self.get('flavors/%s/os-extra_specs/%s' %
+ resp, xml_body = self.get('flavors/%s/flavor-extra-specs/%s' %
(str(flavor_id), key), self.headers)
body = {}
element = etree.fromstring(xml_body)
@@ -176,7 +168,7 @@
value = Text(v)
element.append(value)
- resp, body = self.put('flavors/%s/os-extra_specs/%s' %
+ resp, body = self.put('flavors/%s/flavor-extra-specs/%s' %
(flavor_id, key),
str(doc), self.headers)
body = xml_to_json(etree.fromstring(body))
@@ -184,15 +176,15 @@
def unset_flavor_extra_spec(self, flavor_id, key):
"""Unsets an extra spec based on the mentioned flavor and key."""
- return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
- key))
+ return self.delete('flavors/%s/flavor-extra-specs/%s' %
+ (str(flavor_id), key))
def _parse_array_access(self, node):
return [xml_to_json(x) for x in node]
def list_flavor_access(self, flavor_id):
"""Gets flavor access information given the flavor id."""
- resp, body = self.get('flavors/%s/os-flavor-access' % str(flavor_id),
+ resp, body = self.get('flavors/%s/flavor-access' % str(flavor_id),
self.headers)
body = self._parse_array(etree.fromstring(body))
return resp, body
@@ -200,9 +192,9 @@
def add_flavor_access(self, flavor_id, tenant_id):
"""Add flavor access for the specified tenant."""
doc = Document()
- server = Element("addTenantAccess")
+ server = Element("add_tenant_access")
doc.append(server)
- server.add_attr("tenant", tenant_id)
+ server.add_attr("tenant_id", tenant_id)
resp, body = self.post('flavors/%s/action' % str(flavor_id),
str(doc), self.headers)
body = self._parse_array_access(etree.fromstring(body))
@@ -211,9 +203,9 @@
def remove_flavor_access(self, flavor_id, tenant_id):
"""Remove flavor access from the specified tenant."""
doc = Document()
- server = Element("removeTenantAccess")
+ server = Element("remove_tenant_access")
doc.append(server)
- server.add_attr("tenant", tenant_id)
+ server.add_attr("tenant_id", tenant_id)
resp, body = self.post('flavors/%s/action' % str(flavor_id),
str(doc), self.headers)
body = self._parse_array_access(etree.fromstring(body))
diff --git a/tempest/services/compute/v3/xml/keypairs_client.py b/tempest/services/compute/v3/xml/keypairs_client.py
index 0157245..d87daff 100644
--- a/tempest/services/compute/v3/xml/keypairs_client.py
+++ b/tempest/services/compute/v3/xml/keypairs_client.py
@@ -24,21 +24,21 @@
from tempest.services.compute.xml.common import xml_to_json
-class KeyPairsClientXML(RestClientXML):
+class KeyPairsV3ClientXML(RestClientXML):
def __init__(self, config, username, password, auth_url, tenant_name=None):
- super(KeyPairsClientXML, self).__init__(config, username, password,
- auth_url, tenant_name)
- self.service = self.config.compute.catalog_type
+ super(KeyPairsV3ClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_v3_type
def list_keypairs(self):
- resp, body = self.get("os-keypairs", self.headers)
+ resp, body = self.get("keypairs", self.headers)
node = etree.fromstring(body)
body = [{'keypair': xml_to_json(x)} for x in node.getchildren()]
return resp, body
def get_keypair(self, key_name):
- resp, body = self.get("os-keypairs/%s" % str(key_name), self.headers)
+ resp, body = self.get("keypairs/%s" % str(key_name), self.headers)
body = xml_to_json(etree.fromstring(body))
return resp, body
@@ -60,10 +60,10 @@
doc.append(keypair_element)
- resp, body = self.post("os-keypairs",
+ resp, body = self.post("keypairs",
headers=self.headers, body=str(doc))
body = xml_to_json(etree.fromstring(body))
return resp, body
def delete_keypair(self, key_name):
- return self.delete("os-keypairs/%s" % str(key_name))
+ return self.delete("keypairs/%s" % str(key_name))
diff --git a/tempest/services/compute/v3/xml/servers_client.py b/tempest/services/compute/v3/xml/servers_client.py
index 7af4161..c3381a3 100644
--- a/tempest/services/compute/v3/xml/servers_client.py
+++ b/tempest/services/compute/v3/xml/servers_client.py
@@ -214,6 +214,14 @@
"""Resets the state of a server to active/error."""
return self.action(server_id, 'reset_state', None, state=state)
+ def shelve_server(self, server_id, **kwargs):
+ """Shelves the provided server."""
+ return self.action(server_id, 'shelve', None, **kwargs)
+
+ def unshelve_server(self, server_id, **kwargs):
+ """Un-shelves the provided server."""
+ return self.action(server_id, 'unshelve', None, **kwargs)
+
def delete_server(self, server_id):
"""Deletes the given server."""
return self.delete("servers/%s" % str(server_id))
@@ -299,7 +307,17 @@
flavor_ref=flavor_ref,
image_ref=image_ref,
name=name)
- attrs = ["admin_password", "access_ip_v4", "access_ip_v6", "key_name",
+ attrs = ["admin_password", "key_name",
+ ('os-access-ips:access_ip_v4',
+ 'access_ip_v4',
+ 'xmlns:os-access-ips',
+ "http://docs.openstack.org/compute/ext/"
+ "os-access-ips/api/v3"),
+ ('os-access-ips:access_ip_v6',
+ 'access_ip_v6',
+ 'xmlns:os-access-ips',
+ "http://docs.openstack.org/compute/ext/"
+ "os-access-ips/api/v3"),
("os-user-data:user_data",
'user_data',
'xmlns:os-user-data',
@@ -346,7 +364,10 @@
server.add_attr(post_param, value)
if 'security_groups' in kwargs:
- secgroups = Element("security_groups")
+ server.add_attr("xmlns:os-security-groups",
+ "http://docs.openstack.org/compute/ext/"
+ "securitygroups/api/v3")
+ secgroups = Element("os-security-groups:security_groups")
server.append(secgroups)
for secgroup in kwargs['security_groups']:
s = Element("security_group", name=secgroup['name'])
@@ -441,6 +462,13 @@
body = xml_to_json(etree.fromstring(body))
return resp, body
+ def create_backup(self, server_id, backup_type, rotation, name):
+ """Backup a server instance."""
+ return self.action(server_id, "create_backup", None,
+ backup_type=backup_type,
+ rotation=rotation,
+ name=name)
+
def change_password(self, server_id, password):
return self.action(server_id, "change_password", None,
admin_password=password)
@@ -621,3 +649,11 @@
(server_id, request_id), self.headers)
body = xml_to_json(etree.fromstring(body))
return resp, body
+
+ def force_delete_server(self, server_id, **kwargs):
+ """Force delete a server."""
+ return self.action(server_id, 'force_delete', None, **kwargs)
+
+ def restore_soft_deleted_server(self, server_id, **kwargs):
+ """Restore a soft-deleted server."""
+ return self.action(server_id, 'restore', None, **kwargs)
diff --git a/tempest/services/compute/xml/hosts_client.py b/tempest/services/compute/xml/hosts_client.py
index f7d7b0a..519798e 100644
--- a/tempest/services/compute/xml/hosts_client.py
+++ b/tempest/services/compute/xml/hosts_client.py
@@ -47,18 +47,16 @@
resp, body = self.get("os-hosts/%s" % str(hostname), self.headers)
node = etree.fromstring(body)
- body = [xml_to_json(x) for x in node.getchildren()]
+ body = [xml_to_json(node)]
return resp, body
- def update_host(self, hostname, status=None, maintenance_mode=None,
- **kwargs):
+ def update_host(self, hostname, **kwargs):
"""Update a host."""
- request_body = Element(status=status,
- maintenance_mode=maintenance_mode)
+ request_body = Element("updates")
if kwargs:
- for k, v in kwargs.iteritem():
- request_body.add_attr(k, v)
+ for k, v in kwargs.iteritems():
+ request_body.append(Element(k, v))
resp, body = self.put("os-hosts/%s" % str(hostname),
str(Document(request_body)),
self.headers)
diff --git a/tempest/test.py b/tempest/test.py
index 56c0554..342846f 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -112,15 +112,24 @@
@param bug: bug number causing the test to skip
@param condition: optional condition to be True for the skip to have place
+ @param interface: skip the test if it is the same as self._interface
"""
def decorator(f):
@functools.wraps(f)
- def wrapper(*func_args, **func_kwargs):
- if "bug" in kwargs:
- if "condition" not in kwargs or kwargs["condition"] is True:
- msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
- raise testtools.TestCase.skipException(msg)
- return f(*func_args, **func_kwargs)
+ def wrapper(self, *func_args, **func_kwargs):
+ skip = False
+ if "condition" in kwargs:
+ if kwargs["condition"] is True:
+ skip = True
+ elif "interface" in kwargs:
+ if kwargs["interface"] == self._interface:
+ skip = True
+ else:
+ skip = True
+ if "bug" in kwargs and skip is True:
+ msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
+ raise testtools.TestCase.skipException(msg)
+ return f(self, *func_args, **func_kwargs)
return wrapper
return decorator