blob: 9aa5d35a950eb6f7aa92f1d0f4f36eb5d94b9a57 [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")
Ghanshyam Mannfd90dac2023-08-03 16:26:44 -070054 if cls.create_default_network and not CONF.service_available.neutron:
55 raise cls.skipException("Neutron is not available")
56
Lee Yarwood4b95d4b2020-01-15 10:49:54 +000057 api_version_utils.check_skip_with_microversion(
58 cls.min_microversion, cls.max_microversion,
59 CONF.compute.min_microversion, CONF.compute.max_microversion)
60 api_version_utils.check_skip_with_microversion(
61 cls.volume_min_microversion, cls.volume_max_microversion,
62 CONF.volume.min_microversion, CONF.volume.max_microversion)
63 api_version_utils.check_skip_with_microversion(
64 cls.placement_min_microversion, cls.placement_max_microversion,
65 CONF.placement.min_microversion, CONF.placement.max_microversion)
Jay Pipesf38eaac2012-06-21 13:37:35 -040066
Emily Hugenbruche7991d92014-12-12 16:53:36 +000067 @classmethod
68 def setup_credentials(cls):
Eric Friedbfaa50f2020-01-09 12:04:54 -060069 # Setting network=True, subnet=True creates a default network
70 cls.set_network_resources(
71 network=cls.create_default_network,
Lee Yarwooddb2f5612021-11-12 13:03:57 +000072 subnet=cls.create_default_network,
73 router=cls.create_default_network,
74 dhcp=cls.create_default_network)
Ken'ichi Ohmichi02a8ccd2015-11-05 06:05:29 +000075 super(BaseV2ComputeTest, cls).setup_credentials()
Daryl Walleckc7251962012-03-12 17:26:54 -050076
Emily Hugenbruche7991d92014-12-12 16:53:36 +000077 @classmethod
78 def setup_clients(cls):
Ken'ichi Ohmichi02a8ccd2015-11-05 06:05:29 +000079 super(BaseV2ComputeTest, cls).setup_clients()
Jordan Pittier8160d312017-04-18 11:52:23 +020080 cls.servers_client = cls.os_primary.servers_client
81 cls.server_groups_client = cls.os_primary.server_groups_client
82 cls.flavors_client = cls.os_primary.flavors_client
83 cls.compute_images_client = cls.os_primary.compute_images_client
84 cls.extensions_client = cls.os_primary.extensions_client
85 cls.floating_ip_pools_client = cls.os_primary.floating_ip_pools_client
86 cls.floating_ips_client = cls.os_primary.compute_floating_ips_client
87 cls.keypairs_client = cls.os_primary.keypairs_client
John Warren5cdbf422016-01-05 12:42:43 -050088 cls.security_group_rules_client = (
Jordan Pittier8160d312017-04-18 11:52:23 +020089 cls.os_primary.compute_security_group_rules_client)
90 cls.security_groups_client =\
91 cls.os_primary.compute_security_groups_client
92 cls.quotas_client = cls.os_primary.quotas_client
93 cls.compute_networks_client = cls.os_primary.compute_networks_client
94 cls.limits_client = cls.os_primary.limits_client
95 cls.volumes_extensions_client =\
96 cls.os_primary.volumes_extensions_client
97 cls.snapshots_extensions_client =\
98 cls.os_primary.snapshots_extensions_client
99 cls.interfaces_client = cls.os_primary.interfaces_client
100 cls.fixed_ips_client = cls.os_primary.fixed_ips_client
101 cls.availability_zone_client = cls.os_primary.availability_zone_client
102 cls.agents_client = cls.os_primary.agents_client
103 cls.aggregates_client = cls.os_primary.aggregates_client
104 cls.services_client = cls.os_primary.services_client
Emily Hugenbruche7991d92014-12-12 16:53:36 +0000105 cls.instance_usages_audit_log_client = (
Jordan Pittier8160d312017-04-18 11:52:23 +0200106 cls.os_primary.instance_usages_audit_log_client)
107 cls.hypervisor_client = cls.os_primary.hypervisor_client
108 cls.certificates_client = cls.os_primary.certificates_client
109 cls.migrations_client = cls.os_primary.migrations_client
Emily Hugenbruche7991d92014-12-12 16:53:36 +0000110 cls.security_group_default_rules_client = (
Jordan Pittier8160d312017-04-18 11:52:23 +0200111 cls.os_primary.security_group_default_rules_client)
112 cls.versions_client = cls.os_primary.compute_versions_client
Andrea Frittolia6b30152017-08-04 10:46:10 +0100113 if CONF.service_available.cinder:
114 cls.volumes_client = cls.os_primary.volumes_client_latest
Lee Yarwood4b108522020-01-15 10:50:24 +0000115 cls.attachments_client = cls.os_primary.attachments_client_latest
Lee Yarwood2ad7ca42020-01-07 13:06:25 +0000116 cls.snapshots_client = cls.os_primary.snapshots_client_latest
Matt Riedemannf110a4b2018-01-08 15:03:36 -0500117 if CONF.service_available.glance:
118 if CONF.image_feature_enabled.api_v1:
119 cls.images_client = cls.os_primary.image_client
120 elif CONF.image_feature_enabled.api_v2:
121 cls.images_client = cls.os_primary.image_client_v2
122 else:
123 raise lib_exc.InvalidConfiguration(
124 'Either api_v1 or api_v2 must be True in '
125 '[image-feature-enabled].')
Matt Riedemann14e5e482018-05-31 15:13:18 -0400126 cls._check_depends_on_nova_network()
127
128 @classmethod
129 def _check_depends_on_nova_network(cls):
130 # Since nova-network APIs were removed from Nova in the Rocky release,
131 # determine, based on the max version from the version document, if
132 # the compute API is >Queens and if so, skip tests that rely on
133 # nova-network.
134 if not getattr(cls, 'depends_on_nova_network', False):
135 return
136 versions = cls.versions_client.list_versions()['versions']
137 # Find the v2.1 version which will tell us our max version for the
138 # compute API we're testing against.
139 for version in versions:
140 if version['id'] == 'v2.1':
141 max_version = api_version_request.APIVersionRequest(
142 version['version'])
143 break
144 else:
145 LOG.warning(
146 'Unable to determine max v2.1 compute API version: %s',
147 versions)
148 return
149
150 # The max compute API version in Queens is 2.60 so we cap
151 # at that version.
152 queens = api_version_request.APIVersionRequest('2.60')
153 if max_version > queens:
154 raise cls.skipException('nova-network is gone')
Ivan Kolodyazhnybcfc32e2015-08-06 13:31:36 +0300155
Emily Hugenbruche7991d92014-12-12 16:53:36 +0000156 @classmethod
157 def resource_setup(cls):
Ken'ichi Ohmichi02a8ccd2015-11-05 06:05:29 +0000158 super(BaseV2ComputeTest, cls).resource_setup()
Ghanshyam05049dd2016-02-12 17:44:48 +0900159 cls.request_microversion = (
160 api_version_utils.select_request_microversion(
161 cls.min_microversion,
ghanshyam29591532016-03-11 17:12:43 +0900162 CONF.compute.min_microversion))
Lee Yarwood4b95d4b2020-01-15 10:49:54 +0000163 cls.volume_request_microversion = (
164 api_version_utils.select_request_microversion(
165 cls.volume_min_microversion,
166 CONF.volume.min_microversion))
167 cls.placement_request_microversion = (
168 api_version_utils.select_request_microversion(
169 cls.placement_min_microversion,
170 CONF.placement.min_microversion))
Ghanshyam Mann18b45d72021-12-07 12:37:29 -0600171 cls.setup_api_microversion_fixture(
172 compute_microversion=cls.request_microversion,
173 volume_microversion=cls.volume_request_microversion,
174 placement_microversion=cls.placement_request_microversion)
175
Matthew Treinishb0a78fc2014-01-29 16:49:12 +0000176 cls.build_interval = CONF.compute.build_interval
177 cls.build_timeout = CONF.compute.build_timeout
Matthew Treinishb0a78fc2014-01-29 16:49:12 +0000178 cls.image_ref = CONF.compute.image_ref
179 cls.image_ref_alt = CONF.compute.image_ref_alt
180 cls.flavor_ref = CONF.compute.flavor_ref
181 cls.flavor_ref_alt = CONF.compute.flavor_ref_alt
lanoux283273b2015-12-04 03:01:54 -0800182 cls.ssh_user = CONF.validation.image_ssh_user
Weronika Sikorac54a9112019-09-18 13:55:07 +0000183 cls.ssh_alt_user = CONF.validation.image_alt_ssh_user
lanoux283273b2015-12-04 03:01:54 -0800184 cls.image_ssh_user = CONF.validation.image_ssh_user
Weronika Sikorac54a9112019-09-18 13:55:07 +0000185 cls.image_alt_ssh_user = CONF.validation.image_alt_ssh_user
lanoux283273b2015-12-04 03:01:54 -0800186 cls.image_ssh_password = CONF.validation.image_ssh_password
Weronika Sikorac54a9112019-09-18 13:55:07 +0000187 cls.image_alt_ssh_password = CONF.validation.image_alt_ssh_password
Ken'ichi Ohmichi543a5d42014-05-02 08:44:15 +0900188
Matthew Treinishf7fca6a2013-12-09 16:27:23 +0000189 @classmethod
ghanshyam66b9aed2018-03-30 08:11:10 +0000190 def is_requested_microversion_compatible(cls, max_version):
191 """Check the compatibility of selected request microversion
192
193 This method will check if selected request microversion
194 (cls.request_microversion) for test is compatible with respect
195 to 'max_version'. Compatible means if selected request microversion
196 is in the range(<=) of 'max_version'.
197
198 :param max_version: maximum microversion to compare for compatibility.
199 Example: '2.30'
200 :returns: True if selected request microversion is compatible with
201 'max_version'. False in other case.
202 """
203 try:
204 req_version_obj = api_version_request.APIVersionRequest(
205 cls.request_microversion)
206 # NOTE(gmann): This is case where this method is used before calling
207 # resource_setup(), where cls.request_microversion is set. There may
208 # not be any such case but still we can handle this case.
209 except AttributeError:
210 request_microversion = (
211 api_version_utils.select_request_microversion(
212 cls.min_microversion,
213 CONF.compute.min_microversion))
214 req_version_obj = api_version_request.APIVersionRequest(
215 request_microversion)
216 max_version_obj = api_version_request.APIVersionRequest(max_version)
217 return req_version_obj <= max_version_obj
218
219 @classmethod
Attila Fazekas305e65b2013-10-29 13:23:07 +0100220 def server_check_teardown(cls):
221 """Checks is the shared server clean enough for subsequent test.
Ken'ichi Ohmichi88363cb2015-11-19 08:00:54 +0000222
Attila Fazekas305e65b2013-10-29 13:23:07 +0100223 Method will delete the server when it's dirty.
224 The setUp method is responsible for creating a new server.
225 Exceptions raised in tearDown class are fails the test case,
Marian Horban6afb0232015-11-10 22:47:12 -0500226 This method supposed to use only by tearDown methods, when
Attila Fazekas305e65b2013-10-29 13:23:07 +0100227 the shared server_id is stored in the server_id of the class.
228 """
229 if getattr(cls, 'server_id', None) is not None:
230 try:
Ken'ichi Ohmichi0eb153c2015-07-13 02:18:25 +0000231 waiters.wait_for_server_status(cls.servers_client,
232 cls.server_id, 'ACTIVE')
Attila Fazekas305e65b2013-10-29 13:23:07 +0100233 except Exception as exc:
234 LOG.exception(exc)
235 cls.servers_client.delete_server(cls.server_id)
Ken'ichi Ohmichie91a0c62015-08-13 02:09:16 +0000236 waiters.wait_for_server_termination(cls.servers_client,
237 cls.server_id)
Attila Fazekas305e65b2013-10-29 13:23:07 +0100238 cls.server_id = None
239 raise
240
241 @classmethod
Joe Gordon8843f0f2015-03-17 15:07:34 -0700242 def create_test_server(cls, validatable=False, volume_backed=False,
Artom Lifshitz68f1e5e2020-04-03 13:12:36 -0400243 validation_resources=None, clients=None, **kwargs):
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000244 """Wrapper utility that returns a test server.
Rohit Karajgidc300b22012-05-04 08:11:00 -0700245
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000246 This wrapper utility calls the common create test server and
247 returns a test server. The purpose of this wrapper is to minimize
248 the impact on the code of the tests already using this
249 function.
Joe Gordon8843f0f2015-03-17 15:07:34 -0700250
251 :param validatable: Whether the server will be pingable or sshable.
252 :param volume_backed: Whether the instance is volume backed or not.
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100253 :param validation_resources: Dictionary of validation resources as
254 returned by `get_class_validation_resources`.
Artom Lifshitz68f1e5e2020-04-03 13:12:36 -0400255 :param clients: Client manager, defaults to os_primary.
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100256 :param kwargs: Extra arguments are passed down to the
257 `compute.create_test_server` call.
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000258 """
zhufl7ae22682016-09-18 15:22:33 +0800259 if 'name' not in kwargs:
260 kwargs['name'] = data_utils.rand_name(cls.__name__ + "-server")
Sergey Nikitin8654e5b2017-06-04 22:09:56 +0400261
262 request_version = api_version_request.APIVersionRequest(
263 cls.request_microversion)
264 v2_37_version = api_version_request.APIVersionRequest('2.37')
265
zhuflff9779c2018-01-04 14:41:40 +0800266 tenant_network = cls.get_tenant_network()
Sergey Nikitin8654e5b2017-06-04 22:09:56 +0400267 # NOTE(snikitin): since microversion v2.37 'networks' field is required
zhuflff9779c2018-01-04 14:41:40 +0800268 if (request_version >= v2_37_version and 'networks' not in kwargs and
269 not tenant_network):
Sergey Nikitin8654e5b2017-06-04 22:09:56 +0400270 kwargs['networks'] = 'none'
271
Artom Lifshitz68f1e5e2020-04-03 13:12:36 -0400272 if clients is None:
273 clients = cls.os_primary
274
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000275 body, servers = compute.create_test_server(
Artom Lifshitz68f1e5e2020-04-03 13:12:36 -0400276 clients,
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000277 validatable,
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100278 validation_resources=validation_resources,
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000279 tenant_network=tenant_network,
Joe Gordon8843f0f2015-03-17 15:07:34 -0700280 volume_backed=volume_backed,
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000281 **kwargs)
Ken'ichi Ohmichi51c8c262013-12-21 03:30:37 +0900282
Andrea Frittoli0d0a3f32017-08-29 18:21:37 +0100283 # For each server schedule wait and delete, so we first delete all
284 # and then wait for all
285 for server in servers:
286 cls.addClassResourceCleanup(waiters.wait_for_server_termination,
Artom Lifshitz68f1e5e2020-04-03 13:12:36 -0400287 clients.servers_client, server['id'])
Andrea Frittoli0d0a3f32017-08-29 18:21:37 +0100288 for server in servers:
289 cls.addClassResourceCleanup(
290 test_utils.call_and_ignore_notfound_exc,
Artom Lifshitz68f1e5e2020-04-03 13:12:36 -0400291 clients.servers_client.delete_server, server['id'])
Sean Dague9b669e32012-12-13 18:40:08 -0500292
David Kranz0fb14292015-02-11 15:55:20 -0500293 return body
Sean Dague9b669e32012-12-13 18:40:08 -0500294
Zhi Kun Liu02e7a7b2014-01-08 16:08:32 +0800295 @classmethod
296 def create_security_group(cls, name=None, description=None):
297 if name is None:
298 name = data_utils.rand_name(cls.__name__ + "-securitygroup")
299 if description is None:
Ken'ichi Ohmichi4937f562015-03-23 00:15:01 +0000300 description = data_utils.rand_name('description')
ghanshyamb610b772015-08-24 17:29:38 +0900301 body = cls.security_groups_client.create_security_group(
302 name=name, description=description)['security_group']
Andrea Frittoli238818c2017-08-29 18:28:11 +0100303 cls.addClassResourceCleanup(
304 test_utils.call_and_ignore_notfound_exc,
305 cls.security_groups_client.delete_security_group,
306 body['id'])
Zhi Kun Liu02e7a7b2014-01-08 16:08:32 +0800307
David Kranz9964b4e2015-02-06 15:45:29 -0500308 return body
Zhi Kun Liu02e7a7b2014-01-08 16:08:32 +0800309
Abhijeet.Jain87dd4452014-04-23 15:51:23 +0530310 @classmethod
Ghanshyam2a180b82014-06-16 13:54:22 +0900311 def create_test_server_group(cls, name="", policy=None):
Abhijeet.Jain87dd4452014-04-23 15:51:23 +0530312 if not name:
313 name = data_utils.rand_name(cls.__name__ + "-Server-Group")
zhufled6d1022020-06-05 16:04:49 +0800314 if cls.is_requested_microversion_compatible('2.63'):
315 policy = policy or ['affinity']
316 if not isinstance(policy, list):
317 policy = [policy]
318 kwargs = {'policies': policy}
319 else:
320 policy = policy or 'affinity'
321 if isinstance(policy, list):
322 policy = policy[0]
323 kwargs = {'policy': policy}
Ken'ichi Ohmichi1f36daa2015-09-30 01:41:34 +0000324 body = cls.server_groups_client.create_server_group(
zhufled6d1022020-06-05 16:04:49 +0800325 name=name, **kwargs)['server_group']
Andrea Frittoli238818c2017-08-29 18:28:11 +0100326 cls.addClassResourceCleanup(
327 test_utils.call_and_ignore_notfound_exc,
328 cls.server_groups_client.delete_server_group,
329 body['id'])
David Kranzae99b9a2015-02-16 13:37:01 -0500330 return body
Abhijeet.Jain87dd4452014-04-23 15:51:23 +0530331
Ghanshyam Mann0208fc02023-07-27 11:57:31 -0700332 def wait_for(self, condition, *args):
Sean Daguef237ccb2013-01-04 15:19:14 -0500333 """Repeatedly calls condition() until a timeout."""
David Kranzcf0040c2012-06-26 09:46:56 -0400334 start_time = int(time.time())
335 while True:
336 try:
Ghanshyam Mann0208fc02023-07-27 11:57:31 -0700337 condition(*args)
Matthew Treinish05d9fb92012-12-07 16:14:05 -0500338 except Exception:
David Kranzcf0040c2012-06-26 09:46:56 -0400339 pass
340 else:
341 return
342 if int(time.time()) - start_time >= self.build_timeout:
Ghanshyam Mann0208fc02023-07-27 11:57:31 -0700343 condition(*args)
David Kranzcf0040c2012-06-26 09:46:56 -0400344 return
345 time.sleep(self.build_interval)
Jay Pipesf38eaac2012-06-21 13:37:35 -0400346
Attila Fazekas423834d2014-03-14 17:33:13 +0100347 @classmethod
348 def prepare_instance_network(cls):
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000349 if (CONF.validation.auth_method != 'disabled' and
350 CONF.validation.connect_method == 'floating'):
Attila Fazekas423834d2014-03-14 17:33:13 +0100351 cls.set_network_resources(network=True, subnet=True, router=True,
352 dhcp=True)
353
ivan-zhu8f992be2013-07-31 14:56:58 +0800354 @classmethod
355 def create_image_from_server(cls, server_id, **kwargs):
Matt Riedemannf110a4b2018-01-08 15:03:36 -0500356 """Wrapper utility that returns an image created from the server.
357
358 If compute microversion >= 2.36, the returned image response will
359 be from the image service API rather than the compute image proxy API.
360 """
zhufl35a694b2017-02-14 17:10:53 +0800361 name = kwargs.pop('name',
362 data_utils.rand_name(cls.__name__ + "-image"))
363 wait_until = kwargs.pop('wait_until', None)
364 wait_for_server = kwargs.pop('wait_for_server', True)
ivan-zhu8f992be2013-07-31 14:56:58 +0800365
zhufl35a694b2017-02-14 17:10:53 +0800366 image = cls.compute_images_client.create_image(server_id, name=name,
367 **kwargs)
Felipe Monteiroe65ec452017-09-26 06:47:03 +0100368 if api_version_utils.compare_version_header_to_response(
369 "OpenStack-API-Version", "compute 2.45", image.response, "lt"):
370 image_id = image['image_id']
371 else:
372 image_id = data_utils.parse_image_id(image.response['location'])
Matt Riedemannf110a4b2018-01-08 15:03:36 -0500373
374 # The compute image proxy APIs were deprecated in 2.35 so
375 # use the images client directly if the API microversion being
376 # used is >=2.36.
zhufl66275c22018-03-28 15:32:14 +0800377 if not cls.is_requested_microversion_compatible('2.35'):
Matt Riedemannf110a4b2018-01-08 15:03:36 -0500378 client = cls.images_client
379 else:
380 client = cls.compute_images_client
Andrea Frittolib17f7a32017-08-29 17:45:58 +0100381 cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
Matt Riedemannf110a4b2018-01-08 15:03:36 -0500382 client.delete_image, image_id)
ivan-zhu8f992be2013-07-31 14:56:58 +0800383
zhufl35a694b2017-02-14 17:10:53 +0800384 if wait_until is not None:
Matt Riedemann13954352017-02-07 14:03:54 -0500385 try:
zhufl66275c22018-03-28 15:32:14 +0800386 wait_until = wait_until.upper()
387 if not cls.is_requested_microversion_compatible('2.35'):
388 wait_until = wait_until.lower()
Matt Riedemannf110a4b2018-01-08 15:03:36 -0500389 waiters.wait_for_image_status(client, image_id, wait_until)
Matt Riedemann13954352017-02-07 14:03:54 -0500390 except lib_exc.NotFound:
zhufl35a694b2017-02-14 17:10:53 +0800391 if wait_until.upper() == 'ACTIVE':
Matt Riedemann13954352017-02-07 14:03:54 -0500392 # If the image is not found after create_image returned
393 # that means the snapshot failed in nova-compute and nova
394 # deleted the image. There should be a compute fault
395 # recorded with the server in that case, so get the server
396 # and dump some details.
397 server = (
398 cls.servers_client.show_server(server_id)['server'])
399 if 'fault' in server:
400 raise exceptions.SnapshotNotFoundException(
401 server['fault'], image_id=image_id)
402 else:
403 raise exceptions.SnapshotNotFoundException(
404 image_id=image_id)
405 else:
406 raise
Matt Riedemannf110a4b2018-01-08 15:03:36 -0500407 image = client.show_image(image_id)
408 # Compute image client returns response wrapped in 'image' element
409 # which is not the case with Glance image client.
410 if 'image' in image:
411 image = image['image']
ivan-zhu8f992be2013-07-31 14:56:58 +0800412
zhufl35a694b2017-02-14 17:10:53 +0800413 if wait_until.upper() == 'ACTIVE':
414 if wait_for_server:
Bob Ball5fe62392017-02-20 09:51:00 +0000415 waiters.wait_for_server_status(cls.servers_client,
416 server_id, 'ACTIVE')
David Kranza5299eb2015-01-15 17:24:05 -0500417 return image
ivan-zhu8f992be2013-07-31 14:56:58 +0800418
419 @classmethod
Artom Lifshitz4fc47f62022-05-05 14:17:27 -0400420 def recreate_server(cls, server_id, validatable=False, wait_until='ACTIVE',
421 **kwargs):
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100422 """Destroy an existing class level server and creates a new one
423
424 Some test classes use a test server that can be used by multiple
425 tests. This is done to optimise runtime and test load.
426 If something goes wrong with the test server, it can be rebuilt
427 using this helper.
428
429 This helper can also be used for the initial provisioning if no
430 server_id is specified.
431
432 :param server_id: UUID of the server to be rebuilt. If None is
433 specified, a new server is provisioned.
434 :param validatable: whether to the server needs to be
435 validatable. When True, validation resources are acquired via
436 the `get_class_validation_resources` helper.
437 :param kwargs: extra paramaters are passed through to the
438 `create_test_server` call.
439 :return: the UUID of the created server.
440 """
Matthew Treinish2cd19a42013-12-02 21:54:42 +0000441 if server_id:
zhufl9b682902016-12-15 09:16:34 +0800442 cls.delete_server(server_id)
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000443
Ghanshyam3390d9f2015-12-25 12:48:02 +0900444 cls.password = data_utils.rand_password()
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000445 server = cls.create_test_server(
446 validatable,
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100447 validation_resources=cls.get_class_validation_resources(
448 cls.os_primary),
Artom Lifshitz4fc47f62022-05-05 14:17:27 -0400449 wait_until=wait_until,
Ghanshyam3390d9f2015-12-25 12:48:02 +0900450 adminPass=cls.password,
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000451 **kwargs)
Matthew Treinish2cd19a42013-12-02 21:54:42 +0000452 return server['id']
ivan-zhu8f992be2013-07-31 14:56:58 +0800453
Matt Riedemann5dc594c2014-01-27 11:40:28 -0800454 @classmethod
Jesse Keating613b4982015-05-04 15:05:19 -0700455 def delete_server(cls, server_id):
456 """Deletes an existing server and waits for it to be gone."""
457 try:
458 cls.servers_client.delete_server(server_id)
Ken'ichi Ohmichie91a0c62015-08-13 02:09:16 +0000459 waiters.wait_for_server_termination(cls.servers_client,
460 server_id)
Jesse Keating613b4982015-05-04 15:05:19 -0700461 except Exception:
Jordan Pittier525ec712016-12-07 17:51:26 +0100462 LOG.exception('Failed to delete server %s', server_id)
Jesse Keating613b4982015-05-04 15:05:19 -0700463
Balazs Gibizerd8bbaba2022-05-17 17:15:40 +0200464 def resize_server(
465 self, server_id, new_flavor_id, wait_until='ACTIVE', **kwargs
466 ):
zhufl3d018b02016-11-25 16:43:04 +0800467 """resize and confirm_resize an server, waits for it to be ACTIVE."""
Jorge San Emeterio572ac542023-03-09 10:38:56 +0000468 body = self.servers_client.resize_server(
469 server_id, new_flavor_id, **kwargs)
470 waiters.wait_for_server_status(
471 self.servers_client, server_id, 'VERIFY_RESIZE',
472 request_id=body.response['x-openstack-request-id'])
zhuflbcb71172018-03-29 13:49:31 +0800473 self.servers_client.confirm_resize_server(server_id)
Balazs Gibizerd8bbaba2022-05-17 17:15:40 +0200474
zhuflbcb71172018-03-29 13:49:31 +0800475 waiters.wait_for_server_status(
476 self.servers_client, server_id, 'ACTIVE')
477 server = self.servers_client.show_server(server_id)['server']
Balazs Gibizerd8bbaba2022-05-17 17:15:40 +0200478
479 validation_resources = self.get_class_validation_resources(
480 self.os_primary)
481 if (
482 validation_resources and
483 wait_until in ("SSHABLE", "PINGABLE") and
484 CONF.validation.run_validation
485 ):
486 tenant_network = self.get_tenant_network()
487 compute.wait_for_ssh_or_ping(
488 server, self.os_primary, tenant_network,
489 True, validation_resources, wait_until, True)
490
zhuflbcb71172018-03-29 13:49:31 +0800491 self.assert_flavor_equal(new_flavor_id, server['flavor'])
zhufl3d018b02016-11-25 16:43:04 +0800492
Artom Lifshitzea2b59c2021-08-19 14:34:00 -0400493 def reboot_server(self, server_id, type):
494 """Reboot a server and wait for it to be ACTIVE."""
495 self.servers_client.reboot_server(server_id, type=type)
496 waiters.wait_for_server_status(
497 self.servers_client, server_id, 'ACTIVE')
498
zhufl3d018b02016-11-25 16:43:04 +0800499 @classmethod
Matt Riedemann5dc594c2014-01-27 11:40:28 -0800500 def delete_volume(cls, volume_id):
501 """Deletes the given volume and waits for it to be gone."""
zhufldecdcf62017-09-13 10:27:28 +0800502 try:
503 cls.volumes_client.delete_volume(volume_id)
504 # TODO(mriedem): We should move the wait_for_resource_deletion
505 # into the delete_volume method as a convenience to the caller.
506 cls.volumes_client.wait_for_resource_deletion(volume_id)
507 except lib_exc.NotFound:
508 LOG.warning("Unable to delete volume '%s' since it was not found. "
509 "Maybe it was already deleted?", volume_id)
Ken'ichi Ohmichi543a5d42014-05-02 08:44:15 +0900510
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000511 @classmethod
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100512 def get_server_ip(cls, server, validation_resources=None):
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000513 """Get the server fixed or floating IP.
514
Sean Dague20e98612016-01-06 14:33:28 -0500515 Based on the configuration we're in, return a correct ip
516 address for validating that a guest is up.
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100517
518 :param server: The server dict as returned by the API
519 :param validation_resources: The dict of validation resources
520 provisioned for the server.
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000521 """
Lee Yarwood20556df2021-11-12 09:38:18 +0000522 return compute.get_server_ip(
523 server, validation_resources=validation_resources)
Joseph Lanouxffe09dd2015-03-18 16:45:33 +0000524
Matt Riedemann342b37c2016-09-21 15:38:12 -0400525 @classmethod
zhufl8d23f922016-12-12 17:29:42 +0800526 def create_volume(cls, image_ref=None, **kwargs):
Matt Riedemann342b37c2016-09-21 15:38:12 -0400527 """Create a volume and wait for it to become 'available'.
528
Artom Lifshitzfc8f8e62016-04-13 11:08:32 +0000529 :param image_ref: Specify an image id to create a bootable volume.
Dan Smith5e2019f2023-07-21 10:38:59 -0700530 :param wait_for_available: Wait until the volume becomes available
531 before returning
Sergey Vilgelmeac094a2018-11-21 18:27:51 -0600532 :param kwargs: other parameters to create volume.
Matt Riedemann342b37c2016-09-21 15:38:12 -0400533 :returns: The available volume.
534 """
zhufl8d23f922016-12-12 17:29:42 +0800535 if 'size' not in kwargs:
536 kwargs['size'] = CONF.volume.volume_size
537 if 'display_name' not in kwargs:
538 vol_name = data_utils.rand_name(cls.__name__ + '-volume')
539 kwargs['display_name'] = vol_name
Artom Lifshitzfc8f8e62016-04-13 11:08:32 +0000540 if image_ref is not None:
zhufl8d23f922016-12-12 17:29:42 +0800541 kwargs['imageRef'] = image_ref
Dan Smith5e2019f2023-07-21 10:38:59 -0700542 wait = kwargs.pop('wait_for_available', True)
Sophie Huangdba4c9d2021-06-11 16:26:32 +0000543 if CONF.volume.volume_type and 'volume_type' not in kwargs:
544 # If volume_type is not provided in config then no need to
545 # add a volume type and
546 # if volume_type has already been added by child class then
547 # no need to override.
548 kwargs['volume_type'] = CONF.volume.volume_type
Martin Kopec00e6d6c2019-06-05 14:30:06 +0000549 if CONF.compute.compute_volume_common_az:
550 kwargs.setdefault('availability_zone',
551 CONF.compute.compute_volume_common_az)
zhufl8d23f922016-12-12 17:29:42 +0800552 volume = cls.volumes_client.create_volume(**kwargs)['volume']
Andrea Frittoli1fc499e2017-08-29 18:33:03 +0100553 cls.addClassResourceCleanup(
554 cls.volumes_client.wait_for_resource_deletion, volume['id'])
555 cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
556 cls.volumes_client.delete_volume,
557 volume['id'])
Dan Smith5e2019f2023-07-21 10:38:59 -0700558 if wait:
559 waiters.wait_for_volume_resource_status(cls.volumes_client,
560 volume['id'], 'available')
Matt Riedemann342b37c2016-09-21 15:38:12 -0400561 return volume
562
Matt Riedemann0d4551b2017-10-10 13:00:48 -0400563 def _detach_volume(self, server, volume):
564 """Helper method to detach a volume.
565
566 Ignores 404 responses if the volume or server do not exist, or the
567 volume is already detached from the server.
568 """
569 try:
570 volume = self.volumes_client.show_volume(volume['id'])['volume']
571 # Check the status. You can only detach an in-use volume, otherwise
572 # the compute API will return a 400 response.
573 if volume['status'] == 'in-use':
574 self.servers_client.detach_volume(server['id'], volume['id'])
Eric Friedb3eab672017-11-21 12:42:18 -0600575 except lib_exc.NotFound:
Matt Riedemann0d4551b2017-10-10 13:00:48 -0400576 # Ignore 404s on detach in case the server is deleted or the volume
577 # is already detached.
578 pass
579
Dan Smithd3155552023-02-27 06:48:38 -0800580 def attach_volume(self, server, volume, device=None, tag=None,
581 wait_for_detach=True):
Matt Riedemanncb16a662016-10-01 18:30:05 -0400582 """Attaches volume to server and waits for 'in-use' volume status.
583
584 The volume will be detached when the test tears down.
585
586 :param server: The server to which the volume will be attached.
587 :param volume: The volume to attach.
588 :param device: Optional mountpoint for the attached volume. Note that
589 this is not guaranteed for all hypervisors and is not recommended.
Artom Lifshitzb6b2bba2016-10-31 14:56:40 -0400590 :param tag: Optional device role tag to apply to the volume.
Matt Riedemanncb16a662016-10-01 18:30:05 -0400591 """
592 attach_kwargs = dict(volumeId=volume['id'])
593 if device:
594 attach_kwargs['device'] = device
Artom Lifshitzb6b2bba2016-10-31 14:56:40 -0400595 if tag:
596 attach_kwargs['tag'] = tag
597
zhufl36f0a972017-02-28 15:43:33 +0800598 attachment = self.servers_client.attach_volume(
599 server['id'], **attach_kwargs)['volumeAttachment']
Lee Yarwood1bd60592021-06-04 10:18:35 +0100600
601 # NOTE(lyarwood): During attach we initially wait for the volume
602 # attachment and then check the volume state.
603 waiters.wait_for_volume_attachment_create(
604 self.volumes_client, volume['id'], server['id'])
605 # TODO(lyarwood): Remove the following volume status checks and move to
606 # attachment status checks across all volumes now with the 3.27
607 # microversion somehow.
608 if not volume['multiattach']:
609 waiters.wait_for_volume_resource_status(
610 self.volumes_client, volume['id'], 'in-use')
611
612 # NOTE(lyarwood): On teardown (LIFO) initially wait for the volume
613 # attachment in Nova to be removed. While this technically happens last
614 # we want this to be the first waiter as if it fails we can then dump
615 # the contents of the console log. The final check of the volume state
616 # should be a no-op by this point and is just added for completeness
617 # when detaching non-multiattach volumes.
Dan Smithd3155552023-02-27 06:48:38 -0800618 if not volume['multiattach'] and wait_for_detach:
Lee Yarwood1bd60592021-06-04 10:18:35 +0100619 self.addCleanup(
620 waiters.wait_for_volume_resource_status, self.volumes_client,
621 volume['id'], 'available')
622 self.addCleanup(
623 waiters.wait_for_volume_attachment_remove_from_server,
624 self.servers_client, server['id'], volume['id'])
Matt Riedemann0d4551b2017-10-10 13:00:48 -0400625 self.addCleanup(self._detach_volume, server, volume)
Lee Yarwood1bd60592021-06-04 10:18:35 +0100626
zhufl36f0a972017-02-28 15:43:33 +0800627 return attachment
Matt Riedemann342b37c2016-09-21 15:38:12 -0400628
Lee Yarwood2ad7ca42020-01-07 13:06:25 +0000629 def create_volume_snapshot(self, volume_id, name=None, description=None,
630 metadata=None, force=False):
631 name = name or data_utils.rand_name(
632 self.__class__.__name__ + '-snapshot')
633 snapshot = self.snapshots_client.create_snapshot(
634 volume_id=volume_id,
635 force=force,
636 display_name=name,
637 description=description,
638 metadata=metadata)['snapshot']
639 self.addCleanup(self.snapshots_client.wait_for_resource_deletion,
640 snapshot['id'])
641 self.addCleanup(self.snapshots_client.delete_snapshot, snapshot['id'])
642 waiters.wait_for_volume_resource_status(self.snapshots_client,
643 snapshot['id'], 'available')
644 snapshot = self.snapshots_client.show_snapshot(
645 snapshot['id'])['snapshot']
646 return snapshot
647
zhuflbcb71172018-03-29 13:49:31 +0800648 def assert_flavor_equal(self, flavor_id, server_flavor):
649 """Check whether server_flavor equals to flavor.
650
651 :param flavor_id: flavor id
652 :param server_flavor: flavor info returned by show_server.
653 """
654 # Nova API > 2.46 no longer includes flavor.id, and schema check
655 # will cover whether 'id' should be in flavor
656 if server_flavor.get('id'):
657 msg = ('server flavor is not same as flavor!')
658 self.assertEqual(flavor_id, server_flavor['id'], msg)
659 else:
660 flavor = self.flavors_client.show_flavor(flavor_id)['flavor']
661 self.assertEqual(flavor['name'], server_flavor['original_name'],
662 "original_name in server flavor is not same as "
663 "flavor name!")
664 for key in ['ram', 'vcpus', 'disk']:
665 msg = ('attribute %s in server flavor is not same as '
666 'flavor!' % key)
667 self.assertEqual(flavor[key], server_flavor[key], msg)
668
Ken'ichi Ohmichi543a5d42014-05-02 08:44:15 +0900669
Ken'ichi Ohmichi02a8ccd2015-11-05 06:05:29 +0000670class BaseV2ComputeAdminTest(BaseV2ComputeTest):
Ken'ichi Ohmichibcefa3d2014-05-09 08:14:05 +0900671 """Base test case class for Compute Admin API tests."""
ivan-zhuf2b00502013-10-18 10:06:52 +0800672
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000673 credentials = ['primary', 'admin']
Emily Hugenbruche7991d92014-12-12 16:53:36 +0000674
675 @classmethod
676 def setup_clients(cls):
Ken'ichi Ohmichi02a8ccd2015-11-05 06:05:29 +0000677 super(BaseV2ComputeAdminTest, cls).setup_clients()
Ken'ichi Ohmichi9f5adf82014-12-12 04:01:32 +0000678 cls.availability_zone_admin_client = (
Jordan Pittier8160d312017-04-18 11:52:23 +0200679 cls.os_admin.availability_zone_client)
680 cls.admin_flavors_client = cls.os_admin.flavors_client
681 cls.admin_servers_client = cls.os_admin.servers_client
Artom Lifshitzb0ee03e52021-12-01 14:04:15 -0500682 cls.admin_image_client = cls.os_admin.image_client_v2
Rao Adnan Khanac1aaf62017-04-27 08:01:18 -0500683 cls.admin_assisted_volume_snapshots_client = \
684 cls.os_admin.assisted_volume_snapshots_client
zhufl36eeab02017-01-18 11:49:04 +0800685
686 def create_flavor(self, ram, vcpus, disk, name=None,
687 is_public='True', **kwargs):
688 if name is None:
689 name = data_utils.rand_name(self.__class__.__name__ + "-flavor")
690 id = kwargs.pop('id', data_utils.rand_int_id(start=1000))
691 client = self.admin_flavors_client
692 flavor = client.create_flavor(
693 ram=ram, vcpus=vcpus, disk=disk, name=name,
694 id=id, is_public=is_public, **kwargs)['flavor']
695 self.addCleanup(client.wait_for_resource_deletion, flavor['id'])
696 self.addCleanup(client.delete_flavor, flavor['id'])
697 return flavor
Duc Truong09941202017-06-07 10:15:20 -0700698
zhufl7bc916d2018-08-22 14:47:39 +0800699 @classmethod
700 def get_host_for_server(cls, server_id):
701 server_details = cls.admin_servers_client.show_server(server_id)
Duc Truong09941202017-06-07 10:15:20 -0700702 return server_details['server']['OS-EXT-SRV-ATTR:host']
703
704 def get_host_other_than(self, server_id):
705 source_host = self.get_host_for_server(server_id)
706
Radoslav Gerganov50325e22018-03-29 12:02:04 +0300707 svcs = self.os_admin.services_client.list_services(
708 binary='nova-compute')['services']
Marian Krcmarik6e3f99e2020-02-26 23:27:51 +0100709 hosts = []
710 for svc in svcs:
Maksim Malchukf4970a32022-11-16 23:29:33 +0300711 if svc['host'].endswith('-ironic'):
712 continue
Marian Krcmarik6e3f99e2020-02-26 23:27:51 +0100713 if svc['state'] == 'up' and svc['status'] == 'enabled':
714 if CONF.compute.compute_volume_common_az:
715 if svc['zone'] == CONF.compute.compute_volume_common_az:
716 hosts.append(svc['host'])
717 else:
718 hosts.append(svc['host'])
Duc Truong09941202017-06-07 10:15:20 -0700719
720 for target_host in hosts:
721 if source_host != target_host:
722 return target_host