blob: ce6cd609217c84465f5482fb00a77e8966f7de90 [file] [log] [blame]
ZhiQiang Fan39f97222013-09-20 04:49:44 +08001# Copyright 2012 OpenStack Foundation
Jay Pipes13b479b2012-06-11 14:52:27 -04002# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
David Kranzcf0040c2012-06-26 09:46:56 -040016import time
Jay Pipesf38eaac2012-06-21 13:37:35 -040017
Doug Hellmann583ce2c2015-03-11 14:55:46 +000018from oslo_log import log as logging
Matthew Treinish01472ff2015-02-20 17:26:52 -050019
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000020from tempest.common import compute
Ken'ichi Ohmichi8b9c7802015-07-08 05:57:37 +000021from tempest.common import waiters
Matthew Treinishb0a78fc2014-01-29 16:49:12 +000022from tempest import config
Sean Dague20e98612016-01-06 14:33:28 -050023from tempest import exceptions
Sergey Nikitin8654e5b2017-06-04 22:09:56 +040024from tempest.lib.common import api_version_request
Ghanshyam1f47cf92016-02-25 04:57:18 +090025from tempest.lib.common import api_version_utils
Ken'ichi Ohmichi757833a2017-03-10 10:30:30 -080026from tempest.lib.common.utils import data_utils
Jordan Pittier9e227c52016-02-09 14:35:18 +010027from tempest.lib.common.utils import test_utils
Andrea Frittoli (andreaf)db9672e2016-02-23 14:07:24 -050028from tempest.lib import exceptions as lib_exc
Attila Fazekasdc216422013-01-29 15:12:14 +010029import tempest.test
Jay Pipesf38eaac2012-06-21 13:37:35 -040030
Matthew Treinishb0a78fc2014-01-29 16:49:12 +000031CONF = config.CONF
Tiago Melloeda03b52012-08-22 23:47:29 -030032
Jay Pipesf38eaac2012-06-21 13:37:35 -040033LOG = logging.getLogger(__name__)
Daryl Walleckc7251962012-03-12 17:26:54 -050034
35
Ken'ichi Ohmichi4d237e72015-11-05 06:32:33 +000036class BaseV2ComputeTest(api_version_utils.BaseMicroversionTest,
37 tempest.test.BaseTestCase):
Sean Daguef237ccb2013-01-04 15:19:14 -050038 """Base test case class for all Compute API tests."""
Daryl Walleckc7251962012-03-12 17:26:54 -050039
Attila Fazekas430dae32013-10-17 15:19:32 +020040 force_tenant_isolation = False
Eric Friedbfaa50f2020-01-09 12:04:54 -060041 # Set this to True in subclasses to create a default network. See
42 # https://bugs.launchpad.net/tempest/+bug/1844568
43 create_default_network = False
Chris Yeoh8a79b9d2013-01-18 19:32:47 +103044
Andrea Frittolib21de6c2015-02-06 20:12:38 +000045 # TODO(andreaf) We should care also for the alt_manager here
46 # but only once client lazy load in the manager is done
47 credentials = ['primary']
48
Jay Pipesf38eaac2012-06-21 13:37:35 -040049 @classmethod
Emily Hugenbruche7991d92014-12-12 16:53:36 +000050 def skip_checks(cls):
Ken'ichi Ohmichi02a8ccd2015-11-05 06:05:29 +000051 super(BaseV2ComputeTest, cls).skip_checks()
Matthew Treinishf8ff3582015-08-25 12:41:56 -040052 if not CONF.service_available.nova:
53 raise cls.skipException("Nova is not available")
Lee Yarwood4b95d4b2020-01-15 10:49:54 +000054 api_version_utils.check_skip_with_microversion(
55 cls.min_microversion, cls.max_microversion,
56 CONF.compute.min_microversion, CONF.compute.max_microversion)
57 api_version_utils.check_skip_with_microversion(
58 cls.volume_min_microversion, cls.volume_max_microversion,
59 CONF.volume.min_microversion, CONF.volume.max_microversion)
60 api_version_utils.check_skip_with_microversion(
61 cls.placement_min_microversion, cls.placement_max_microversion,
62 CONF.placement.min_microversion, CONF.placement.max_microversion)
Jay Pipesf38eaac2012-06-21 13:37:35 -040063
Emily Hugenbruche7991d92014-12-12 16:53:36 +000064 @classmethod
65 def setup_credentials(cls):
Eric Friedbfaa50f2020-01-09 12:04:54 -060066 # Setting network=True, subnet=True creates a default network
67 cls.set_network_resources(
68 network=cls.create_default_network,
Lee Yarwooddb2f5612021-11-12 13:03:57 +000069 subnet=cls.create_default_network,
70 router=cls.create_default_network,
71 dhcp=cls.create_default_network)
Ken'ichi Ohmichi02a8ccd2015-11-05 06:05:29 +000072 super(BaseV2ComputeTest, cls).setup_credentials()
Daryl Walleckc7251962012-03-12 17:26:54 -050073
Emily Hugenbruche7991d92014-12-12 16:53:36 +000074 @classmethod
75 def setup_clients(cls):
Ken'ichi Ohmichi02a8ccd2015-11-05 06:05:29 +000076 super(BaseV2ComputeTest, cls).setup_clients()
Jordan Pittier8160d312017-04-18 11:52:23 +020077 cls.servers_client = cls.os_primary.servers_client
78 cls.server_groups_client = cls.os_primary.server_groups_client
79 cls.flavors_client = cls.os_primary.flavors_client
80 cls.compute_images_client = cls.os_primary.compute_images_client
81 cls.extensions_client = cls.os_primary.extensions_client
82 cls.floating_ip_pools_client = cls.os_primary.floating_ip_pools_client
83 cls.floating_ips_client = cls.os_primary.compute_floating_ips_client
84 cls.keypairs_client = cls.os_primary.keypairs_client
John Warren5cdbf422016-01-05 12:42:43 -050085 cls.security_group_rules_client = (
Jordan Pittier8160d312017-04-18 11:52:23 +020086 cls.os_primary.compute_security_group_rules_client)
87 cls.security_groups_client =\
88 cls.os_primary.compute_security_groups_client
89 cls.quotas_client = cls.os_primary.quotas_client
90 cls.compute_networks_client = cls.os_primary.compute_networks_client
91 cls.limits_client = cls.os_primary.limits_client
92 cls.volumes_extensions_client =\
93 cls.os_primary.volumes_extensions_client
94 cls.snapshots_extensions_client =\
95 cls.os_primary.snapshots_extensions_client
96 cls.interfaces_client = cls.os_primary.interfaces_client
97 cls.fixed_ips_client = cls.os_primary.fixed_ips_client
98 cls.availability_zone_client = cls.os_primary.availability_zone_client
99 cls.agents_client = cls.os_primary.agents_client
100 cls.aggregates_client = cls.os_primary.aggregates_client
101 cls.services_client = cls.os_primary.services_client
Emily Hugenbruche7991d92014-12-12 16:53:36 +0000102 cls.instance_usages_audit_log_client = (
Jordan Pittier8160d312017-04-18 11:52:23 +0200103 cls.os_primary.instance_usages_audit_log_client)
104 cls.hypervisor_client = cls.os_primary.hypervisor_client
105 cls.certificates_client = cls.os_primary.certificates_client
106 cls.migrations_client = cls.os_primary.migrations_client
Emily Hugenbruche7991d92014-12-12 16:53:36 +0000107 cls.security_group_default_rules_client = (
Jordan Pittier8160d312017-04-18 11:52:23 +0200108 cls.os_primary.security_group_default_rules_client)
109 cls.versions_client = cls.os_primary.compute_versions_client
Andrea Frittolia6b30152017-08-04 10:46:10 +0100110 if CONF.service_available.cinder:
111 cls.volumes_client = cls.os_primary.volumes_client_latest
Lee Yarwood4b108522020-01-15 10:50:24 +0000112 cls.attachments_client = cls.os_primary.attachments_client_latest
Lee Yarwood2ad7ca42020-01-07 13:06:25 +0000113 cls.snapshots_client = cls.os_primary.snapshots_client_latest
Matt Riedemannf110a4b2018-01-08 15:03:36 -0500114 if CONF.service_available.glance:
115 if CONF.image_feature_enabled.api_v1:
116 cls.images_client = cls.os_primary.image_client
117 elif CONF.image_feature_enabled.api_v2:
118 cls.images_client = cls.os_primary.image_client_v2
119 else:
120 raise lib_exc.InvalidConfiguration(
121 'Either api_v1 or api_v2 must be True in '
122 '[image-feature-enabled].')
Matt Riedemann14e5e482018-05-31 15:13:18 -0400123 cls._check_depends_on_nova_network()
124
125 @classmethod
126 def _check_depends_on_nova_network(cls):
127 # Since nova-network APIs were removed from Nova in the Rocky release,
128 # determine, based on the max version from the version document, if
129 # the compute API is >Queens and if so, skip tests that rely on
130 # nova-network.
131 if not getattr(cls, 'depends_on_nova_network', False):
132 return
133 versions = cls.versions_client.list_versions()['versions']
134 # Find the v2.1 version which will tell us our max version for the
135 # compute API we're testing against.
136 for version in versions:
137 if version['id'] == 'v2.1':
138 max_version = api_version_request.APIVersionRequest(
139 version['version'])
140 break
141 else:
142 LOG.warning(
143 'Unable to determine max v2.1 compute API version: %s',
144 versions)
145 return
146
147 # The max compute API version in Queens is 2.60 so we cap
148 # at that version.
149 queens = api_version_request.APIVersionRequest('2.60')
150 if max_version > queens:
151 raise cls.skipException('nova-network is gone')
Ivan Kolodyazhnybcfc32e2015-08-06 13:31:36 +0300152
Emily Hugenbruche7991d92014-12-12 16:53:36 +0000153 @classmethod
154 def resource_setup(cls):
Ken'ichi Ohmichi02a8ccd2015-11-05 06:05:29 +0000155 super(BaseV2ComputeTest, cls).resource_setup()
Ghanshyam05049dd2016-02-12 17:44:48 +0900156 cls.request_microversion = (
157 api_version_utils.select_request_microversion(
158 cls.min_microversion,
ghanshyam29591532016-03-11 17:12:43 +0900159 CONF.compute.min_microversion))
Lee Yarwood4b95d4b2020-01-15 10:49:54 +0000160 cls.volume_request_microversion = (
161 api_version_utils.select_request_microversion(
162 cls.volume_min_microversion,
163 CONF.volume.min_microversion))
164 cls.placement_request_microversion = (
165 api_version_utils.select_request_microversion(
166 cls.placement_min_microversion,
167 CONF.placement.min_microversion))
Ghanshyam Mann18b45d72021-12-07 12:37:29 -0600168 cls.setup_api_microversion_fixture(
169 compute_microversion=cls.request_microversion,
170 volume_microversion=cls.volume_request_microversion,
171 placement_microversion=cls.placement_request_microversion)
172
Matthew Treinishb0a78fc2014-01-29 16:49:12 +0000173 cls.build_interval = CONF.compute.build_interval
174 cls.build_timeout = CONF.compute.build_timeout
Matthew Treinishb0a78fc2014-01-29 16:49:12 +0000175 cls.image_ref = CONF.compute.image_ref
176 cls.image_ref_alt = CONF.compute.image_ref_alt
177 cls.flavor_ref = CONF.compute.flavor_ref
178 cls.flavor_ref_alt = CONF.compute.flavor_ref_alt
lanoux283273b2015-12-04 03:01:54 -0800179 cls.ssh_user = CONF.validation.image_ssh_user
Weronika Sikorac54a9112019-09-18 13:55:07 +0000180 cls.ssh_alt_user = CONF.validation.image_alt_ssh_user
lanoux283273b2015-12-04 03:01:54 -0800181 cls.image_ssh_user = CONF.validation.image_ssh_user
Weronika Sikorac54a9112019-09-18 13:55:07 +0000182 cls.image_alt_ssh_user = CONF.validation.image_alt_ssh_user
lanoux283273b2015-12-04 03:01:54 -0800183 cls.image_ssh_password = CONF.validation.image_ssh_password
Weronika Sikorac54a9112019-09-18 13:55:07 +0000184 cls.image_alt_ssh_password = CONF.validation.image_alt_ssh_password
Ken'ichi Ohmichi543a5d42014-05-02 08:44:15 +0900185
Matthew Treinishf7fca6a2013-12-09 16:27:23 +0000186 @classmethod
ghanshyam66b9aed2018-03-30 08:11:10 +0000187 def is_requested_microversion_compatible(cls, max_version):
188 """Check the compatibility of selected request microversion
189
190 This method will check if selected request microversion
191 (cls.request_microversion) for test is compatible with respect
192 to 'max_version'. Compatible means if selected request microversion
193 is in the range(<=) of 'max_version'.
194
195 :param max_version: maximum microversion to compare for compatibility.
196 Example: '2.30'
197 :returns: True if selected request microversion is compatible with
198 'max_version'. False in other case.
199 """
200 try:
201 req_version_obj = api_version_request.APIVersionRequest(
202 cls.request_microversion)
203 # NOTE(gmann): This is case where this method is used before calling
204 # resource_setup(), where cls.request_microversion is set. There may
205 # not be any such case but still we can handle this case.
206 except AttributeError:
207 request_microversion = (
208 api_version_utils.select_request_microversion(
209 cls.min_microversion,
210 CONF.compute.min_microversion))
211 req_version_obj = api_version_request.APIVersionRequest(
212 request_microversion)
213 max_version_obj = api_version_request.APIVersionRequest(max_version)
214 return req_version_obj <= max_version_obj
215
216 @classmethod
Attila Fazekas305e65b2013-10-29 13:23:07 +0100217 def server_check_teardown(cls):
218 """Checks is the shared server clean enough for subsequent test.
Ken'ichi Ohmichi88363cb2015-11-19 08:00:54 +0000219
Attila Fazekas305e65b2013-10-29 13:23:07 +0100220 Method will delete the server when it's dirty.
221 The setUp method is responsible for creating a new server.
222 Exceptions raised in tearDown class are fails the test case,
Marian Horban6afb0232015-11-10 22:47:12 -0500223 This method supposed to use only by tearDown methods, when
Attila Fazekas305e65b2013-10-29 13:23:07 +0100224 the shared server_id is stored in the server_id of the class.
225 """
226 if getattr(cls, 'server_id', None) is not None:
227 try:
Ken'ichi Ohmichi0eb153c2015-07-13 02:18:25 +0000228 waiters.wait_for_server_status(cls.servers_client,
229 cls.server_id, 'ACTIVE')
Attila Fazekas305e65b2013-10-29 13:23:07 +0100230 except Exception as exc:
231 LOG.exception(exc)
232 cls.servers_client.delete_server(cls.server_id)
Ken'ichi Ohmichie91a0c62015-08-13 02:09:16 +0000233 waiters.wait_for_server_termination(cls.servers_client,
234 cls.server_id)
Attila Fazekas305e65b2013-10-29 13:23:07 +0100235 cls.server_id = None
236 raise
237
238 @classmethod
Joe Gordon8843f0f2015-03-17 15:07:34 -0700239 def create_test_server(cls, validatable=False, volume_backed=False,
Artom Lifshitz68f1e5e2020-04-03 13:12:36 -0400240 validation_resources=None, clients=None, **kwargs):
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000241 """Wrapper utility that returns a test server.
Rohit Karajgidc300b22012-05-04 08:11:00 -0700242
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000243 This wrapper utility calls the common create test server and
244 returns a test server. The purpose of this wrapper is to minimize
245 the impact on the code of the tests already using this
246 function.
Joe Gordon8843f0f2015-03-17 15:07:34 -0700247
248 :param validatable: Whether the server will be pingable or sshable.
249 :param volume_backed: Whether the instance is volume backed or not.
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100250 :param validation_resources: Dictionary of validation resources as
251 returned by `get_class_validation_resources`.
Artom Lifshitz68f1e5e2020-04-03 13:12:36 -0400252 :param clients: Client manager, defaults to os_primary.
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100253 :param kwargs: Extra arguments are passed down to the
254 `compute.create_test_server` call.
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000255 """
zhufl7ae22682016-09-18 15:22:33 +0800256 if 'name' not in kwargs:
257 kwargs['name'] = data_utils.rand_name(cls.__name__ + "-server")
Sergey Nikitin8654e5b2017-06-04 22:09:56 +0400258
259 request_version = api_version_request.APIVersionRequest(
260 cls.request_microversion)
261 v2_37_version = api_version_request.APIVersionRequest('2.37')
262
zhuflff9779c2018-01-04 14:41:40 +0800263 tenant_network = cls.get_tenant_network()
Sergey Nikitin8654e5b2017-06-04 22:09:56 +0400264 # NOTE(snikitin): since microversion v2.37 'networks' field is required
zhuflff9779c2018-01-04 14:41:40 +0800265 if (request_version >= v2_37_version and 'networks' not in kwargs and
266 not tenant_network):
Sergey Nikitin8654e5b2017-06-04 22:09:56 +0400267 kwargs['networks'] = 'none'
268
Artom Lifshitz68f1e5e2020-04-03 13:12:36 -0400269 if clients is None:
270 clients = cls.os_primary
271
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000272 body, servers = compute.create_test_server(
Artom Lifshitz68f1e5e2020-04-03 13:12:36 -0400273 clients,
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000274 validatable,
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100275 validation_resources=validation_resources,
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000276 tenant_network=tenant_network,
Joe Gordon8843f0f2015-03-17 15:07:34 -0700277 volume_backed=volume_backed,
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000278 **kwargs)
Ken'ichi Ohmichi51c8c262013-12-21 03:30:37 +0900279
Andrea Frittoli0d0a3f32017-08-29 18:21:37 +0100280 # For each server schedule wait and delete, so we first delete all
281 # and then wait for all
282 for server in servers:
283 cls.addClassResourceCleanup(waiters.wait_for_server_termination,
Artom Lifshitz68f1e5e2020-04-03 13:12:36 -0400284 clients.servers_client, server['id'])
Andrea Frittoli0d0a3f32017-08-29 18:21:37 +0100285 for server in servers:
286 cls.addClassResourceCleanup(
287 test_utils.call_and_ignore_notfound_exc,
Artom Lifshitz68f1e5e2020-04-03 13:12:36 -0400288 clients.servers_client.delete_server, server['id'])
Sean Dague9b669e32012-12-13 18:40:08 -0500289
David Kranz0fb14292015-02-11 15:55:20 -0500290 return body
Sean Dague9b669e32012-12-13 18:40:08 -0500291
Zhi Kun Liu02e7a7b2014-01-08 16:08:32 +0800292 @classmethod
293 def create_security_group(cls, name=None, description=None):
294 if name is None:
295 name = data_utils.rand_name(cls.__name__ + "-securitygroup")
296 if description is None:
Ken'ichi Ohmichi4937f562015-03-23 00:15:01 +0000297 description = data_utils.rand_name('description')
ghanshyamb610b772015-08-24 17:29:38 +0900298 body = cls.security_groups_client.create_security_group(
299 name=name, description=description)['security_group']
Andrea Frittoli238818c2017-08-29 18:28:11 +0100300 cls.addClassResourceCleanup(
301 test_utils.call_and_ignore_notfound_exc,
302 cls.security_groups_client.delete_security_group,
303 body['id'])
Zhi Kun Liu02e7a7b2014-01-08 16:08:32 +0800304
David Kranz9964b4e2015-02-06 15:45:29 -0500305 return body
Zhi Kun Liu02e7a7b2014-01-08 16:08:32 +0800306
Abhijeet.Jain87dd4452014-04-23 15:51:23 +0530307 @classmethod
Ghanshyam2a180b82014-06-16 13:54:22 +0900308 def create_test_server_group(cls, name="", policy=None):
Abhijeet.Jain87dd4452014-04-23 15:51:23 +0530309 if not name:
310 name = data_utils.rand_name(cls.__name__ + "-Server-Group")
zhufled6d1022020-06-05 16:04:49 +0800311 if cls.is_requested_microversion_compatible('2.63'):
312 policy = policy or ['affinity']
313 if not isinstance(policy, list):
314 policy = [policy]
315 kwargs = {'policies': policy}
316 else:
317 policy = policy or 'affinity'
318 if isinstance(policy, list):
319 policy = policy[0]
320 kwargs = {'policy': policy}
Ken'ichi Ohmichi1f36daa2015-09-30 01:41:34 +0000321 body = cls.server_groups_client.create_server_group(
zhufled6d1022020-06-05 16:04:49 +0800322 name=name, **kwargs)['server_group']
Andrea Frittoli238818c2017-08-29 18:28:11 +0100323 cls.addClassResourceCleanup(
324 test_utils.call_and_ignore_notfound_exc,
325 cls.server_groups_client.delete_server_group,
326 body['id'])
David Kranzae99b9a2015-02-16 13:37:01 -0500327 return body
Abhijeet.Jain87dd4452014-04-23 15:51:23 +0530328
Ghanshyam Mann0208fc02023-07-27 11:57:31 -0700329 def wait_for(self, condition, *args):
Sean Daguef237ccb2013-01-04 15:19:14 -0500330 """Repeatedly calls condition() until a timeout."""
David Kranzcf0040c2012-06-26 09:46:56 -0400331 start_time = int(time.time())
332 while True:
333 try:
Ghanshyam Mann0208fc02023-07-27 11:57:31 -0700334 condition(*args)
Matthew Treinish05d9fb92012-12-07 16:14:05 -0500335 except Exception:
David Kranzcf0040c2012-06-26 09:46:56 -0400336 pass
337 else:
338 return
339 if int(time.time()) - start_time >= self.build_timeout:
Ghanshyam Mann0208fc02023-07-27 11:57:31 -0700340 condition(*args)
David Kranzcf0040c2012-06-26 09:46:56 -0400341 return
342 time.sleep(self.build_interval)
Jay Pipesf38eaac2012-06-21 13:37:35 -0400343
Attila Fazekas423834d2014-03-14 17:33:13 +0100344 @classmethod
345 def prepare_instance_network(cls):
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000346 if (CONF.validation.auth_method != 'disabled' and
347 CONF.validation.connect_method == 'floating'):
Attila Fazekas423834d2014-03-14 17:33:13 +0100348 cls.set_network_resources(network=True, subnet=True, router=True,
349 dhcp=True)
350
ivan-zhu8f992be2013-07-31 14:56:58 +0800351 @classmethod
352 def create_image_from_server(cls, server_id, **kwargs):
Matt Riedemannf110a4b2018-01-08 15:03:36 -0500353 """Wrapper utility that returns an image created from the server.
354
355 If compute microversion >= 2.36, the returned image response will
356 be from the image service API rather than the compute image proxy API.
357 """
zhufl35a694b2017-02-14 17:10:53 +0800358 name = kwargs.pop('name',
359 data_utils.rand_name(cls.__name__ + "-image"))
360 wait_until = kwargs.pop('wait_until', None)
361 wait_for_server = kwargs.pop('wait_for_server', True)
ivan-zhu8f992be2013-07-31 14:56:58 +0800362
zhufl35a694b2017-02-14 17:10:53 +0800363 image = cls.compute_images_client.create_image(server_id, name=name,
364 **kwargs)
Felipe Monteiroe65ec452017-09-26 06:47:03 +0100365 if api_version_utils.compare_version_header_to_response(
366 "OpenStack-API-Version", "compute 2.45", image.response, "lt"):
367 image_id = image['image_id']
368 else:
369 image_id = data_utils.parse_image_id(image.response['location'])
Matt Riedemannf110a4b2018-01-08 15:03:36 -0500370
371 # The compute image proxy APIs were deprecated in 2.35 so
372 # use the images client directly if the API microversion being
373 # used is >=2.36.
zhufl66275c22018-03-28 15:32:14 +0800374 if not cls.is_requested_microversion_compatible('2.35'):
Matt Riedemannf110a4b2018-01-08 15:03:36 -0500375 client = cls.images_client
376 else:
377 client = cls.compute_images_client
Andrea Frittolib17f7a32017-08-29 17:45:58 +0100378 cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
Matt Riedemannf110a4b2018-01-08 15:03:36 -0500379 client.delete_image, image_id)
ivan-zhu8f992be2013-07-31 14:56:58 +0800380
zhufl35a694b2017-02-14 17:10:53 +0800381 if wait_until is not None:
Matt Riedemann13954352017-02-07 14:03:54 -0500382 try:
zhufl66275c22018-03-28 15:32:14 +0800383 wait_until = wait_until.upper()
384 if not cls.is_requested_microversion_compatible('2.35'):
385 wait_until = wait_until.lower()
Matt Riedemannf110a4b2018-01-08 15:03:36 -0500386 waiters.wait_for_image_status(client, image_id, wait_until)
Matt Riedemann13954352017-02-07 14:03:54 -0500387 except lib_exc.NotFound:
zhufl35a694b2017-02-14 17:10:53 +0800388 if wait_until.upper() == 'ACTIVE':
Matt Riedemann13954352017-02-07 14:03:54 -0500389 # If the image is not found after create_image returned
390 # that means the snapshot failed in nova-compute and nova
391 # deleted the image. There should be a compute fault
392 # recorded with the server in that case, so get the server
393 # and dump some details.
394 server = (
395 cls.servers_client.show_server(server_id)['server'])
396 if 'fault' in server:
397 raise exceptions.SnapshotNotFoundException(
398 server['fault'], image_id=image_id)
399 else:
400 raise exceptions.SnapshotNotFoundException(
401 image_id=image_id)
402 else:
403 raise
Matt Riedemannf110a4b2018-01-08 15:03:36 -0500404 image = client.show_image(image_id)
405 # Compute image client returns response wrapped in 'image' element
406 # which is not the case with Glance image client.
407 if 'image' in image:
408 image = image['image']
ivan-zhu8f992be2013-07-31 14:56:58 +0800409
zhufl35a694b2017-02-14 17:10:53 +0800410 if wait_until.upper() == 'ACTIVE':
411 if wait_for_server:
Bob Ball5fe62392017-02-20 09:51:00 +0000412 waiters.wait_for_server_status(cls.servers_client,
413 server_id, 'ACTIVE')
David Kranza5299eb2015-01-15 17:24:05 -0500414 return image
ivan-zhu8f992be2013-07-31 14:56:58 +0800415
416 @classmethod
Artom Lifshitz4fc47f62022-05-05 14:17:27 -0400417 def recreate_server(cls, server_id, validatable=False, wait_until='ACTIVE',
418 **kwargs):
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100419 """Destroy an existing class level server and creates a new one
420
421 Some test classes use a test server that can be used by multiple
422 tests. This is done to optimise runtime and test load.
423 If something goes wrong with the test server, it can be rebuilt
424 using this helper.
425
426 This helper can also be used for the initial provisioning if no
427 server_id is specified.
428
429 :param server_id: UUID of the server to be rebuilt. If None is
430 specified, a new server is provisioned.
431 :param validatable: whether to the server needs to be
432 validatable. When True, validation resources are acquired via
433 the `get_class_validation_resources` helper.
434 :param kwargs: extra paramaters are passed through to the
435 `create_test_server` call.
436 :return: the UUID of the created server.
437 """
Matthew Treinish2cd19a42013-12-02 21:54:42 +0000438 if server_id:
zhufl9b682902016-12-15 09:16:34 +0800439 cls.delete_server(server_id)
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000440
Ghanshyam3390d9f2015-12-25 12:48:02 +0900441 cls.password = data_utils.rand_password()
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000442 server = cls.create_test_server(
443 validatable,
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100444 validation_resources=cls.get_class_validation_resources(
445 cls.os_primary),
Artom Lifshitz4fc47f62022-05-05 14:17:27 -0400446 wait_until=wait_until,
Ghanshyam3390d9f2015-12-25 12:48:02 +0900447 adminPass=cls.password,
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000448 **kwargs)
Matthew Treinish2cd19a42013-12-02 21:54:42 +0000449 return server['id']
ivan-zhu8f992be2013-07-31 14:56:58 +0800450
Matt Riedemann5dc594c2014-01-27 11:40:28 -0800451 @classmethod
Jesse Keating613b4982015-05-04 15:05:19 -0700452 def delete_server(cls, server_id):
453 """Deletes an existing server and waits for it to be gone."""
454 try:
455 cls.servers_client.delete_server(server_id)
Ken'ichi Ohmichie91a0c62015-08-13 02:09:16 +0000456 waiters.wait_for_server_termination(cls.servers_client,
457 server_id)
Jesse Keating613b4982015-05-04 15:05:19 -0700458 except Exception:
Jordan Pittier525ec712016-12-07 17:51:26 +0100459 LOG.exception('Failed to delete server %s', server_id)
Jesse Keating613b4982015-05-04 15:05:19 -0700460
Balazs Gibizerd8bbaba2022-05-17 17:15:40 +0200461 def resize_server(
462 self, server_id, new_flavor_id, wait_until='ACTIVE', **kwargs
463 ):
zhufl3d018b02016-11-25 16:43:04 +0800464 """resize and confirm_resize an server, waits for it to be ACTIVE."""
Jorge San Emeterio572ac542023-03-09 10:38:56 +0000465 body = self.servers_client.resize_server(
466 server_id, new_flavor_id, **kwargs)
467 waiters.wait_for_server_status(
468 self.servers_client, server_id, 'VERIFY_RESIZE',
469 request_id=body.response['x-openstack-request-id'])
zhuflbcb71172018-03-29 13:49:31 +0800470 self.servers_client.confirm_resize_server(server_id)
Balazs Gibizerd8bbaba2022-05-17 17:15:40 +0200471
zhuflbcb71172018-03-29 13:49:31 +0800472 waiters.wait_for_server_status(
473 self.servers_client, server_id, 'ACTIVE')
474 server = self.servers_client.show_server(server_id)['server']
Balazs Gibizerd8bbaba2022-05-17 17:15:40 +0200475
476 validation_resources = self.get_class_validation_resources(
477 self.os_primary)
478 if (
479 validation_resources and
480 wait_until in ("SSHABLE", "PINGABLE") and
481 CONF.validation.run_validation
482 ):
483 tenant_network = self.get_tenant_network()
484 compute.wait_for_ssh_or_ping(
485 server, self.os_primary, tenant_network,
486 True, validation_resources, wait_until, True)
487
zhuflbcb71172018-03-29 13:49:31 +0800488 self.assert_flavor_equal(new_flavor_id, server['flavor'])
zhufl3d018b02016-11-25 16:43:04 +0800489
Artom Lifshitzea2b59c2021-08-19 14:34:00 -0400490 def reboot_server(self, server_id, type):
491 """Reboot a server and wait for it to be ACTIVE."""
492 self.servers_client.reboot_server(server_id, type=type)
493 waiters.wait_for_server_status(
494 self.servers_client, server_id, 'ACTIVE')
495
zhufl3d018b02016-11-25 16:43:04 +0800496 @classmethod
Matt Riedemann5dc594c2014-01-27 11:40:28 -0800497 def delete_volume(cls, volume_id):
498 """Deletes the given volume and waits for it to be gone."""
zhufldecdcf62017-09-13 10:27:28 +0800499 try:
500 cls.volumes_client.delete_volume(volume_id)
501 # TODO(mriedem): We should move the wait_for_resource_deletion
502 # into the delete_volume method as a convenience to the caller.
503 cls.volumes_client.wait_for_resource_deletion(volume_id)
504 except lib_exc.NotFound:
505 LOG.warning("Unable to delete volume '%s' since it was not found. "
506 "Maybe it was already deleted?", volume_id)
Ken'ichi Ohmichi543a5d42014-05-02 08:44:15 +0900507
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000508 @classmethod
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100509 def get_server_ip(cls, server, validation_resources=None):
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000510 """Get the server fixed or floating IP.
511
Sean Dague20e98612016-01-06 14:33:28 -0500512 Based on the configuration we're in, return a correct ip
513 address for validating that a guest is up.
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100514
515 :param server: The server dict as returned by the API
516 :param validation_resources: The dict of validation resources
517 provisioned for the server.
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000518 """
Lee Yarwood20556df2021-11-12 09:38:18 +0000519 return compute.get_server_ip(
520 server, validation_resources=validation_resources)
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000521
Matt Riedemann342b37c2016-09-21 15:38:12 -0400522 @classmethod
zhufl8d23f922016-12-12 17:29:42 +0800523 def create_volume(cls, image_ref=None, **kwargs):
Matt Riedemann342b37c2016-09-21 15:38:12 -0400524 """Create a volume and wait for it to become 'available'.
525
Artom Lifshitzfc8f8e62016-04-13 11:08:32 +0000526 :param image_ref: Specify an image id to create a bootable volume.
Dan Smith5e2019f2023-07-21 10:38:59 -0700527 :param wait_for_available: Wait until the volume becomes available
528 before returning
Sergey Vilgelmeac094a2018-11-21 18:27:51 -0600529 :param kwargs: other parameters to create volume.
Matt Riedemann342b37c2016-09-21 15:38:12 -0400530 :returns: The available volume.
531 """
zhufl8d23f922016-12-12 17:29:42 +0800532 if 'size' not in kwargs:
533 kwargs['size'] = CONF.volume.volume_size
534 if 'display_name' not in kwargs:
535 vol_name = data_utils.rand_name(cls.__name__ + '-volume')
536 kwargs['display_name'] = vol_name
Artom Lifshitzfc8f8e62016-04-13 11:08:32 +0000537 if image_ref is not None:
zhufl8d23f922016-12-12 17:29:42 +0800538 kwargs['imageRef'] = image_ref
Dan Smith5e2019f2023-07-21 10:38:59 -0700539 wait = kwargs.pop('wait_for_available', True)
Sophie Huangdba4c9d2021-06-11 16:26:32 +0000540 if CONF.volume.volume_type and 'volume_type' not in kwargs:
541 # If volume_type is not provided in config then no need to
542 # add a volume type and
543 # if volume_type has already been added by child class then
544 # no need to override.
545 kwargs['volume_type'] = CONF.volume.volume_type
Martin Kopec00e6d6c2019-06-05 14:30:06 +0000546 if CONF.compute.compute_volume_common_az:
547 kwargs.setdefault('availability_zone',
548 CONF.compute.compute_volume_common_az)
zhufl8d23f922016-12-12 17:29:42 +0800549 volume = cls.volumes_client.create_volume(**kwargs)['volume']
Andrea Frittoli1fc499e2017-08-29 18:33:03 +0100550 cls.addClassResourceCleanup(
551 cls.volumes_client.wait_for_resource_deletion, volume['id'])
552 cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
553 cls.volumes_client.delete_volume,
554 volume['id'])
Dan Smith5e2019f2023-07-21 10:38:59 -0700555 if wait:
556 waiters.wait_for_volume_resource_status(cls.volumes_client,
557 volume['id'], 'available')
Matt Riedemann342b37c2016-09-21 15:38:12 -0400558 return volume
559
Matt Riedemann0d4551b2017-10-10 13:00:48 -0400560 def _detach_volume(self, server, volume):
561 """Helper method to detach a volume.
562
563 Ignores 404 responses if the volume or server do not exist, or the
564 volume is already detached from the server.
565 """
566 try:
567 volume = self.volumes_client.show_volume(volume['id'])['volume']
568 # Check the status. You can only detach an in-use volume, otherwise
569 # the compute API will return a 400 response.
570 if volume['status'] == 'in-use':
571 self.servers_client.detach_volume(server['id'], volume['id'])
Eric Friedb3eab672017-11-21 12:42:18 -0600572 except lib_exc.NotFound:
Matt Riedemann0d4551b2017-10-10 13:00:48 -0400573 # Ignore 404s on detach in case the server is deleted or the volume
574 # is already detached.
575 pass
576
Dan Smithd3155552023-02-27 06:48:38 -0800577 def attach_volume(self, server, volume, device=None, tag=None,
578 wait_for_detach=True):
Matt Riedemanncb16a662016-10-01 18:30:05 -0400579 """Attaches volume to server and waits for 'in-use' volume status.
580
581 The volume will be detached when the test tears down.
582
583 :param server: The server to which the volume will be attached.
584 :param volume: The volume to attach.
585 :param device: Optional mountpoint for the attached volume. Note that
586 this is not guaranteed for all hypervisors and is not recommended.
Artom Lifshitzb6b2bba2016-10-31 14:56:40 -0400587 :param tag: Optional device role tag to apply to the volume.
Matt Riedemanncb16a662016-10-01 18:30:05 -0400588 """
589 attach_kwargs = dict(volumeId=volume['id'])
590 if device:
591 attach_kwargs['device'] = device
Artom Lifshitzb6b2bba2016-10-31 14:56:40 -0400592 if tag:
593 attach_kwargs['tag'] = tag
594
zhufl36f0a972017-02-28 15:43:33 +0800595 attachment = self.servers_client.attach_volume(
596 server['id'], **attach_kwargs)['volumeAttachment']
Lee Yarwood1bd60592021-06-04 10:18:35 +0100597
598 # NOTE(lyarwood): During attach we initially wait for the volume
599 # attachment and then check the volume state.
600 waiters.wait_for_volume_attachment_create(
601 self.volumes_client, volume['id'], server['id'])
602 # TODO(lyarwood): Remove the following volume status checks and move to
603 # attachment status checks across all volumes now with the 3.27
604 # microversion somehow.
605 if not volume['multiattach']:
606 waiters.wait_for_volume_resource_status(
607 self.volumes_client, volume['id'], 'in-use')
608
609 # NOTE(lyarwood): On teardown (LIFO) initially wait for the volume
610 # attachment in Nova to be removed. While this technically happens last
611 # we want this to be the first waiter as if it fails we can then dump
612 # the contents of the console log. The final check of the volume state
613 # should be a no-op by this point and is just added for completeness
614 # when detaching non-multiattach volumes.
Dan Smithd3155552023-02-27 06:48:38 -0800615 if not volume['multiattach'] and wait_for_detach:
Lee Yarwood1bd60592021-06-04 10:18:35 +0100616 self.addCleanup(
617 waiters.wait_for_volume_resource_status, self.volumes_client,
618 volume['id'], 'available')
619 self.addCleanup(
620 waiters.wait_for_volume_attachment_remove_from_server,
621 self.servers_client, server['id'], volume['id'])
Matt Riedemann0d4551b2017-10-10 13:00:48 -0400622 self.addCleanup(self._detach_volume, server, volume)
Lee Yarwood1bd60592021-06-04 10:18:35 +0100623
zhufl36f0a972017-02-28 15:43:33 +0800624 return attachment
Matt Riedemann342b37c2016-09-21 15:38:12 -0400625
Lee Yarwood2ad7ca42020-01-07 13:06:25 +0000626 def create_volume_snapshot(self, volume_id, name=None, description=None,
627 metadata=None, force=False):
628 name = name or data_utils.rand_name(
629 self.__class__.__name__ + '-snapshot')
630 snapshot = self.snapshots_client.create_snapshot(
631 volume_id=volume_id,
632 force=force,
633 display_name=name,
634 description=description,
635 metadata=metadata)['snapshot']
636 self.addCleanup(self.snapshots_client.wait_for_resource_deletion,
637 snapshot['id'])
638 self.addCleanup(self.snapshots_client.delete_snapshot, snapshot['id'])
639 waiters.wait_for_volume_resource_status(self.snapshots_client,
640 snapshot['id'], 'available')
641 snapshot = self.snapshots_client.show_snapshot(
642 snapshot['id'])['snapshot']
643 return snapshot
644
zhuflbcb71172018-03-29 13:49:31 +0800645 def assert_flavor_equal(self, flavor_id, server_flavor):
646 """Check whether server_flavor equals to flavor.
647
648 :param flavor_id: flavor id
649 :param server_flavor: flavor info returned by show_server.
650 """
651 # Nova API > 2.46 no longer includes flavor.id, and schema check
652 # will cover whether 'id' should be in flavor
653 if server_flavor.get('id'):
654 msg = ('server flavor is not same as flavor!')
655 self.assertEqual(flavor_id, server_flavor['id'], msg)
656 else:
657 flavor = self.flavors_client.show_flavor(flavor_id)['flavor']
658 self.assertEqual(flavor['name'], server_flavor['original_name'],
659 "original_name in server flavor is not same as "
660 "flavor name!")
661 for key in ['ram', 'vcpus', 'disk']:
662 msg = ('attribute %s in server flavor is not same as '
663 'flavor!' % key)
664 self.assertEqual(flavor[key], server_flavor[key], msg)
665
Ken'ichi Ohmichi543a5d42014-05-02 08:44:15 +0900666
Ken'ichi Ohmichi02a8ccd2015-11-05 06:05:29 +0000667class BaseV2ComputeAdminTest(BaseV2ComputeTest):
Ken'ichi Ohmichibcefa3d2014-05-09 08:14:05 +0900668 """Base test case class for Compute Admin API tests."""
ivan-zhuf2b00502013-10-18 10:06:52 +0800669
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000670 credentials = ['primary', 'admin']
Emily Hugenbruche7991d92014-12-12 16:53:36 +0000671
672 @classmethod
673 def setup_clients(cls):
Ken'ichi Ohmichi02a8ccd2015-11-05 06:05:29 +0000674 super(BaseV2ComputeAdminTest, cls).setup_clients()
Ken'ichi Ohmichi9f5adf82014-12-12 04:01:32 +0000675 cls.availability_zone_admin_client = (
Jordan Pittier8160d312017-04-18 11:52:23 +0200676 cls.os_admin.availability_zone_client)
677 cls.admin_flavors_client = cls.os_admin.flavors_client
678 cls.admin_servers_client = cls.os_admin.servers_client
Artom Lifshitzb0ee03e52021-12-01 14:04:15 -0500679 cls.admin_image_client = cls.os_admin.image_client_v2
Rao Adnan Khanac1aaf62017-04-27 08:01:18 -0500680 cls.admin_assisted_volume_snapshots_client = \
681 cls.os_admin.assisted_volume_snapshots_client
zhufl36eeab02017-01-18 11:49:04 +0800682
683 def create_flavor(self, ram, vcpus, disk, name=None,
684 is_public='True', **kwargs):
685 if name is None:
686 name = data_utils.rand_name(self.__class__.__name__ + "-flavor")
687 id = kwargs.pop('id', data_utils.rand_int_id(start=1000))
688 client = self.admin_flavors_client
689 flavor = client.create_flavor(
690 ram=ram, vcpus=vcpus, disk=disk, name=name,
691 id=id, is_public=is_public, **kwargs)['flavor']
692 self.addCleanup(client.wait_for_resource_deletion, flavor['id'])
693 self.addCleanup(client.delete_flavor, flavor['id'])
694 return flavor
Duc Truong09941202017-06-07 10:15:20 -0700695
zhufl7bc916d2018-08-22 14:47:39 +0800696 @classmethod
697 def get_host_for_server(cls, server_id):
698 server_details = cls.admin_servers_client.show_server(server_id)
Duc Truong09941202017-06-07 10:15:20 -0700699 return server_details['server']['OS-EXT-SRV-ATTR:host']
700
701 def get_host_other_than(self, server_id):
702 source_host = self.get_host_for_server(server_id)
703
Radoslav Gerganov50325e22018-03-29 12:02:04 +0300704 svcs = self.os_admin.services_client.list_services(
705 binary='nova-compute')['services']
Marian Krcmarik6e3f99e2020-02-26 23:27:51 +0100706 hosts = []
707 for svc in svcs:
Maksim Malchukf4970a32022-11-16 23:29:33 +0300708 if svc['host'].endswith('-ironic'):
709 continue
Marian Krcmarik6e3f99e2020-02-26 23:27:51 +0100710 if svc['state'] == 'up' and svc['status'] == 'enabled':
711 if CONF.compute.compute_volume_common_az:
712 if svc['zone'] == CONF.compute.compute_volume_common_az:
713 hosts.append(svc['host'])
714 else:
715 hosts.append(svc['host'])
Duc Truong09941202017-06-07 10:15:20 -0700716
717 for target_host in hosts:
718 if source_host != target_host:
719 return target_host