Merge "refactor: Prepare test_list_projects for pre-prov"
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index 6dd00d3..ce9bbb5 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -338,10 +338,18 @@
.. _2.32: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id29
+ * `2.36`_
+
+ .. _2.36: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#microversion
+
* `2.37`_
.. _2.37: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id34
+ * `2.39`_
+
+ .. _2.39: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id35
+
* `2.42`_
.. _2.42: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-ocata
diff --git a/releasenotes/notes/add-storyboard-in-skip-because-decorator-3e139aa8a4f7970f.yaml b/releasenotes/notes/add-storyboard-in-skip-because-decorator-3e139aa8a4f7970f.yaml
new file mode 100644
index 0000000..dd4a90b
--- /dev/null
+++ b/releasenotes/notes/add-storyboard-in-skip-because-decorator-3e139aa8a4f7970f.yaml
@@ -0,0 +1,17 @@
+---
+features:
+ - |
+ Add a new parameter called ``bug_type`` to
+ ``tempest.lib.decorators.related_bug`` and
+ ``tempest.lib.decorators.skip_because`` decorators, which accepts
+ 2 values:
+
+ * launchpad
+ * storyboard
+
+ This offers the possibility of tracking bugs related to tests using
+ launchpad or storyboard references. The default value is launchpad
+ for backward compatibility.
+
+ Passing in a non-digit ``bug`` value to either decorator will raise
+ a ``InvalidParam`` exception (previously ``ValueError``).
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index 57d3983..d8faa33 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -48,11 +48,11 @@
if (hyper['hypervisor_type'] ==
CONF.compute.hypervisor_type)]
- hosts_available = [hyper['service']['host'] for hyper in hypers
- if (hyper['state'] == 'up' and
- hyper['status'] == 'enabled')]
- if hosts_available:
- cls.host = hosts_available[0]
+ cls.hosts_available = [hyper['service']['host'] for hyper in hypers
+ if (hyper['state'] == 'up' and
+ hyper['status'] == 'enabled')]
+ if cls.hosts_available:
+ cls.host = cls.hosts_available[0]
else:
msg = "no available compute node found"
if CONF.compute.hypervisor_type:
@@ -206,11 +206,23 @@
az_name = data_utils.rand_name(self.az_name_prefix)
aggregate = self._create_test_aggregate(availability_zone=az_name)
- self.client.add_host(aggregate['id'], host=self.host)
- self.addCleanup(self.client.remove_host, aggregate['id'],
- host=self.host)
+ # Find a host that has not been added to other zone,
+ # for one host can't be added to different zones.
+ aggregates = self.client.list_aggregates()['aggregates']
+ hosts_in_zone = []
+ for v in aggregates:
+ if v['availability_zone']:
+ hosts_in_zone.extend(v['hosts'])
+ hosts = [v for v in self.hosts_available if v not in hosts_in_zone]
+ if not hosts:
+ raise self.skipException("All hosts are already in other zones, "
+ "so can't add host to aggregate.")
+ host = hosts[0]
+
+ self.client.add_host(aggregate['id'], host=host)
+ self.addCleanup(self.client.remove_host, aggregate['id'], host=host)
admin_servers_client = self.os_admin.servers_client
server = self.create_test_server(availability_zone=az_name,
wait_until='ACTIVE')
body = admin_servers_client.show_server(server['id'])['server']
- self.assertEqual(self.host, body['OS-EXT-SRV-ATTR:host'])
+ self.assertEqual(host, body['OS-EXT-SRV-ATTR:host'])
diff --git a/tempest/api/compute/admin/test_flavors_microversions.py b/tempest/api/compute/admin/test_flavors_microversions.py
index 9f014e6..31b9217 100644
--- a/tempest/api/compute/admin/test_flavors_microversions.py
+++ b/tempest/api/compute/admin/test_flavors_microversions.py
@@ -33,14 +33,14 @@
disk=10,
id=flavor_id)['id']
# Checking show API response schema
- self.flavors_client.show_flavor(new_flavor_id)['flavor']
+ self.flavors_client.show_flavor(new_flavor_id)
# Checking update API response schema
self.admin_flavors_client.update_flavor(new_flavor_id,
- description='new')['flavor']
+ description='new')
# Checking list details API response schema
- self.flavors_client.list_flavors(detail=True)['flavors']
+ self.flavors_client.list_flavors(detail=True)
# Checking list API response schema
- self.flavors_client.list_flavors()['flavors']
+ self.flavors_client.list_flavors()
class FlavorsV261TestJSON(FlavorsV255TestJSON):
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index bc38144..8350f7c 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -66,7 +66,8 @@
kwargs = dict()
block_migration = getattr(self, 'block_migration', None)
if self.block_migration is None:
- kwargs['disk_over_commit'] = False
+ if self.is_requested_microversion_compatible('2.24'):
+ kwargs['disk_over_commit'] = False
block_migration = (CONF.compute_feature_enabled.
block_migration_for_live_migration and
not volume_backed)
diff --git a/tempest/api/compute/admin/test_live_migration_negative.py b/tempest/api/compute/admin/test_live_migration_negative.py
index deabbc2..8327a3b 100644
--- a/tempest/api/compute/admin/test_live_migration_negative.py
+++ b/tempest/api/compute/admin/test_live_migration_negative.py
@@ -32,9 +32,10 @@
def _migrate_server_to(self, server_id, dest_host):
bmflm = CONF.compute_feature_enabled.block_migration_for_live_migration
- self.admin_servers_client.live_migrate_server(
- server_id, host=dest_host, block_migration=bmflm,
- disk_over_commit=False)
+ kwargs = dict(host=dest_host, block_migration=bmflm)
+ if self.is_requested_microversion_compatible('2.24'):
+ kwargs['disk_over_commit'] = False
+ self.admin_servers_client.live_migrate_server(server_id, **kwargs)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7fb7856e-ae92-44c9-861a-af62d7830bcb')
diff --git a/tempest/api/compute/admin/test_migrations.py b/tempest/api/compute/admin/test_migrations.py
index a6b71b2..e030575 100644
--- a/tempest/api/compute/admin/test_migrations.py
+++ b/tempest/api/compute/admin/test_migrations.py
@@ -106,7 +106,7 @@
'ACTIVE')
server = self.servers_client.show_server(server['id'])['server']
- self.assertEqual(flavor['id'], server['flavor']['id'])
+ self.assert_flavor_equal(flavor['id'], server['flavor'])
def _test_cold_migrate_server(self, revert=False):
if CONF.compute.min_compute_nodes < 2:
diff --git a/tempest/api/compute/admin/test_networks.py b/tempest/api/compute/admin/test_networks.py
index 87ce39d..99907a8 100644
--- a/tempest/api/compute/admin/test_networks.py
+++ b/tempest/api/compute/admin/test_networks.py
@@ -26,6 +26,7 @@
API docs:
https://developer.openstack.org/api-ref/compute/#networks-os-networks-deprecated
"""
+ max_microversion = '2.35'
@classmethod
def setup_clients(cls):
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index cdfc44a..170b2cc 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -176,7 +176,7 @@
self.assertEqual(self.s1_id, rebuilt_server['id'])
rebuilt_image_id = rebuilt_server['image']['id']
self.assertEqual(self.image_ref_alt, rebuilt_image_id)
- self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id'])
+ self.assert_flavor_equal(self.flavor_ref, rebuilt_server['flavor'])
waiters.wait_for_server_status(self.non_admin_client,
rebuilt_server['id'], 'ACTIVE',
raise_on_error=False)
diff --git a/tempest/api/compute/admin/test_services_negative.py b/tempest/api/compute/admin/test_services_negative.py
index 993c8ec..d264829 100644
--- a/tempest/api/compute/admin/test_services_negative.py
+++ b/tempest/api/compute/admin/test_services_negative.py
@@ -74,8 +74,6 @@
@classmethod
def resource_setup(cls):
super(ServicesAdminNegativeV253TestJSON, cls).resource_setup()
- # Nova returns 400 if `binary` is not nova-compute.
- cls.binary = 'nova-compute'
cls.fake_service_id = data_utils.rand_uuid()
@decorators.attr(type=['negative'])
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index d0c1973..7fbb994 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -428,21 +428,16 @@
except Exception:
LOG.exception('Failed to delete server %s', server_id)
- @classmethod
- def resize_server(cls, server_id, new_flavor_id, **kwargs):
+ def resize_server(self, server_id, new_flavor_id, **kwargs):
"""resize and confirm_resize an server, waits for it to be ACTIVE."""
- cls.servers_client.resize_server(server_id, new_flavor_id, **kwargs)
- waiters.wait_for_server_status(cls.servers_client, server_id,
+ self.servers_client.resize_server(server_id, new_flavor_id, **kwargs)
+ waiters.wait_for_server_status(self.servers_client, server_id,
'VERIFY_RESIZE')
- cls.servers_client.confirm_resize_server(server_id)
- waiters.wait_for_server_status(cls.servers_client, server_id, 'ACTIVE')
- server = cls.servers_client.show_server(server_id)['server']
- # Nova API > 2.46 no longer includes flavor.id
- if server['flavor'].get('id'):
- if new_flavor_id != server['flavor']['id']:
- msg = ('Flavor id of %s is not equal to new_flavor_id.'
- % server_id)
- raise lib_exc.TempestException(msg)
+ self.servers_client.confirm_resize_server(server_id)
+ waiters.wait_for_server_status(
+ self.servers_client, server_id, 'ACTIVE')
+ server = self.servers_client.show_server(server_id)['server']
+ self.assert_flavor_equal(new_flavor_id, server['flavor'])
@classmethod
def delete_volume(cls, volume_id):
@@ -561,6 +556,27 @@
volume['id'], 'in-use')
return attachment
+ def assert_flavor_equal(self, flavor_id, server_flavor):
+ """Check whether server_flavor equals to flavor.
+
+ :param flavor_id: flavor id
+ :param server_flavor: flavor info returned by show_server.
+ """
+ # Nova API > 2.46 no longer includes flavor.id, and schema check
+ # will cover whether 'id' should be in flavor
+ if server_flavor.get('id'):
+ msg = ('server flavor is not same as flavor!')
+ self.assertEqual(flavor_id, server_flavor['id'], msg)
+ else:
+ flavor = self.flavors_client.show_flavor(flavor_id)['flavor']
+ self.assertEqual(flavor['name'], server_flavor['original_name'],
+ "original_name in server flavor is not same as "
+ "flavor name!")
+ for key in ['ram', 'vcpus', 'disk']:
+ msg = ('attribute %s in server flavor is not same as '
+ 'flavor!' % key)
+ self.assertEqual(flavor[key], server_flavor[key], msg)
+
class BaseV2ComputeAdminTest(BaseV2ComputeTest):
"""Base test case class for Compute Admin API tests."""
diff --git a/tempest/api/compute/limits/test_absolute_limits.py b/tempest/api/compute/limits/test_absolute_limits.py
index 0585fec..8c2202e 100644
--- a/tempest/api/compute/limits/test_absolute_limits.py
+++ b/tempest/api/compute/limits/test_absolute_limits.py
@@ -18,6 +18,7 @@
class AbsoluteLimitsTestJSON(base.BaseV2ComputeTest):
+ max_microversion = '2.56'
@classmethod
def setup_clients(cls):
@@ -26,22 +27,14 @@
@decorators.idempotent_id('b54c66af-6ab6-4cf0-a9e5-a0cb58d75e0b')
def test_absLimits_get(self):
- # To check if all limits are present in the response
- limits = self.client.show_limits()['limits']
- absolute_limits = limits['absolute']
- expected_elements = ['maxImageMeta', 'maxPersonality',
- 'maxPersonalitySize',
- 'maxServerMeta', 'maxTotalCores',
- 'maxTotalFloatingIps', 'maxSecurityGroups',
- 'maxSecurityGroupRules', 'maxTotalInstances',
- 'maxTotalKeypairs', 'maxTotalRAMSize',
- 'maxServerGroups', 'maxServerGroupMembers',
- 'totalCoresUsed', 'totalFloatingIpsUsed',
- 'totalSecurityGroupsUsed', 'totalInstancesUsed',
- 'totalRAMUsed', 'totalServerGroupsUsed']
- # check whether all expected elements exist
- missing_elements =\
- [ele for ele in expected_elements if ele not in absolute_limits]
- self.assertEmpty(missing_elements,
- "Failed to find element %s in absolute limits list"
- % ', '.join(ele for ele in missing_elements))
+ # To check if all limits are present in the response (will be checked
+ # by schema)
+ self.client.show_limits()
+
+
+class AbsoluteLimitsV257TestJSON(base.BaseV2ComputeTest):
+ min_microversion = '2.57'
+ max_microversion = 'latest'
+
+ # NOTE(felipemonteiro): This class tests the Absolute Limits APIs
+ # response schema for the 2.57 microversion.
diff --git a/tempest/api/compute/limits/test_absolute_limits_negative.py b/tempest/api/compute/limits/test_absolute_limits_negative.py
index bef4eb5..500638a 100644
--- a/tempest/api/compute/limits/test_absolute_limits_negative.py
+++ b/tempest/api/compute/limits/test_absolute_limits_negative.py
@@ -33,15 +33,15 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('215cd465-d8ae-49c9-bf33-9c911913a5c8')
- def test_max_image_meta_exceed_limit(self):
- # We should not create vm with image meta over maxImageMeta limit
+ def test_max_metadata_exceed_limit(self):
+ # We should not create vm with metadata over maxServerMeta limit
# Get max limit value
limits = self.client.show_limits()['limits']
- max_meta = limits['absolute']['maxImageMeta']
+ max_meta = limits['absolute']['maxServerMeta']
# No point in running this test if there is no limit.
if max_meta == -1:
- raise self.skipException('no limit for maxImageMeta')
+ raise self.skipException('no limit for maxServerMeta')
# Create server should fail, since we are passing > metadata Limit!
max_meta_data = max_meta + 1
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index 122c4f5..4f0dbad 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -80,7 +80,7 @@
self.assertEqual("", self.server['image'])
else:
self.assertEqual(self.image_ref, self.server['image']['id'])
- self.assertEqual(self.flavor_ref, self.server['flavor']['id'])
+ self.assert_flavor_equal(self.flavor_ref, self.server['flavor'])
self.assertEqual(self.meta, self.server['metadata'])
@decorators.attr(type='smoke')
diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
index 5d9bf48..40681cb 100644
--- a/tempest/api/compute/servers/test_device_tagging.py
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -382,5 +382,8 @@
waiters.wait_for_interface_detach(self.interfaces_client,
server['id'],
interface['port_id'])
- self.verify_metadata_from_api(server, ssh_client,
- self.verify_empty_devices)
+ # FIXME(mriedem): The assertion that the tagged devices are removed
+ # from the metadata for the server is being skipped until bug 1775947
+ # is fixed.
+ # self.verify_metadata_from_api(server, ssh_client,
+ # self.verify_empty_devices)
diff --git a/tempest/api/compute/servers/test_novnc.py b/tempest/api/compute/servers/test_novnc.py
index 1dfd0f9..3e44b56 100644
--- a/tempest/api/compute/servers/test_novnc.py
+++ b/tempest/api/compute/servers/test_novnc.py
@@ -58,6 +58,9 @@
def resource_setup(cls):
super(NoVNCConsoleTestJSON, cls).resource_setup()
cls.server = cls.create_test_server(wait_until="ACTIVE")
+ cls.use_get_remote_console = False
+ if not cls.is_requested_microversion_compatible('2.5'):
+ cls.use_get_remote_console = True
def _validate_novnc_html(self, vnc_url):
"""Verify we can connect to novnc and get back the javascript."""
@@ -170,8 +173,13 @@
@decorators.idempotent_id('c640fdff-8ab4-45a4-a5d8-7e6146cbd0dc')
def test_novnc(self):
- body = self.client.get_vnc_console(self.server['id'],
- type='novnc')['console']
+ if self.use_get_remote_console:
+ body = self.client.get_remote_console(
+ self.server['id'], console_type='novnc',
+ protocol='vnc')['remote_console']
+ else:
+ body = self.client.get_vnc_console(self.server['id'],
+ type='novnc')['console']
self.assertEqual('novnc', body['type'])
# Do the initial HTTP Request to novncproxy to get the NoVNC JavaScript
self._validate_novnc_html(body['url'])
@@ -184,8 +192,13 @@
@decorators.idempotent_id('f9c79937-addc-4aaa-9e0e-841eef02aeb7')
def test_novnc_bad_token(self):
- body = self.client.get_vnc_console(self.server['id'],
- type='novnc')['console']
+ if self.use_get_remote_console:
+ body = self.client.get_remote_console(
+ self.server['id'], console_type='novnc',
+ protocol='vnc')['remote_console']
+ else:
+ body = self.client.get_vnc_console(self.server['id'],
+ type='novnc')['console']
self.assertEqual('novnc', body['type'])
# Do the WebSockify HTTP Request to novncproxy with a bad token
url = body['url'].replace('token=', 'token=bad')
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 350e8ba..896abbb 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -197,7 +197,7 @@
self.assertEqual(self.server_id, rebuilt_server['id'])
rebuilt_image_id = rebuilt_server['image']['id']
self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id))
- self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id'])
+ self.assert_flavor_equal(self.flavor_ref, rebuilt_server['flavor'])
# Verify the server properties after the rebuild completes
waiters.wait_for_server_status(self.client,
@@ -251,7 +251,7 @@
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'])
+ self.assert_flavor_equal(self.flavor_ref, rebuilt_server['flavor'])
# Verify the server properties after the rebuild completes
waiters.wait_for_server_status(self.client,
@@ -303,7 +303,7 @@
expected_status)
server = self.client.show_server(server_id)['server']
- self.assertEqual(self.flavor_ref_alt, server['flavor']['id'])
+ self.assert_flavor_equal(self.flavor_ref_alt, server['flavor'])
if stop:
# NOTE(mriedem): tearDown requires the server to be started.
@@ -367,7 +367,7 @@
waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
server = self.client.show_server(self.server_id)['server']
- self.assertEqual(self.flavor_ref, server['flavor']['id'])
+ self.assert_flavor_equal(self.flavor_ref, server['flavor'])
@decorators.idempotent_id('fbbf075f-a812-4022-bc5c-ccb8047eef12')
@decorators.related_bug('1737599')
@@ -527,10 +527,10 @@
def _get_output(self):
output = self.client.get_console_output(
- self.server_id, length=10)['output']
+ self.server_id, length=3)['output']
self.assertTrue(output, "Console output was empty.")
lines = len(output.split('\n'))
- self.assertEqual(lines, 10)
+ self.assertEqual(lines, 3)
@decorators.idempotent_id('4b8867e6-fffa-4d54-b1d1-6fdda57be2f3')
@testtools.skipUnless(CONF.compute_feature_enabled.console_output,
@@ -561,8 +561,8 @@
# NOTE: This test tries to get full length console log, and the
# length should be bigger than the one of test_get_console_output.
- self.assertGreater(lines, 10, "Cannot get enough console log "
- "length. (lines: %s)" % lines)
+ self.assertGreater(lines, 3, "Cannot get enough console log "
+ "length. (lines: %s)" % lines)
self.wait_for(_check_full_length_console_log)
@@ -690,8 +690,13 @@
# Get the VNC console of type 'novnc' and 'xvpvnc'
console_types = ['novnc', 'xvpvnc']
for console_type in console_types:
- body = self.client.get_vnc_console(self.server_id,
- type=console_type)['console']
+ if self.is_requested_microversion_compatible('2.5'):
+ body = self.client.get_vnc_console(
+ self.server_id, type=console_type)['console']
+ else:
+ body = self.client.get_remote_console(
+ self.server_id, console_type=console_type,
+ protocol='vnc')['remote_console']
self.assertEqual(console_type, body['type'])
self.assertNotEqual('', body['url'])
self._validate_url(body['url'])
diff --git a/tempest/api/compute/servers/test_server_group.py b/tempest/api/compute/servers/test_server_group.py
index 5286c8f..1b7cb96 100644
--- a/tempest/api/compute/servers/test_server_group.py
+++ b/tempest/api/compute/servers/test_server_group.py
@@ -47,8 +47,16 @@
super(ServerGroupTestJSON, cls).resource_setup()
cls.policy = ['affinity']
- cls.created_server_group = cls.create_test_server_group(
- policy=cls.policy)
+ def setUp(self):
+ super(ServerGroupTestJSON, self).setUp()
+ # TODO(zhufl): After microversion 2.13 project_id and user_id are
+ # added to the body of server_group, and microversion is not used
+ # in resource_setup for now, so we should create server group in setUp
+ # in order to use the same microversion as in testcases till
+ # microversion support in resource_setup is fulfilled.
+ if not hasattr(self, 'created_server_group'):
+ self.__class__.created_server_group = \
+ self.create_test_server_group(policy=self.policy)
def _create_server_group(self, name, policy):
# create the test server-group with given policy
diff --git a/tempest/api/compute/test_networks.py b/tempest/api/compute/test_networks.py
index b8c79d7..76131e2 100644
--- a/tempest/api/compute/test_networks.py
+++ b/tempest/api/compute/test_networks.py
@@ -20,6 +20,8 @@
class ComputeNetworksTest(base.BaseV2ComputeTest):
+ max_microversion = '2.35'
+
@classmethod
def skip_checks(cls):
super(ComputeNetworksTest, cls).skip_checks()
diff --git a/tempest/api/volume/admin/test_volume_services_negative.py b/tempest/api/volume/admin/test_volume_services_negative.py
index 6f3dbc6..3a863a1 100644
--- a/tempest/api/volume/admin/test_volume_services_negative.py
+++ b/tempest/api/volume/admin/test_volume_services_negative.py
@@ -23,10 +23,9 @@
@classmethod
def resource_setup(cls):
super(VolumeServicesNegativeTest, cls).resource_setup()
- cls.services = cls.admin_volume_services_client.list_services()[
- 'services']
- cls.host = cls.services[0]['host']
- cls.binary = cls.services[0]['binary']
+ services = cls.admin_volume_services_client.list_services()['services']
+ cls.host = services[0]['host']
+ cls.binary = services[0]['binary']
@decorators.attr(type='negative')
@decorators.idempotent_id('3246ce65-ba70-4159-aa3b-082c28e4b484')
diff --git a/tempest/lib/api_schema/response/compute/v2_1/server_groups.py b/tempest/lib/api_schema/response/compute/v2_1/server_groups.py
new file mode 100644
index 0000000..01db20b
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_1/server_groups.py
@@ -0,0 +1,65 @@
+# Copyright 2017 NTT Corporation. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+common_server_group = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'name': {'type': 'string'},
+ 'policies': {
+ 'type': 'array',
+ 'items': {'type': 'string'}
+ },
+ # 'members' attribute contains the array of instance's UUID of
+ # instances present in server group
+ 'members': {
+ 'type': 'array',
+ 'items': {'type': 'string'}
+ },
+ 'metadata': {'type': 'object'}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'name', 'policies', 'members', 'metadata']
+}
+
+create_show_server_group = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server_group': common_server_group
+ },
+ 'additionalProperties': False,
+ 'required': ['server_group']
+ }
+}
+
+delete_server_group = {
+ 'status_code': [204]
+}
+
+list_server_groups = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server_groups': {
+ 'type': 'array',
+ 'items': common_server_group
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['server_groups']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_1/servers.py b/tempest/lib/api_schema/response/compute/v2_1/servers.py
index 2954de0..3300298 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/servers.py
@@ -345,58 +345,6 @@
}
}
-common_server_group = {
- 'type': 'object',
- 'properties': {
- 'id': {'type': 'string'},
- 'name': {'type': 'string'},
- 'policies': {
- 'type': 'array',
- 'items': {'type': 'string'}
- },
- # 'members' attribute contains the array of instance's UUID of
- # instances present in server group
- 'members': {
- 'type': 'array',
- 'items': {'type': 'string'}
- },
- 'metadata': {'type': 'object'}
- },
- 'additionalProperties': False,
- 'required': ['id', 'name', 'policies', 'members', 'metadata']
-}
-
-create_show_server_group = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'server_group': common_server_group
- },
- 'additionalProperties': False,
- 'required': ['server_group']
- }
-}
-
-delete_server_group = {
- 'status_code': [204]
-}
-
-list_server_groups = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'server_groups': {
- 'type': 'array',
- 'items': common_server_group
- }
- },
- 'additionalProperties': False,
- 'required': ['server_groups']
- }
-}
-
instance_actions = {
'type': 'object',
'properties': {
@@ -430,8 +378,9 @@
'traceback': {'type': ['string', 'null']}
},
'additionalProperties': False,
- 'required': ['event', 'start_time', 'finish_time', 'result',
- 'traceback']
+ # NOTE(zhufl): events.traceback can only be seen by admin users
+ # with default policy.json, so it shouldn't be a required field.
+ 'required': ['event', 'start_time', 'finish_time', 'result']
}
}
diff --git a/tempest/lib/api_schema/response/compute/v2_13/servers.py b/tempest/lib/api_schema/response/compute/v2_13/server_groups.py
similarity index 74%
rename from tempest/lib/api_schema/response/compute/v2_13/servers.py
rename to tempest/lib/api_schema/response/compute/v2_13/server_groups.py
index a90f3e4..2b59e38 100644
--- a/tempest/lib/api_schema/response/compute/v2_13/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_13/server_groups.py
@@ -14,21 +14,22 @@
import copy
-from tempest.lib.api_schema.response.compute.v2_1 import servers
+from tempest.lib.api_schema.response.compute.v2_1 import server_groups
-common_server_group = copy.deepcopy(servers.common_server_group)
+common_server_group = copy.deepcopy(server_groups.common_server_group)
common_server_group['properties']['project_id'] = {'type': 'string'}
common_server_group['properties']['user_id'] = {'type': 'string'}
common_server_group['required'].append('project_id')
common_server_group['required'].append('user_id')
-create_show_server_group = copy.deepcopy(servers.create_show_server_group)
+create_show_server_group = copy.deepcopy(
+ server_groups.create_show_server_group)
create_show_server_group['response_body']['properties'][
'server_group'] = common_server_group
-delete_server_group = copy.deepcopy(servers.delete_server_group)
+delete_server_group = copy.deepcopy(server_groups.delete_server_group)
-list_server_groups = copy.deepcopy(servers.list_server_groups)
+list_server_groups = copy.deepcopy(server_groups.list_server_groups)
list_server_groups['response_body']['properties']['server_groups'][
'items'] = common_server_group
diff --git a/tempest/lib/api_schema/response/compute/v2_36/__init__.py b/tempest/lib/api_schema/response/compute/v2_36/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_36/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_36/limits.py b/tempest/lib/api_schema/response/compute/v2_36/limits.py
new file mode 100644
index 0000000..8e94690
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_36/limits.py
@@ -0,0 +1,35 @@
+# Copyright 2018 ZTE 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.lib.api_schema.response.compute.v2_1 import limits as limitv21
+
+# Compute microversion 2.36:
+# remove attributes in get_limit:
+# 'maxSecurityGroupRules',
+# 'maxSecurityGroups',
+# 'maxTotalFloatingIps',
+# 'totalFloatingIpsUsed',
+# 'totalSecurityGroupsUsed'
+
+get_limit = copy.deepcopy(limitv21.get_limit)
+
+for item in ['maxSecurityGroupRules', 'maxSecurityGroups',
+ 'maxTotalFloatingIps', 'totalFloatingIpsUsed',
+ 'totalSecurityGroupsUsed']:
+ get_limit['response_body']['properties']['limits']['properties'][
+ 'absolute']['properties'].pop(item)
+ get_limit['response_body']['properties']['limits']['properties'][
+ 'absolute']['required'].remove(item)
diff --git a/tempest/lib/api_schema/response/compute/v2_39/__init__.py b/tempest/lib/api_schema/response/compute/v2_39/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_39/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_39/limits.py b/tempest/lib/api_schema/response/compute/v2_39/limits.py
new file mode 100644
index 0000000..3df6616
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_39/limits.py
@@ -0,0 +1,29 @@
+# Copyright 2018 ZTE 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.lib.api_schema.response.compute.v2_36 import limits as limitv236
+
+# Compute microversion 2.39:
+# remove attributes in get_limit:
+# 'maxImageMeta'
+
+get_limit = copy.deepcopy(limitv236.get_limit)
+
+get_limit['response_body']['properties']['limits']['properties']['absolute'][
+ 'properties'].pop('maxImageMeta')
+
+get_limit['response_body']['properties']['limits']['properties']['absolute'][
+ 'required'].remove('maxImageMeta')
diff --git a/tempest/lib/api_schema/response/compute/v2_47/servers.py b/tempest/lib/api_schema/response/compute/v2_47/servers.py
index 5d6d4c3..5922f76 100644
--- a/tempest/lib/api_schema/response/compute/v2_47/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_47/servers.py
@@ -53,3 +53,14 @@
servers226.rebuild_server_with_admin_pass)
rebuild_server_with_admin_pass['response_body']['properties']['server'][
'properties'].update({'flavor': flavor})
+
+# NOTE(zhufl): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.26 ***
+list_tags = copy.deepcopy(servers226.list_tags)
+update_all_tags = copy.deepcopy(servers226.update_all_tags)
+delete_all_tags = copy.deepcopy(servers226.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers226.check_tag_existence)
+update_tag = copy.deepcopy(servers226.update_tag)
+delete_tag = copy.deepcopy(servers226.delete_tag)
diff --git a/tempest/lib/api_schema/response/compute/v2_48/servers.py b/tempest/lib/api_schema/response/compute/v2_48/servers.py
index 5904758..02b00d6 100644
--- a/tempest/lib/api_schema/response/compute/v2_48/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_48/servers.py
@@ -113,3 +113,14 @@
}
get_server = copy.deepcopy(servers247.get_server)
+
+# NOTE(zhufl): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.26 ***
+list_tags = copy.deepcopy(servers247.list_tags)
+update_all_tags = copy.deepcopy(servers247.update_all_tags)
+delete_all_tags = copy.deepcopy(servers247.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers247.check_tag_existence)
+update_tag = copy.deepcopy(servers247.update_tag)
+delete_tag = copy.deepcopy(servers247.delete_tag)
diff --git a/tempest/lib/api_schema/response/compute/v2_54/servers.py b/tempest/lib/api_schema/response/compute/v2_54/servers.py
index c084696..e264186 100644
--- a/tempest/lib/api_schema/response/compute/v2_54/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_54/servers.py
@@ -39,11 +39,18 @@
rebuild_server_with_admin_pass['response_body']['properties']['server'][
'required'].append('key_name')
-# ****** Schemas unchanged in microversion 2.54 since microversion 2.47 ***
-
# NOTE(gmann): Below are the unchanged schema in this microversion. We need
# to keep this schema in this file to have the generic way to select the
# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged in microversion 2.54 since microversion 2.47 ***
get_server = copy.deepcopy(servers247.get_server)
list_servers_detail = copy.deepcopy(servers247.list_servers_detail)
update_server = copy.deepcopy(servers247.update_server)
+
+# ****** Schemas unchanged since microversion 2.26 ***
+list_tags = copy.deepcopy(servers247.list_tags)
+update_all_tags = copy.deepcopy(servers247.update_all_tags)
+delete_all_tags = copy.deepcopy(servers247.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers247.check_tag_existence)
+update_tag = copy.deepcopy(servers247.update_tag)
+delete_tag = copy.deepcopy(servers247.delete_tag)
diff --git a/tempest/lib/api_schema/response/compute/v2_57/limits.py b/tempest/lib/api_schema/response/compute/v2_57/limits.py
new file mode 100644
index 0000000..dcb8b3d
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_57/limits.py
@@ -0,0 +1,30 @@
+# Copyright 2018 ZTE 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.lib.api_schema.response.compute.v2_39 import limits as limitv239
+
+# Compute microversion 2.57:
+# remove attributes in get_limit:
+# 'maxPersonality',
+# 'maxPersonalitySize'
+
+get_limit = copy.deepcopy(limitv239.get_limit)
+
+for item in ['maxPersonality', 'maxPersonalitySize']:
+ get_limit['response_body']['properties']['limits']['properties'][
+ 'absolute']['properties'].pop(item)
+ get_limit['response_body']['properties']['limits']['properties'][
+ 'absolute']['required'].remove(item)
diff --git a/tempest/lib/api_schema/response/compute/v2_57/servers.py b/tempest/lib/api_schema/response/compute/v2_57/servers.py
index ed1ca7d..d7de5fd 100644
--- a/tempest/lib/api_schema/response/compute/v2_57/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_57/servers.py
@@ -43,11 +43,18 @@
rebuild_server_with_admin_pass['response_body']['properties']['server'][
'required'].append('user_data')
-# ****** Schemas unchanged in microversion 2.57 since microversion 2.54 ***
-
# NOTE(gmann): Below are the unchanged schema in this microversion. We need
-# to keeo this schema in this file to have the generic way to select the
+# to keep this schema in this file to have the generic way to select the
# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged in microversion 2.57 since microversion 2.54 ***
get_server = copy.deepcopy(servers254.get_server)
list_servers_detail = copy.deepcopy(servers254.list_servers_detail)
update_server = copy.deepcopy(servers254.update_server)
+
+# ****** Schemas unchanged since microversion 2.26 ***
+list_tags = copy.deepcopy(servers254.list_tags)
+update_all_tags = copy.deepcopy(servers254.update_all_tags)
+delete_all_tags = copy.deepcopy(servers254.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers254.check_tag_existence)
+update_tag = copy.deepcopy(servers254.update_tag)
+delete_tag = copy.deepcopy(servers254.delete_tag)
diff --git a/tempest/lib/api_schema/response/compute/v2_63/servers.py b/tempest/lib/api_schema/response/compute/v2_63/servers.py
index 5cdaf54..6a20890 100644
--- a/tempest/lib/api_schema/response/compute/v2_63/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_63/servers.py
@@ -63,3 +63,14 @@
'properties'].update({'trusted_image_certificates': trusted_certs})
get_server['response_body']['properties']['server'][
'required'].append('trusted_image_certificates')
+
+# NOTE(zhufl): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.26 ***
+list_tags = copy.deepcopy(servers257.list_tags)
+update_all_tags = copy.deepcopy(servers257.update_all_tags)
+delete_all_tags = copy.deepcopy(servers257.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers257.check_tag_existence)
+update_tag = copy.deepcopy(servers257.update_tag)
+delete_tag = copy.deepcopy(servers257.delete_tag)
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
index e99dd24..b399aa0 100644
--- a/tempest/lib/decorators.py
+++ b/tempest/lib/decorators.py
@@ -19,39 +19,83 @@
import six
import testtools
+from tempest.lib import exceptions as lib_exc
+
LOG = logging.getLogger(__name__)
+_SUPPORTED_BUG_TYPES = {
+ 'launchpad': 'https://launchpad.net/bugs/%s',
+ 'storyboard': 'https://storyboard.openstack.org/#!/story/%s',
+}
+
+
+def _validate_bug_and_bug_type(bug, bug_type):
+ """Validates ``bug`` and ``bug_type`` values.
+
+ :param bug: bug number causing the test to skip (launchpad or storyboard)
+ :param bug_type: 'launchpad' or 'storyboard', default 'launchpad'
+ :raises: InvalidParam if ``bug`` is not a digit or ``bug_type`` is not
+ a valid value
+ """
+ if not bug.isdigit():
+ invalid_param = '%s must be a valid %s number' % (bug, bug_type)
+ raise lib_exc.InvalidParam(invalid_param=invalid_param)
+ if bug_type not in _SUPPORTED_BUG_TYPES:
+ invalid_param = 'bug_type "%s" must be one of: %s' % (
+ bug_type, ', '.join(_SUPPORTED_BUG_TYPES.keys()))
+ raise lib_exc.InvalidParam(invalid_param=invalid_param)
+
+
+def _get_bug_url(bug, bug_type='launchpad'):
+ """Get the bug URL based on the ``bug_type`` and ``bug``
+
+ :param bug: The launchpad/storyboard bug number causing the test
+ :param bug_type: 'launchpad' or 'storyboard', default 'launchpad'
+ :returns: Bug URL corresponding to ``bug_type`` value
+ """
+ _validate_bug_and_bug_type(bug, bug_type)
+ return _SUPPORTED_BUG_TYPES[bug_type] % bug
+
def skip_because(*args, **kwargs):
"""A decorator useful to skip tests hitting known bugs
- @param bug: bug number causing the test to skip
- @param condition: optional condition to be True for the skip to have place
+ ``bug`` must be a number and ``condition`` must be true for the test to
+ skip.
+
+ :param bug: bug number causing the test to skip (launchpad or storyboard)
+ :param bug_type: 'launchpad' or 'storyboard', default 'launchpad'
+ :param condition: optional condition to be True for the skip to have place
+ :raises: testtools.TestCase.skipException if ``condition`` is True and
+ ``bug`` is included
"""
def decorator(f):
@functools.wraps(f)
def wrapper(*func_args, **func_kwargs):
skip = False
+ msg = ''
if "condition" in kwargs:
if kwargs["condition"] is True:
skip = True
else:
skip = True
if "bug" in kwargs and skip is True:
- if not kwargs['bug'].isdigit():
- raise ValueError('bug must be a valid bug number')
- msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
+ bug = kwargs['bug']
+ bug_type = kwargs.get('bug_type', 'launchpad')
+ bug_url = _get_bug_url(bug, bug_type)
+ msg = "Skipped until bug: %s is resolved." % bug_url
raise testtools.TestCase.skipException(msg)
return f(*func_args, **func_kwargs)
return wrapper
return decorator
-def related_bug(bug, status_code=None):
- """A decorator useful to know solutions from launchpad bug reports
+def related_bug(bug, status_code=None, bug_type='launchpad'):
+ """A decorator useful to know solutions from launchpad/storyboard reports
- @param bug: The launchpad bug number causing the test
- @param status_code: The status code related to the bug report
+ :param bug: The launchpad/storyboard bug number causing the test bug
+ :param bug_type: 'launchpad' or 'storyboard', default 'launchpad'
+ :param status_code: The status code related to the bug report
"""
def decorator(f):
@functools.wraps(f)
@@ -61,9 +105,10 @@
except Exception as exc:
exc_status_code = getattr(exc, 'status_code', None)
if status_code is None or status_code == exc_status_code:
- LOG.error('Hints: This test was made for the bug %s. '
- 'The failure could be related to '
- 'https://launchpad.net/bugs/%s', bug, bug)
+ if bug:
+ LOG.error('Hints: This test was made for the bug_type '
+ '%s. The failure could be related to '
+ '%s', bug, _get_bug_url(bug, bug_type))
raise exc
return wrapper
return decorator
diff --git a/tempest/lib/services/compute/limits_client.py b/tempest/lib/services/compute/limits_client.py
index efe9889..9af80c4 100644
--- a/tempest/lib/services/compute/limits_client.py
+++ b/tempest/lib/services/compute/limits_client.py
@@ -15,15 +15,25 @@
from oslo_serialization import jsonutils as json
-from tempest.lib.api_schema.response.compute.v2_1 import limits as schema
+from tempest.lib.api_schema.response.compute.v2_1 import limits as schemav21
+from tempest.lib.api_schema.response.compute.v2_36 import limits as schemav236
+from tempest.lib.api_schema.response.compute.v2_39 import limits as schemav239
+from tempest.lib.api_schema.response.compute.v2_57 import limits as schemav257
from tempest.lib.common import rest_client
from tempest.lib.services.compute import base_compute_client
class LimitsClient(base_compute_client.BaseComputeClient):
+ schema_versions_info = [
+ {'min': None, 'max': '2.35', 'schema': schemav21},
+ {'min': '2.36', 'max': '2.38', 'schema': schemav236},
+ {'min': '2.39', 'max': '2.56', 'schema': schemav239},
+ {'min': '2.57', 'max': None, 'schema': schemav257}]
+
def show_limits(self):
resp, body = self.get("limits")
body = json.loads(body)
+ schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.get_limit, resp, body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/compute/server_groups_client.py b/tempest/lib/services/compute/server_groups_client.py
index 03cd645..0d440d5 100644
--- a/tempest/lib/services/compute/server_groups_client.py
+++ b/tempest/lib/services/compute/server_groups_client.py
@@ -16,8 +16,10 @@
from oslo_serialization import jsonutils as json
-from tempest.lib.api_schema.response.compute.v2_1 import servers as schema
-from tempest.lib.api_schema.response.compute.v2_13 import servers as schemav213
+from tempest.lib.api_schema.response.compute.v2_1 import server_groups \
+ as schema
+from tempest.lib.api_schema.response.compute.v2_13 import server_groups \
+ as schemav213
from tempest.lib.common import rest_client
from tempest.lib.services.compute import base_compute_client
diff --git a/tempest/lib/services/image/v2/images_client.py b/tempest/lib/services/image/v2/images_client.py
index ed6df47..3c38dba 100644
--- a/tempest/lib/services/image/v2/images_client.py
+++ b/tempest/lib/services/image/v2/images_client.py
@@ -32,7 +32,7 @@
For a full list of available parameters, please refer to the official
API reference:
- http://developer.openstack.org/api-ref/image/v2/index.html#update-an-image
+ https://developer.openstack.org/api-ref/image/v2/#update-image
"""
data = json.dumps(patch)
headers = {"Content-Type": "application/openstack-images-v2.0"
@@ -47,7 +47,7 @@
For a full list of available parameters, please refer to the official
API reference:
- http://developer.openstack.org/api-ref/image/v2/index.html#create-an-image
+ https://developer.openstack.org/api-ref/image/v2/#create-image
"""
data = json.dumps(kwargs)
resp, body = self.post('images', data)
@@ -84,7 +84,7 @@
For a full list of available parameters, please refer to the official
API reference:
- http://developer.openstack.org/api-ref/image/v2/#delete-an-image
+ https://developer.openstack.org/api-ref/image/v2/#delete-image
"""
url = 'images/%s' % image_id
resp, _ = self.delete(url)
@@ -96,7 +96,7 @@
For a full list of available parameters, please refer to the official
API reference:
- http://developer.openstack.org/api-ref/image/v2/#show-images
+ https://developer.openstack.org/api-ref/image/v2/#list-images
"""
url = 'images'
@@ -113,7 +113,7 @@
For a full list of available parameters, please refer to the official
API reference:
- http://developer.openstack.org/api-ref/image/v2/#show-image-details
+ https://developer.openstack.org/api-ref/image/v2/#show-image
"""
url = 'images/%s' % image_id
resp, body = self.get(url)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 9db7f92..be52eef 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -94,6 +94,10 @@
if not client:
client = self.ports_client
name = data_utils.rand_name(self.__class__.__name__)
+ if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
+ kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
+ if CONF.network.port_profile and 'binding:profile' not in kwargs:
+ kwargs['binding:profile'] = CONF.network.port_profile
result = client.create_port(
name=name,
network_id=network_id,
@@ -642,9 +646,10 @@
return floating_ip
def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
- private_key=None):
+ private_key=None, server=None):
ssh_client = self.get_remote_client(ip_address,
- private_key=private_key)
+ private_key=private_key,
+ server=server)
if dev_name is not None:
ssh_client.make_fs(dev_name)
ssh_client.exec_command('sudo mount /dev/%s %s' % (dev_name,
@@ -658,9 +663,10 @@
return timestamp
def get_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
- private_key=None):
+ private_key=None, server=None):
ssh_client = self.get_remote_client(ip_address,
- private_key=private_key)
+ private_key=private_key,
+ server=server)
if dev_name is not None:
ssh_client.mount(dev_name, mount_path)
timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index b0e4669..7452ee6 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -197,7 +197,14 @@
'VERIFY_RESIZE')
self.servers_client.confirm_resize_server(server['id'])
server = self.servers_client.show_server(server['id'])['server']
- self.assertEqual(resize_flavor, server['flavor']['id'])
+ # Nova API > 2.46 no longer includes flavor.id, and schema check
+ # will cover whether 'id' should be in flavor
+ if server['flavor'].get('id'):
+ self.assertEqual(resize_flavor, server['flavor']['id'])
+ else:
+ flavor = self.flavors_client.show_flavor(resize_flavor)['flavor']
+ for key in ['original_name', 'ram', 'vcpus', 'disk']:
+ self.assertEqual(flavor[key], server['flavor'][key])
self._wait_server_status_and_check_network_connectivity(
server, keypair, floating_ip)
diff --git a/tempest/scenario/test_shelve_instance.py b/tempest/scenario/test_shelve_instance.py
index 68f18d1..d6b6d14 100644
--- a/tempest/scenario/test_shelve_instance.py
+++ b/tempest/scenario/test_shelve_instance.py
@@ -63,7 +63,8 @@
instance_ip = self.get_server_ip(server)
timestamp = self.create_timestamp(instance_ip,
- private_key=keypair['private_key'])
+ private_key=keypair['private_key'],
+ server=server)
# Prevent bug #1257594 from coming back
# Unshelve used to boot the instance with the original image, not
@@ -71,7 +72,8 @@
self._shelve_then_unshelve_server(server)
timestamp2 = self.get_timestamp(instance_ip,
- private_key=keypair['private_key'])
+ private_key=keypair['private_key'],
+ server=server)
self.assertEqual(timestamp, timestamp2)
@decorators.attr(type='slow')
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index b51a781..a33d4d4 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -57,7 +57,8 @@
instance_ip = self.get_server_ip(server)
timestamp = self.create_timestamp(instance_ip,
- private_key=keypair['private_key'])
+ private_key=keypair['private_key'],
+ server=server)
# snapshot the instance
snapshot_image = self.create_server_snapshot(server=server)
@@ -71,5 +72,6 @@
# check the existence of the timestamp file in the second instance
server_from_snapshot_ip = self.get_server_ip(server_from_snapshot)
timestamp2 = self.get_timestamp(server_from_snapshot_ip,
- private_key=keypair['private_key'])
+ private_key=keypair['private_key'],
+ server=server_from_snapshot)
self.assertEqual(timestamp, timestamp2)
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index ef369d6..2782119 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -96,7 +96,8 @@
keypair['private_key'])
timestamp = self.create_timestamp(ip_for_server,
CONF.compute.volume_device_name,
- private_key=keypair['private_key'])
+ private_key=keypair['private_key'],
+ server=server)
self.nova_volume_detach(server, volume)
# snapshot the volume
@@ -126,5 +127,6 @@
# check the existence of the timestamp file in the volume2
timestamp2 = self.get_timestamp(ip_for_snapshot,
CONF.compute.volume_device_name,
- private_key=keypair['private_key'])
+ private_key=keypair['private_key'],
+ server=server_from_snapshot)
self.assertEqual(timestamp, timestamp2)
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 1564f25..79c2d14 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -108,7 +108,8 @@
LOG.info("Setting timestamp in instance %s", instance_1st)
ip_instance_1st = self.get_server_ip(instance_1st)
timestamp = self.create_timestamp(ip_instance_1st,
- private_key=keypair['private_key'])
+ private_key=keypair['private_key'],
+ server=instance_1st)
# delete instance
LOG.info("Deleting first instance: %s", instance_1st)
@@ -126,7 +127,8 @@
LOG.info("Getting timestamp in instance %s", instance_2nd)
ip_instance_2nd = self.get_server_ip(instance_2nd)
timestamp2 = self.get_timestamp(ip_instance_2nd,
- private_key=keypair['private_key'])
+ private_key=keypair['private_key'],
+ server=instance_2nd)
self.assertEqual(timestamp, timestamp2)
# snapshot a volume
@@ -150,7 +152,8 @@
server_from_snapshot)
server_from_snapshot_ip = self.get_server_ip(server_from_snapshot)
timestamp3 = self.get_timestamp(server_from_snapshot_ip,
- private_key=keypair['private_key'])
+ private_key=keypair['private_key'],
+ server=server_from_snapshot)
self.assertEqual(timestamp, timestamp3)
@decorators.idempotent_id('05795fb2-b2a7-4c9f-8fac-ff25aedb1489')
diff --git a/tempest/scenario/test_volume_migrate_attached.py b/tempest/scenario/test_volume_migrate_attached.py
index ff7996a..4624249 100644
--- a/tempest/scenario/test_volume_migrate_attached.py
+++ b/tempest/scenario/test_volume_migrate_attached.py
@@ -114,7 +114,8 @@
LOG.info("Setting timestamp in instance %s", instance['id'])
ip_instance = self.get_server_ip(instance)
timestamp = self.create_timestamp(ip_instance,
- private_key=keypair['private_key'])
+ private_key=keypair['private_key'],
+ server=instance)
# retype volume with migration from backend #1 to backend #2
LOG.info("Retyping Volume %s to new type %s", volume_origin['id'],
@@ -125,5 +126,6 @@
LOG.info("Getting timestamp in postmigrated instance %s",
instance['id'])
timestamp2 = self.get_timestamp(ip_instance,
- private_key=keypair['private_key'])
+ private_key=keypair['private_key'],
+ server=instance)
self.assertEqual(timestamp, timestamp2)
diff --git a/tempest/tests/lib/services/volume/v2/test_volumes_client.py b/tempest/tests/lib/services/volume/v2/test_volumes_client.py
deleted file mode 100644
index d7b042e..0000000
--- a/tempest/tests/lib/services/volume/v2/test_volumes_client.py
+++ /dev/null
@@ -1,127 +0,0 @@
-# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.services.volume.v2 import volumes_client
-from tempest.tests.lib import fake_auth_provider
-from tempest.tests.lib.services import base
-
-
-class TestVolumesClient(base.BaseServiceTest):
-
- FAKE_VOLUME_METADATA_ITEM = {
- "meta": {
- "key1": "value1"
- }
- }
-
- FAKE_VOLUME_IMAGE_METADATA = {
- "metadata": {
- "container_format": "bare",
- "min_ram": "0",
- "disk_format": "raw",
- "image_name": "xly-ubuntu16-server",
- "image_id": "3e087b0c-10c5-4255-b147-6e8e9dbad6fc",
- "checksum": "008f5d22fe3cb825d714da79607a90f9",
- "min_disk": "0",
- "size": "8589934592"
- }
- }
-
- def setUp(self):
- super(TestVolumesClient, self).setUp()
- fake_auth = fake_auth_provider.FakeAuthProvider()
- self.client = volumes_client.VolumesClient(fake_auth,
- 'volume',
- 'regionOne')
-
- def _test_retype_volume(self, bytes_body=False):
- kwargs = {
- "new_type": "dedup-tier-replication",
- "migration_policy": "never"
- }
-
- self.check_service_client_function(
- self.client.retype_volume,
- 'tempest.lib.common.rest_client.RestClient.post',
- {},
- to_utf=bytes_body,
- status=202,
- volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
- **kwargs
- )
-
- def _test_force_detach_volume(self, bytes_body=False):
- kwargs = {
- 'attachment_id': '6980e295-920f-412e-b189-05c50d605acd',
- 'connector': {
- 'initiator': 'iqn.2017-04.org.fake:01'
- }
- }
-
- self.check_service_client_function(
- self.client.force_detach_volume,
- 'tempest.lib.common.rest_client.RestClient.post',
- {},
- to_utf=bytes_body,
- status=202,
- volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
- **kwargs
- )
-
- def _test_show_volume_metadata_item(self, bytes_body=False):
- self.check_service_client_function(
- self.client.show_volume_metadata_item,
- 'tempest.lib.common.rest_client.RestClient.get',
- self.FAKE_VOLUME_METADATA_ITEM,
- to_utf=bytes_body,
- volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
- id="key1")
-
- def _test_show_volume_image_metadata(self, bytes_body=False):
- fake_volume_id = "a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8"
- self.check_service_client_function(
- self.client.show_volume_image_metadata,
- 'tempest.lib.common.rest_client.RestClient.post',
- self.FAKE_VOLUME_IMAGE_METADATA,
- to_utf=bytes_body,
- mock_args=['volumes/%s/action' % fake_volume_id,
- json.dumps({"os-show_image_metadata": {}})],
- volume_id=fake_volume_id)
-
- def test_force_detach_volume_with_str_body(self):
- self._test_force_detach_volume()
-
- def test_force_detach_volume_with_bytes_body(self):
- self._test_force_detach_volume(bytes_body=True)
-
- def test_show_volume_metadata_item_with_str_body(self):
- self._test_show_volume_metadata_item()
-
- def test_show_volume_metadata_item_with_bytes_body(self):
- self._test_show_volume_metadata_item(bytes_body=True)
-
- def test_show_volume_image_metadata_with_str_body(self):
- self._test_show_volume_image_metadata()
-
- def test_show_volume_image_metadata_with_bytes_body(self):
- self._test_show_volume_image_metadata(bytes_body=True)
-
- def test_retype_volume_with_str_body(self):
- self._test_retype_volume()
-
- def test_retype_volume_with_bytes_body(self):
- self._test_retype_volume(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v3/test_volumes_client.py b/tempest/tests/lib/services/volume/v3/test_volumes_client.py
index a515fd3..1250536 100644
--- a/tempest/tests/lib/services/volume/v3/test_volumes_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_volumes_client.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+from oslo_serialization import jsonutils as json
+
from tempest.lib.services.volume.v3 import volumes_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
@@ -27,6 +29,25 @@
}
}
+ FAKE_VOLUME_METADATA_ITEM = {
+ "meta": {
+ "key1": "value1"
+ }
+ }
+
+ FAKE_VOLUME_IMAGE_METADATA = {
+ "metadata": {
+ "container_format": "bare",
+ "min_ram": "0",
+ "disk_format": "raw",
+ "image_name": "xly-ubuntu16-server",
+ "image_id": "3e087b0c-10c5-4255-b147-6e8e9dbad6fc",
+ "checksum": "008f5d22fe3cb825d714da79607a90f9",
+ "min_disk": "0",
+ "size": "8589934592"
+ }
+ }
+
def setUp(self):
super(TestVolumesClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -34,6 +55,60 @@
'volume',
'regionOne')
+ def _test_retype_volume(self, bytes_body=False):
+ kwargs = {
+ "new_type": "dedup-tier-replication",
+ "migration_policy": "never"
+ }
+
+ self.check_service_client_function(
+ self.client.retype_volume,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ to_utf=bytes_body,
+ status=202,
+ volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
+ **kwargs
+ )
+
+ def _test_force_detach_volume(self, bytes_body=False):
+ kwargs = {
+ 'attachment_id': '6980e295-920f-412e-b189-05c50d605acd',
+ 'connector': {
+ 'initiator': 'iqn.2017-04.org.fake:01'
+ }
+ }
+
+ self.check_service_client_function(
+ self.client.force_detach_volume,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ to_utf=bytes_body,
+ status=202,
+ volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
+ **kwargs
+ )
+
+ def _test_show_volume_metadata_item(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_volume_metadata_item,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_VOLUME_METADATA_ITEM,
+ to_utf=bytes_body,
+ volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
+ id="key1")
+
+ def _test_show_volume_image_metadata(self, bytes_body=False):
+ fake_volume_id = "a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8"
+ self.check_service_client_function(
+ self.client.show_volume_image_metadata,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_VOLUME_IMAGE_METADATA,
+ to_utf=bytes_body,
+ mock_args=['volumes/%s/action' % fake_volume_id,
+ json.dumps({"os-show_image_metadata": {}})],
+ volume_id=fake_volume_id)
+
def _test_show_volume_summary(self, bytes_body=False):
self.check_service_client_function(
self.client.show_volume_summary,
@@ -41,6 +116,30 @@
self.FAKE_VOLUME_SUMMARY,
bytes_body)
+ def test_force_detach_volume_with_str_body(self):
+ self._test_force_detach_volume()
+
+ def test_force_detach_volume_with_bytes_body(self):
+ self._test_force_detach_volume(bytes_body=True)
+
+ def test_show_volume_metadata_item_with_str_body(self):
+ self._test_show_volume_metadata_item()
+
+ def test_show_volume_metadata_item_with_bytes_body(self):
+ self._test_show_volume_metadata_item(bytes_body=True)
+
+ def test_show_volume_image_metadata_with_str_body(self):
+ self._test_show_volume_image_metadata()
+
+ def test_show_volume_image_metadata_with_bytes_body(self):
+ self._test_show_volume_image_metadata(bytes_body=True)
+
+ def test_retype_volume_with_str_body(self):
+ self._test_retype_volume()
+
+ def test_retype_volume_with_bytes_body(self):
+ self._test_retype_volume(bytes_body=True)
+
def test_show_volume_summary_with_str_body(self):
self._test_show_volume_summary()
diff --git a/tempest/tests/lib/test_decorators.py b/tempest/tests/lib/test_decorators.py
index ed0eea3..0b1a599 100644
--- a/tempest/tests/lib/test_decorators.py
+++ b/tempest/tests/lib/test_decorators.py
@@ -19,6 +19,7 @@
from tempest.lib import base as test
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
from tempest.tests import base
@@ -62,21 +63,40 @@
t = TestFoo('test_bar')
if expected_to_skip:
- self.assertRaises(testtools.TestCase.skipException, t.test_bar)
+ e = self.assertRaises(testtools.TestCase.skipException, t.test_bar)
+ bug = decorator_args['bug']
+ bug_type = decorator_args.get('bug_type', 'launchpad')
+ self.assertRegex(
+ str(e),
+ r'Skipped until bug\: %s.*' % decorators._get_bug_url(
+ bug, bug_type)
+ )
else:
# assert that test_bar returned 0
self.assertEqual(TestFoo('test_bar').test_bar(), 0)
- def test_skip_because_bug(self):
+ def test_skip_because_launchpad_bug(self):
self._test_skip_because_helper(bug='12345')
- def test_skip_because_bug_and_condition_true(self):
+ def test_skip_because_launchpad_bug_and_condition_true(self):
self._test_skip_because_helper(bug='12348', condition=True)
- def test_skip_because_bug_and_condition_false(self):
+ def test_skip_because_launchpad_bug_and_condition_false(self):
self._test_skip_because_helper(expected_to_skip=False,
bug='12349', condition=False)
+ def test_skip_because_storyboard_bug(self):
+ self._test_skip_because_helper(bug='1992', bug_type='storyboard')
+
+ def test_skip_because_storyboard_bug_and_condition_true(self):
+ self._test_skip_because_helper(bug='1992', bug_type='storyboard',
+ condition=True)
+
+ def test_skip_because_storyboard_bug_and_condition_false(self):
+ self._test_skip_because_helper(expected_to_skip=False,
+ bug='1992', bug_type='storyboard',
+ condition=False)
+
def test_skip_because_bug_without_bug_never_skips(self):
"""Never skip without a bug parameter."""
self._test_skip_because_helper(expected_to_skip=False,
@@ -84,8 +104,8 @@
self._test_skip_because_helper(expected_to_skip=False)
def test_skip_because_invalid_bug_number(self):
- """Raise ValueError if with an invalid bug number"""
- self.assertRaises(ValueError, self._test_skip_because_helper,
+ """Raise InvalidParam if with an invalid bug number"""
+ self.assertRaises(lib_exc.InvalidParam, self._test_skip_because_helper,
bug='critical_bug')
@@ -126,6 +146,13 @@
class TestRelatedBugDecorator(base.TestCase):
+
+ def _get_my_exception(self):
+ class MyException(Exception):
+ def __init__(self, status_code):
+ self.status_code = status_code
+ return MyException
+
def test_relatedbug_when_no_exception(self):
f = mock.Mock()
sentinel = object()
@@ -137,10 +164,9 @@
test_foo(sentinel)
f.assert_called_once_with(sentinel)
- def test_relatedbug_when_exception(self):
- class MyException(Exception):
- def __init__(self, status_code):
- self.status_code = status_code
+ def test_relatedbug_when_exception_with_launchpad_bug_type(self):
+ """Validate related_bug decorator with bug_type == 'launchpad'"""
+ MyException = self._get_my_exception()
def f(self):
raise MyException(status_code=500)
@@ -152,4 +178,53 @@
with mock.patch.object(decorators.LOG, 'error') as m_error:
self.assertRaises(MyException, test_foo, object())
- m_error.assert_called_once_with(mock.ANY, '1234', '1234')
+ m_error.assert_called_once_with(
+ mock.ANY, '1234', 'https://launchpad.net/bugs/1234')
+
+ def test_relatedbug_when_exception_with_storyboard_bug_type(self):
+ """Validate related_bug decorator with bug_type == 'storyboard'"""
+ MyException = self._get_my_exception()
+
+ def f(self):
+ raise MyException(status_code=500)
+
+ @decorators.related_bug(bug="1234", status_code=500,
+ bug_type='storyboard')
+ def test_foo(self):
+ f(self)
+
+ with mock.patch.object(decorators.LOG, 'error') as m_error:
+ self.assertRaises(MyException, test_foo, object())
+
+ m_error.assert_called_once_with(
+ mock.ANY, '1234', 'https://storyboard.openstack.org/#!/story/1234')
+
+ def test_relatedbug_when_exception_invalid_bug_type(self):
+ """Check related_bug decorator raises exc when bug_type is not valid"""
+ MyException = self._get_my_exception()
+
+ def f(self):
+ raise MyException(status_code=500)
+
+ @decorators.related_bug(bug="1234", status_code=500,
+ bug_type=mock.sentinel.invalid)
+ def test_foo(self):
+ f(self)
+
+ with mock.patch.object(decorators.LOG, 'error'):
+ self.assertRaises(lib_exc.InvalidParam, test_foo, object())
+
+ def test_relatedbug_when_exception_invalid_bug_number(self):
+ """Check related_bug decorator raises exc when bug_number != digit"""
+ MyException = self._get_my_exception()
+
+ def f(self):
+ raise MyException(status_code=500)
+
+ @decorators.related_bug(bug="not a digit", status_code=500,
+ bug_type='launchpad')
+ def test_foo(self):
+ f(self)
+
+ with mock.patch.object(decorators.LOG, 'error'):
+ self.assertRaises(lib_exc.InvalidParam, test_foo, object())