blob: ea1cddc5fc9a5240518150ce8ae6f4b4d4cec350 [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
David Kranzcf0040c2012-06-26 09:46:56 -0400329 def wait_for(self, condition):
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:
334 condition()
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:
340 condition()
341 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."""
zhuflbcb71172018-03-29 13:49:31 +0800465 self.servers_client.resize_server(server_id, new_flavor_id, **kwargs)
466 waiters.wait_for_server_status(self.servers_client, server_id,
zhufl3d018b02016-11-25 16:43:04 +0800467 'VERIFY_RESIZE')
zhuflbcb71172018-03-29 13:49:31 +0800468 self.servers_client.confirm_resize_server(server_id)
Balazs Gibizerd8bbaba2022-05-17 17:15:40 +0200469
zhuflbcb71172018-03-29 13:49:31 +0800470 waiters.wait_for_server_status(
471 self.servers_client, server_id, 'ACTIVE')
472 server = self.servers_client.show_server(server_id)['server']
Balazs Gibizerd8bbaba2022-05-17 17:15:40 +0200473
474 validation_resources = self.get_class_validation_resources(
475 self.os_primary)
476 if (
477 validation_resources and
478 wait_until in ("SSHABLE", "PINGABLE") and
479 CONF.validation.run_validation
480 ):
481 tenant_network = self.get_tenant_network()
482 compute.wait_for_ssh_or_ping(
483 server, self.os_primary, tenant_network,
484 True, validation_resources, wait_until, True)
485
zhuflbcb71172018-03-29 13:49:31 +0800486 self.assert_flavor_equal(new_flavor_id, server['flavor'])
zhufl3d018b02016-11-25 16:43:04 +0800487
Artom Lifshitzea2b59c2021-08-19 14:34:00 -0400488 def reboot_server(self, server_id, type):
489 """Reboot a server and wait for it to be ACTIVE."""
490 self.servers_client.reboot_server(server_id, type=type)
491 waiters.wait_for_server_status(
492 self.servers_client, server_id, 'ACTIVE')
493
zhufl3d018b02016-11-25 16:43:04 +0800494 @classmethod
Matt Riedemann5dc594c2014-01-27 11:40:28 -0800495 def delete_volume(cls, volume_id):
496 """Deletes the given volume and waits for it to be gone."""
zhufldecdcf62017-09-13 10:27:28 +0800497 try:
498 cls.volumes_client.delete_volume(volume_id)
499 # TODO(mriedem): We should move the wait_for_resource_deletion
500 # into the delete_volume method as a convenience to the caller.
501 cls.volumes_client.wait_for_resource_deletion(volume_id)
502 except lib_exc.NotFound:
503 LOG.warning("Unable to delete volume '%s' since it was not found. "
504 "Maybe it was already deleted?", volume_id)
Ken'ichi Ohmichi543a5d42014-05-02 08:44:15 +0900505
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000506 @classmethod
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100507 def get_server_ip(cls, server, validation_resources=None):
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000508 """Get the server fixed or floating IP.
509
Sean Dague20e98612016-01-06 14:33:28 -0500510 Based on the configuration we're in, return a correct ip
511 address for validating that a guest is up.
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100512
513 :param server: The server dict as returned by the API
514 :param validation_resources: The dict of validation resources
515 provisioned for the server.
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000516 """
Lee Yarwood20556df2021-11-12 09:38:18 +0000517 return compute.get_server_ip(
518 server, validation_resources=validation_resources)
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000519
Matt Riedemann342b37c2016-09-21 15:38:12 -0400520 @classmethod
zhufl8d23f922016-12-12 17:29:42 +0800521 def create_volume(cls, image_ref=None, **kwargs):
Matt Riedemann342b37c2016-09-21 15:38:12 -0400522 """Create a volume and wait for it to become 'available'.
523
Artom Lifshitzfc8f8e62016-04-13 11:08:32 +0000524 :param image_ref: Specify an image id to create a bootable volume.
Sergey Vilgelmeac094a2018-11-21 18:27:51 -0600525 :param kwargs: other parameters to create volume.
Matt Riedemann342b37c2016-09-21 15:38:12 -0400526 :returns: The available volume.
527 """
zhufl8d23f922016-12-12 17:29:42 +0800528 if 'size' not in kwargs:
529 kwargs['size'] = CONF.volume.volume_size
530 if 'display_name' not in kwargs:
531 vol_name = data_utils.rand_name(cls.__name__ + '-volume')
532 kwargs['display_name'] = vol_name
Artom Lifshitzfc8f8e62016-04-13 11:08:32 +0000533 if image_ref is not None:
zhufl8d23f922016-12-12 17:29:42 +0800534 kwargs['imageRef'] = image_ref
Sophie Huangdba4c9d2021-06-11 16:26:32 +0000535 if CONF.volume.volume_type and 'volume_type' not in kwargs:
536 # If volume_type is not provided in config then no need to
537 # add a volume type and
538 # if volume_type has already been added by child class then
539 # no need to override.
540 kwargs['volume_type'] = CONF.volume.volume_type
Martin Kopec00e6d6c2019-06-05 14:30:06 +0000541 if CONF.compute.compute_volume_common_az:
542 kwargs.setdefault('availability_zone',
543 CONF.compute.compute_volume_common_az)
zhufl8d23f922016-12-12 17:29:42 +0800544 volume = cls.volumes_client.create_volume(**kwargs)['volume']
Andrea Frittoli1fc499e2017-08-29 18:33:03 +0100545 cls.addClassResourceCleanup(
546 cls.volumes_client.wait_for_resource_deletion, volume['id'])
547 cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
548 cls.volumes_client.delete_volume,
549 volume['id'])
lkuchlan52d7b0d2016-11-07 20:53:19 +0200550 waiters.wait_for_volume_resource_status(cls.volumes_client,
551 volume['id'], 'available')
Matt Riedemann342b37c2016-09-21 15:38:12 -0400552 return volume
553
Matt Riedemann0d4551b2017-10-10 13:00:48 -0400554 def _detach_volume(self, server, volume):
555 """Helper method to detach a volume.
556
557 Ignores 404 responses if the volume or server do not exist, or the
558 volume is already detached from the server.
559 """
560 try:
561 volume = self.volumes_client.show_volume(volume['id'])['volume']
562 # Check the status. You can only detach an in-use volume, otherwise
563 # the compute API will return a 400 response.
564 if volume['status'] == 'in-use':
565 self.servers_client.detach_volume(server['id'], volume['id'])
Eric Friedb3eab672017-11-21 12:42:18 -0600566 except lib_exc.NotFound:
Matt Riedemann0d4551b2017-10-10 13:00:48 -0400567 # Ignore 404s on detach in case the server is deleted or the volume
568 # is already detached.
569 pass
570
Artom Lifshitzb6b2bba2016-10-31 14:56:40 -0400571 def attach_volume(self, server, volume, device=None, tag=None):
Matt Riedemanncb16a662016-10-01 18:30:05 -0400572 """Attaches volume to server and waits for 'in-use' volume status.
573
574 The volume will be detached when the test tears down.
575
576 :param server: The server to which the volume will be attached.
577 :param volume: The volume to attach.
578 :param device: Optional mountpoint for the attached volume. Note that
579 this is not guaranteed for all hypervisors and is not recommended.
Artom Lifshitzb6b2bba2016-10-31 14:56:40 -0400580 :param tag: Optional device role tag to apply to the volume.
Matt Riedemanncb16a662016-10-01 18:30:05 -0400581 """
582 attach_kwargs = dict(volumeId=volume['id'])
583 if device:
584 attach_kwargs['device'] = device
Artom Lifshitzb6b2bba2016-10-31 14:56:40 -0400585 if tag:
586 attach_kwargs['tag'] = tag
587
zhufl36f0a972017-02-28 15:43:33 +0800588 attachment = self.servers_client.attach_volume(
589 server['id'], **attach_kwargs)['volumeAttachment']
Lee Yarwood1bd60592021-06-04 10:18:35 +0100590
591 # NOTE(lyarwood): During attach we initially wait for the volume
592 # attachment and then check the volume state.
593 waiters.wait_for_volume_attachment_create(
594 self.volumes_client, volume['id'], server['id'])
595 # TODO(lyarwood): Remove the following volume status checks and move to
596 # attachment status checks across all volumes now with the 3.27
597 # microversion somehow.
598 if not volume['multiattach']:
599 waiters.wait_for_volume_resource_status(
600 self.volumes_client, volume['id'], 'in-use')
601
602 # NOTE(lyarwood): On teardown (LIFO) initially wait for the volume
603 # attachment in Nova to be removed. While this technically happens last
604 # we want this to be the first waiter as if it fails we can then dump
605 # the contents of the console log. The final check of the volume state
606 # should be a no-op by this point and is just added for completeness
607 # when detaching non-multiattach volumes.
608 if not volume['multiattach']:
609 self.addCleanup(
610 waiters.wait_for_volume_resource_status, self.volumes_client,
611 volume['id'], 'available')
612 self.addCleanup(
613 waiters.wait_for_volume_attachment_remove_from_server,
614 self.servers_client, server['id'], volume['id'])
Matt Riedemann0d4551b2017-10-10 13:00:48 -0400615 self.addCleanup(self._detach_volume, server, volume)
Lee Yarwood1bd60592021-06-04 10:18:35 +0100616
zhufl36f0a972017-02-28 15:43:33 +0800617 return attachment
Matt Riedemann342b37c2016-09-21 15:38:12 -0400618
Lee Yarwood2ad7ca42020-01-07 13:06:25 +0000619 def create_volume_snapshot(self, volume_id, name=None, description=None,
620 metadata=None, force=False):
621 name = name or data_utils.rand_name(
622 self.__class__.__name__ + '-snapshot')
623 snapshot = self.snapshots_client.create_snapshot(
624 volume_id=volume_id,
625 force=force,
626 display_name=name,
627 description=description,
628 metadata=metadata)['snapshot']
629 self.addCleanup(self.snapshots_client.wait_for_resource_deletion,
630 snapshot['id'])
631 self.addCleanup(self.snapshots_client.delete_snapshot, snapshot['id'])
632 waiters.wait_for_volume_resource_status(self.snapshots_client,
633 snapshot['id'], 'available')
634 snapshot = self.snapshots_client.show_snapshot(
635 snapshot['id'])['snapshot']
636 return snapshot
637
zhuflbcb71172018-03-29 13:49:31 +0800638 def assert_flavor_equal(self, flavor_id, server_flavor):
639 """Check whether server_flavor equals to flavor.
640
641 :param flavor_id: flavor id
642 :param server_flavor: flavor info returned by show_server.
643 """
644 # Nova API > 2.46 no longer includes flavor.id, and schema check
645 # will cover whether 'id' should be in flavor
646 if server_flavor.get('id'):
647 msg = ('server flavor is not same as flavor!')
648 self.assertEqual(flavor_id, server_flavor['id'], msg)
649 else:
650 flavor = self.flavors_client.show_flavor(flavor_id)['flavor']
651 self.assertEqual(flavor['name'], server_flavor['original_name'],
652 "original_name in server flavor is not same as "
653 "flavor name!")
654 for key in ['ram', 'vcpus', 'disk']:
655 msg = ('attribute %s in server flavor is not same as '
656 'flavor!' % key)
657 self.assertEqual(flavor[key], server_flavor[key], msg)
658
Ken'ichi Ohmichi543a5d42014-05-02 08:44:15 +0900659
Ken'ichi Ohmichi02a8ccd2015-11-05 06:05:29 +0000660class BaseV2ComputeAdminTest(BaseV2ComputeTest):
Ken'ichi Ohmichibcefa3d2014-05-09 08:14:05 +0900661 """Base test case class for Compute Admin API tests."""
ivan-zhuf2b00502013-10-18 10:06:52 +0800662
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000663 credentials = ['primary', 'admin']
Emily Hugenbruche7991d92014-12-12 16:53:36 +0000664
665 @classmethod
666 def setup_clients(cls):
Ken'ichi Ohmichi02a8ccd2015-11-05 06:05:29 +0000667 super(BaseV2ComputeAdminTest, cls).setup_clients()
Ken'ichi Ohmichi9f5adf82014-12-12 04:01:32 +0000668 cls.availability_zone_admin_client = (
Jordan Pittier8160d312017-04-18 11:52:23 +0200669 cls.os_admin.availability_zone_client)
670 cls.admin_flavors_client = cls.os_admin.flavors_client
671 cls.admin_servers_client = cls.os_admin.servers_client
Artom Lifshitzb0ee03e52021-12-01 14:04:15 -0500672 cls.admin_image_client = cls.os_admin.image_client_v2
Rao Adnan Khanac1aaf62017-04-27 08:01:18 -0500673 cls.admin_assisted_volume_snapshots_client = \
674 cls.os_admin.assisted_volume_snapshots_client
zhufl36eeab02017-01-18 11:49:04 +0800675
676 def create_flavor(self, ram, vcpus, disk, name=None,
677 is_public='True', **kwargs):
678 if name is None:
679 name = data_utils.rand_name(self.__class__.__name__ + "-flavor")
680 id = kwargs.pop('id', data_utils.rand_int_id(start=1000))
681 client = self.admin_flavors_client
682 flavor = client.create_flavor(
683 ram=ram, vcpus=vcpus, disk=disk, name=name,
684 id=id, is_public=is_public, **kwargs)['flavor']
685 self.addCleanup(client.wait_for_resource_deletion, flavor['id'])
686 self.addCleanup(client.delete_flavor, flavor['id'])
687 return flavor
Duc Truong09941202017-06-07 10:15:20 -0700688
zhufl7bc916d2018-08-22 14:47:39 +0800689 @classmethod
690 def get_host_for_server(cls, server_id):
691 server_details = cls.admin_servers_client.show_server(server_id)
Duc Truong09941202017-06-07 10:15:20 -0700692 return server_details['server']['OS-EXT-SRV-ATTR:host']
693
694 def get_host_other_than(self, server_id):
695 source_host = self.get_host_for_server(server_id)
696
Radoslav Gerganov50325e22018-03-29 12:02:04 +0300697 svcs = self.os_admin.services_client.list_services(
698 binary='nova-compute')['services']
Marian Krcmarik6e3f99e2020-02-26 23:27:51 +0100699 hosts = []
700 for svc in svcs:
Maksim Malchukf4970a32022-11-16 23:29:33 +0300701 if svc['host'].endswith('-ironic'):
702 continue
Marian Krcmarik6e3f99e2020-02-26 23:27:51 +0100703 if svc['state'] == 'up' and svc['status'] == 'enabled':
704 if CONF.compute.compute_volume_common_az:
705 if svc['zone'] == CONF.compute.compute_volume_common_az:
706 hosts.append(svc['host'])
707 else:
708 hosts.append(svc['host'])
Duc Truong09941202017-06-07 10:15:20 -0700709
710 for target_host in hosts:
711 if source_host != target_host:
712 return target_host