blob: 0450d9427b1be450ad2fe6511003bfb3582aa837 [file] [log] [blame]
ZhiQiang Fan39f97222013-09-20 04:49:44 +08001# Copyright 2012 OpenStack Foundation
Sean Dague6dbc6da2013-05-08 17:49:46 -04002# Copyright 2013 IBM Corp.
3# All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
Dan Smith49c2b3b2023-04-26 15:52:22 -070017import copy
Martin Kopec02af6a42020-03-03 12:39:12 +000018import os
Sean Dague6dbc6da2013-05-08 17:49:46 -040019import subprocess
20
Sean Dague6dbc6da2013-05-08 17:49:46 -040021import netaddr
Soniya Vyas795ef252020-12-10 19:07:23 +053022
Doug Hellmann583ce2c2015-03-11 14:55:46 +000023from oslo_log import log
Andrey Pavlovc8bd4b12015-08-17 10:20:17 +030024from oslo_serialization import jsonutils as json
Yatin Kumbhareee4924c2016-06-09 15:12:06 +053025from oslo_utils import netutils
Sean Dague6dbc6da2013-05-08 17:49:46 -040026
lanoux5fc14522015-09-21 08:17:35 +000027from tempest.common import compute
Masayuki Igawa4ded9f02014-02-17 15:05:59 +090028from tempest.common.utils.linux import remote_client
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +000029from tempest.common.utils import net_utils
Ken'ichi Ohmichi0eb153c2015-07-13 02:18:25 +000030from tempest.common import waiters
Matthew Treinish6c072292014-01-29 19:15:52 +000031from tempest import config
Giulio Fidente92f77192013-08-26 17:13:28 +020032from tempest import exceptions
Ghanshyam Mann09c4eb92019-06-04 13:07:12 +000033from tempest.lib.common import api_version_utils
Ken'ichi Ohmichibe4fb502017-03-10 10:04:48 -080034from tempest.lib.common.utils import data_utils
Jordan Pittier9e227c52016-02-09 14:35:18 +010035from tempest.lib.common.utils import test_utils
Andrea Frittoli (andreaf)db9672e2016-02-23 14:07:24 -050036from tempest.lib import exceptions as lib_exc
Sean Dague6dbc6da2013-05-08 17:49:46 -040037import tempest.test
Sean Dague6dbc6da2013-05-08 17:49:46 -040038
Matthew Treinish6c072292014-01-29 19:15:52 +000039CONF = config.CONF
Sean Dague6dbc6da2013-05-08 17:49:46 -040040
Attila Fazekasfb7552a2013-08-27 13:02:26 +020041LOG = log.getLogger(__name__)
42
Ghanshyam Mann09c4eb92019-06-04 13:07:12 +000043LATEST_MICROVERSION = 'latest'
44
Sean Dague6dbc6da2013-05-08 17:49:46 -040045
Andrea Frittoli2e733b52014-07-16 14:12:11 +010046class ScenarioTest(tempest.test.BaseTestCase):
Andrea Frittoli486ede72014-09-25 11:50:05 +010047 """Base class for scenario tests. Uses tempest own clients. """
Andrea Frittoli2e733b52014-07-16 14:12:11 +010048
Ghanshyam Mann64281392021-03-24 18:48:38 -050049 credentials = ['primary', 'admin']
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +000050
Ghanshyam Mann09c4eb92019-06-04 13:07:12 +000051 compute_min_microversion = None
52 compute_max_microversion = LATEST_MICROVERSION
53 volume_min_microversion = None
54 volume_max_microversion = LATEST_MICROVERSION
55 placement_min_microversion = None
56 placement_max_microversion = LATEST_MICROVERSION
57
58 @classmethod
59 def skip_checks(cls):
60 super(ScenarioTest, cls).skip_checks()
61 api_version_utils.check_skip_with_microversion(
62 cls.compute_min_microversion, cls.compute_max_microversion,
63 CONF.compute.min_microversion, CONF.compute.max_microversion)
64 api_version_utils.check_skip_with_microversion(
65 cls.volume_min_microversion, cls.volume_max_microversion,
66 CONF.volume.min_microversion, CONF.volume.max_microversion)
67 api_version_utils.check_skip_with_microversion(
68 cls.placement_min_microversion, cls.placement_max_microversion,
69 CONF.placement.min_microversion, CONF.placement.max_microversion)
70
71 @classmethod
72 def resource_setup(cls):
73 super(ScenarioTest, cls).resource_setup()
74 cls.compute_request_microversion = (
75 api_version_utils.select_request_microversion(
76 cls.compute_min_microversion,
77 CONF.compute.min_microversion))
78 cls.volume_request_microversion = (
79 api_version_utils.select_request_microversion(
80 cls.volume_min_microversion,
81 CONF.volume.min_microversion))
82 cls.placement_request_microversion = (
83 api_version_utils.select_request_microversion(
84 cls.placement_min_microversion,
85 CONF.placement.min_microversion))
86
Ghanshyam Mann18b45d72021-12-07 12:37:29 -060087 cls.setup_api_microversion_fixture(
88 compute_microversion=cls.compute_request_microversion,
89 volume_microversion=cls.volume_request_microversion,
90 placement_microversion=cls.placement_request_microversion)
Ghanshyam Mann09c4eb92019-06-04 13:07:12 +000091
Dan Smith49c2b3b2023-04-26 15:52:22 -070092 @classmethod
93 def setup_credentials(cls):
94 # Setting network=True, subnet=True creates a default network
95 cls.set_network_resources(
96 network=True,
97 subnet=True,
98 router=True,
99 dhcp=True)
100 super(ScenarioTest, cls).setup_credentials()
101
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530102 def setup_compute_client(cls):
Ghanshyam Mann1072f502021-03-24 19:15:22 -0500103 """Compute client"""
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530104 cls.compute_images_client = cls.os_primary.compute_images_client
105 cls.keypairs_client = cls.os_primary.keypairs_client
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530106 cls.servers_client = cls.os_primary.servers_client
107 cls.interface_client = cls.os_primary.interfaces_client
Ghanshyam Mann1072f502021-03-24 19:15:22 -0500108 cls.flavors_client = cls.os_primary.flavors_client
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530109
110 def setup_network_client(cls):
111 """Neutron network client"""
112 cls.networks_client = cls.os_primary.networks_client
113 cls.ports_client = cls.os_primary.ports_client
114 cls.routers_client = cls.os_primary.routers_client
115 cls.subnets_client = cls.os_primary.subnets_client
116 cls.floating_ips_client = cls.os_primary.floating_ips_client
117 cls.security_groups_client = cls.os_primary.security_groups_client
118 cls.security_group_rules_client = (
119 cls.os_primary.security_group_rules_client)
120
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +0000121 @classmethod
122 def setup_clients(cls):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530123 """This setup the service clients for the tests"""
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +0000124 super(ScenarioTest, cls).setup_clients()
Jordan Pittier1d2e40f2016-01-05 18:49:14 +0100125 if CONF.service_available.glance:
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700126 if CONF.image_feature_enabled.api_v2:
jeremy.zhang0343be52017-05-25 21:29:57 +0800127 cls.image_client = cls.os_primary.image_client_v2
Matt Riedemann2aa19d42016-06-06 17:45:41 -0400128 else:
Matthew Treinish4217a702016-10-07 17:27:11 -0400129 raise lib_exc.InvalidConfiguration(
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700130 'api_v2 must be True in [image-feature-enabled].')
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530131
132 cls.setup_compute_client(cls)
133 cls.setup_network_client(cls)
Andrea Frittolia6b30152017-08-04 10:46:10 +0100134 if CONF.service_available.cinder:
135 cls.volumes_client = cls.os_primary.volumes_client_latest
136 cls.snapshots_client = cls.os_primary.snapshots_client_latest
lkuchlane20e6a82018-05-08 11:28:46 +0300137 cls.backups_client = cls.os_primary.backups_client_latest
Ivan Kolodyazhnybcfc32e2015-08-06 13:31:36 +0300138
Jordan Pittierf672b7d2016-06-20 18:50:40 +0200139 # ## Test functions library
Jordan Pittierf672b7d2016-06-20 18:50:40 +0200140 # The create_[resource] functions only return body and discard the
141 # resp part which is not used in scenario tests
Andrea Frittoli247058f2014-07-16 16:09:22 +0100142
zhufl1e446b52017-10-16 16:54:57 +0800143 def create_port(self, network_id, client=None, **kwargs):
Martin Kopec9c874412020-12-17 20:43:26 +0000144 """Creates port for the respective network_id
145
146 :param network_id: the id of the network
147 :param client: the client to use, defaults to self.ports_client
148 :param kwargs: additional arguments such as:
149 - namestart - a string to generate a name for the port from
150 - default is self.__class__.__name__
151 - 'binding:vnic_type' - defaults to CONF.network.port_vnic_type
152 - 'binding:profile' - defaults to CONF.network.port_profile
153 """
Eliad Cohenbec2d4d2022-09-14 17:52:59 +0000154
Lenny Verkhovsky136376f2016-06-29 14:33:34 +0300155 if not client:
156 client = self.ports_client
Martin Kopec9c874412020-12-17 20:43:26 +0000157 name = data_utils.rand_name(
158 kwargs.pop('namestart', self.__class__.__name__))
Edan David408a97b2018-01-15 03:52:15 -0500159 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
160 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
161 if CONF.network.port_profile and 'binding:profile' not in kwargs:
162 kwargs['binding:profile'] = CONF.network.port_profile
Lenny Verkhovsky136376f2016-06-29 14:33:34 +0300163 result = client.create_port(
164 name=name,
165 network_id=network_id,
166 **kwargs)
Soniya Vyas0123f522020-09-24 17:43:26 +0530167 self.assertIsNotNone(result, 'Unable to allocate port')
Eliad Cohenbec2d4d2022-09-14 17:52:59 +0000168 port_id = result['port']['id']
Lenny Verkhovsky136376f2016-06-29 14:33:34 +0300169 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Eliad Cohenbec2d4d2022-09-14 17:52:59 +0000170 client.delete_port, port_id)
171 port = waiters.wait_for_port_status(
172 client=client, port_id=port_id, status="DOWN")
173 return port["port"]
Lenny Verkhovsky136376f2016-06-29 14:33:34 +0300174
Martin Kopec30b4d532020-10-16 12:02:43 +0000175 def create_keypair(self, client=None, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530176 """Creates keypair
177
178 Keypair is a public key of OpenSSH key pair used for accessing
179 and create servers
180 Keypair can also be created by a private key for the same purpose
181 Here, the keys are randomly generated[public/private]
182 """
Yair Frieddb6c9e92014-08-06 08:53:13 +0300183 if not client:
184 client = self.keypairs_client
Martin Kopec30b4d532020-10-16 12:02:43 +0000185 if not kwargs.get('name'):
186 kwargs['name'] = data_utils.rand_name(self.__class__.__name__)
Andrea Frittoli247058f2014-07-16 16:09:22 +0100187 # We don't need to create a keypair by pubkey in scenario
Martin Kopec30b4d532020-10-16 12:02:43 +0000188 body = client.create_keypair(**kwargs)
189 self.addCleanup(client.delete_keypair, kwargs['name'])
ghanshyamdee01f22015-08-17 11:41:47 +0900190 return body['keypair']
Andrea Frittoli247058f2014-07-16 16:09:22 +0100191
Anusha Ramineni9aaef8b2016-01-19 10:56:40 +0530192 def create_server(self, name=None, image_id=None, flavor=None,
Dan Smith65744692023-05-04 09:06:41 -0700193 validatable=None, wait_until='ACTIVE',
Jordan Pittierf672b7d2016-06-20 18:50:40 +0200194 clients=None, **kwargs):
lanoux5fc14522015-09-21 08:17:35 +0000195 """Wrapper utility that returns a test server.
Andrea Frittoli247058f2014-07-16 16:09:22 +0100196
lanoux5fc14522015-09-21 08:17:35 +0000197 This wrapper utility calls the common create test server and
198 returns a test server. The purpose of this wrapper is to minimize
199 the impact on the code of the tests already using this
200 function.
Noam Angel6e309952019-01-27 05:52:40 +0000201
202 :param **kwargs:
203 See extra parameters below
204
205 :Keyword Arguments:
206 * *vnic_type* (``string``) --
207 used when launching instances with pre-configured ports.
208 Examples:
209 normal: a traditional virtual port that is either attached
210 to a linux bridge or an openvswitch bridge on a
211 compute node.
212 direct: an SR-IOV port that is directly attached to a VM
213 macvtap: an SR-IOV port that is attached to a VM via a macvtap
214 device.
Tom Stappaerts27fd5cb2020-11-26 12:07:47 +0100215 direct-physical: an SR-IOV port that is directly attached to a
216 VM using physical instead of virtual
217 functions.
218 baremetal: a baremetal port directly attached to a baremetal
219 node.
220 virtio-forwarder: an SR-IOV port that is indirectly attached
221 to a VM using a low-latency vhost-user
222 forwarding process.
Noam Angel6e309952019-01-27 05:52:40 +0000223 Defaults to ``CONF.network.port_vnic_type``.
224 * *port_profile* (``dict``) --
225 This attribute is a dictionary that can be used (with admin
226 credentials) to supply information influencing the binding of
227 the port.
228 example: port_profile = "capabilities:[switchdev]"
229 Defaults to ``CONF.network.port_profile``.
Martin Kopec9c874412020-12-17 20:43:26 +0000230 * *create_port_body* (``dict``) --
231 This attribute is a dictionary of additional arguments to be
232 passed to create_port method.
Andrea Frittoli247058f2014-07-16 16:09:22 +0100233 """
Andrea Frittoli247058f2014-07-16 16:09:22 +0100234
lanoux5fc14522015-09-21 08:17:35 +0000235 # NOTE(jlanoux): As a first step, ssh checks in the scenario
236 # tests need to be run regardless of the run_validation and
237 # validatable parameters and thus until the ssh validation job
238 # becomes voting in CI. The test resources management and IP
239 # association are taken care of in the scenario tests.
240 # Therefore, the validatable parameter is set to false in all
241 # those tests. In this way create_server just return a standard
242 # server and the scenario tests always perform ssh checks.
243
244 # Needed for the cross_tenant_traffic test:
245 if clients is None:
jeremy.zhang0343be52017-05-25 21:29:57 +0800246 clients = self.os_primary
lanoux5fc14522015-09-21 08:17:35 +0000247
zhufl24208c22016-10-25 15:23:48 +0800248 if name is None:
249 name = data_utils.rand_name(self.__class__.__name__ + "-server")
250
Noam Angel6e309952019-01-27 05:52:40 +0000251 vnic_type = kwargs.pop('vnic_type', CONF.network.port_vnic_type)
252 profile = kwargs.pop('port_profile', CONF.network.port_profile)
lanoux5fc14522015-09-21 08:17:35 +0000253
Lenny Verkhovskyfe3a03f2018-02-28 10:19:37 +0000254 # If vnic_type or profile are configured create port for
lanoux5fc14522015-09-21 08:17:35 +0000255 # every network
Lenny Verkhovskyfe3a03f2018-02-28 10:19:37 +0000256 if vnic_type or profile:
lanoux5fc14522015-09-21 08:17:35 +0000257 ports = []
Martin Kopec9c874412020-12-17 20:43:26 +0000258 create_port_body = kwargs.pop('create_port_body', {})
Lenny Verkhovsky69363502016-07-17 16:33:33 +0300259
Lenny Verkhovskyfe3a03f2018-02-28 10:19:37 +0000260 if vnic_type:
261 create_port_body['binding:vnic_type'] = vnic_type
262
263 if profile:
264 create_port_body['binding:profile'] = profile
265
lanoux5fc14522015-09-21 08:17:35 +0000266 if kwargs:
267 # Convert security group names to security group ids
268 # to pass to create_port
269 if 'security_groups' in kwargs:
Thiago Paiva66cded22016-08-15 14:55:58 -0300270 security_groups = \
John Warrenf9606e92015-12-10 12:12:42 -0500271 clients.security_groups_client.list_security_groups(
lanoux5fc14522015-09-21 08:17:35 +0000272 ).get('security_groups')
273 sec_dict = dict([(s['name'], s['id'])
afazekas40fcb9b2019-03-08 11:25:11 +0100274 for s in security_groups])
lanoux5fc14522015-09-21 08:17:35 +0000275
276 sec_groups_names = [s['name'] for s in kwargs.pop(
277 'security_groups')]
278 security_groups_ids = [sec_dict[s]
279 for s in sec_groups_names]
280
281 if security_groups_ids:
282 create_port_body[
283 'security_groups'] = security_groups_ids
Lenny Verkhovsky69363502016-07-17 16:33:33 +0300284 networks = kwargs.pop('networks', [])
285 else:
286 networks = []
lanoux5fc14522015-09-21 08:17:35 +0000287
288 # If there are no networks passed to us we look up
Lenny Verkhovsky136376f2016-06-29 14:33:34 +0300289 # for the project's private networks and create a port.
290 # The same behaviour as we would expect when passing
291 # the call to the clients with no networks
lanoux5fc14522015-09-21 08:17:35 +0000292 if not networks:
293 networks = clients.networks_client.list_networks(
Lenny Verkhovsky136376f2016-06-29 14:33:34 +0300294 **{'router:external': False, 'fields': 'id'})['networks']
295
296 # It's net['uuid'] if networks come from kwargs
297 # and net['id'] if they come from
298 # clients.networks_client.list_networks
lanoux5fc14522015-09-21 08:17:35 +0000299 for net in networks:
Lenny Verkhovsky97f7cea2016-08-15 13:29:48 +0000300 net_id = net.get('uuid', net.get('id'))
Lenny Verkhovsky69363502016-07-17 16:33:33 +0300301 if 'port' not in net:
zhufl1e446b52017-10-16 16:54:57 +0800302 port = self.create_port(network_id=net_id,
303 client=clients.ports_client,
304 **create_port_body)
Lenny Verkhovsky69363502016-07-17 16:33:33 +0300305 ports.append({'port': port['id']})
306 else:
307 ports.append({'port': net['port']})
lanoux5fc14522015-09-21 08:17:35 +0000308 if ports:
309 kwargs['networks'] = ports
310 self.ports = ports
311
312 tenant_network = self.get_tenant_network()
313
Marc Koderer979e4942016-12-08 10:07:59 +0100314 if CONF.compute.compute_volume_common_az:
315 kwargs.setdefault('availability_zone',
316 CONF.compute.compute_volume_common_az)
317
Dan Smith65744692023-05-04 09:06:41 -0700318 kwargs['validatable'] = bool(validatable)
Dan Smith49c2b3b2023-04-26 15:52:22 -0700319 keypair = kwargs.pop('keypair', None)
Dan Smith65744692023-05-04 09:06:41 -0700320 if wait_until == 'SSHABLE' and (
321 kwargs.get('validation_resources') is None):
Dan Smith49c2b3b2023-04-26 15:52:22 -0700322 # NOTE(danms): We should do this whether valdiation is enabled or
323 # not to consistently provide the resources to the
324 # create_test_server() function. If validation is disabled, then
325 # get_test_validation_resources() is basically a no-op for
326 # performance.
327 validation_resources = self.get_test_validation_resources(
328 self.os_primary)
329 if keypair:
330 validation_resources = copy.deepcopy(validation_resources)
331 validation_resources.update(
332 keypair=keypair)
Dan Smith65744692023-05-04 09:06:41 -0700333 kwargs.update({
334 'validatable': (validatable if validatable is not None
335 else True),
336 'validation_resources': validation_resources})
Dan Smith49c2b3b2023-04-26 15:52:22 -0700337 if keypair:
338 kwargs.update({'key_name': keypair['name']})
339
Ferenc Horváthbce1fcf2017-06-07 11:19:51 +0200340 body, _ = compute.create_test_server(
lanoux5fc14522015-09-21 08:17:35 +0000341 clients,
342 tenant_network=tenant_network,
343 wait_until=wait_until,
Anusha Ramineni9aaef8b2016-01-19 10:56:40 +0530344 name=name, flavor=flavor,
345 image_id=image_id, **kwargs)
lanoux5fc14522015-09-21 08:17:35 +0000346
Jordan Pittierf672b7d2016-06-20 18:50:40 +0200347 self.addCleanup(waiters.wait_for_server_termination,
348 clients.servers_client, body['id'])
349 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
350 clients.servers_client.delete_server, body['id'])
lanoux5fc14522015-09-21 08:17:35 +0000351 server = clients.servers_client.show_server(body['id'])['server']
Andrea Frittoli247058f2014-07-16 16:09:22 +0100352 return server
353
Markus Zoeller3d2a21c2015-02-27 12:04:22 +0100354 def create_volume(self, size=None, name=None, snapshot_id=None,
Benny Kopilov7beb2d02022-04-12 20:33:53 +0300355 imageRef=None, volume_type=None, wait_until='available',
Milana Levye9a58a12023-02-21 12:55:20 +0000356 client=None, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530357 """Creates volume
358
359 This wrapper utility creates volume and waits for volume to be
Benny Kopilov7beb2d02022-04-12 20:33:53 +0300360 in 'available' state by default. If wait_until is None, means no wait.
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530361 This method returns the volume's full representation by GET request.
362 """
Milana Levye9a58a12023-02-21 12:55:20 +0000363 if client is None:
364 client = self.volumes_client
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530365
Ken'ichi Ohmichiadb905e2016-08-26 15:16:23 -0700366 if size is None:
367 size = CONF.volume.volume_size
Nuno Santosb746d992016-11-17 15:41:55 -0500368 if imageRef:
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700369 image = self.image_client.show_image(imageRef)
zhufl66275c22018-03-28 15:32:14 +0800370 min_disk = image.get('min_disk')
Nuno Santosb746d992016-11-17 15:41:55 -0500371 size = max(size, min_disk)
Andrea Frittoli247058f2014-07-16 16:09:22 +0100372 if name is None:
zhuflf9d95722016-10-19 16:06:17 +0800373 name = data_utils.rand_name(self.__class__.__name__ + "-volume")
Martin Kopecd3ad5e92020-10-16 14:45:09 +0000374 kwargs.update({'name': name,
375 'snapshot_id': snapshot_id,
376 'imageRef': imageRef,
377 'volume_type': volume_type,
378 'size': size})
Marc Koderer979e4942016-12-08 10:07:59 +0100379
380 if CONF.compute.compute_volume_common_az:
381 kwargs.setdefault('availability_zone',
382 CONF.compute.compute_volume_common_az)
383
Milana Levye9a58a12023-02-21 12:55:20 +0000384 volume = client.create_volume(**kwargs)['volume']
Matt Riedemanne85c2702014-09-10 11:50:13 -0700385
Milana Levye9a58a12023-02-21 12:55:20 +0000386 self.addCleanup(client.wait_for_resource_deletion,
Jordan Pittier5e1741c2016-03-02 18:25:51 +0100387 volume['id'])
Jordan Pittier9e227c52016-02-09 14:35:18 +0100388 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Milana Levye9a58a12023-02-21 12:55:20 +0000389 client.delete_volume, volume['id'])
lkuchlan5cbc00a2017-03-26 11:49:54 +0300390 self.assertEqual(name, volume['name'])
Benny Kopilov7beb2d02022-04-12 20:33:53 +0300391 if wait_until:
Milana Levye9a58a12023-02-21 12:55:20 +0000392 waiters.wait_for_volume_resource_status(client,
Benny Kopilov7beb2d02022-04-12 20:33:53 +0300393 volume['id'], wait_until)
394 # The volume retrieved on creation has a non-up-to-date status.
395 # Retrieval after it becomes active ensures correct details.
Milana Levye9a58a12023-02-21 12:55:20 +0000396 volume = client.show_volume(volume['id'])['volume']
397
Andrea Frittoli247058f2014-07-16 16:09:22 +0100398 return volume
399
lkuchlane20e6a82018-05-08 11:28:46 +0300400 def create_backup(self, volume_id, name=None, description=None,
401 force=False, snapshot_id=None, incremental=False,
Martin Kopec4a140052020-10-16 16:26:55 +0000402 container=None, **kwargs):
403 """Creates a backup of the given volume_id or snapshot_id
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530404
Martin Kopec4a140052020-10-16 16:26:55 +0000405 This wrapper utility creates a backup and waits until it is in
406 'available' state.
407
408 :param volume_id: UUID of the volume to back up
409 :param name: backup name, '$classname-backup' by default
410 :param description: Description of the backup, None by default
411 :param force: boolean whether to backup even if the volume is attached
412 False by default
413 :param snapshot_id: UUID of the source snapshot to back up
414 None by default
415 :param incremental: boolean, False by default
416 :param container: a container name, None by default
417 :param **kwargs: additional parameters per the documentation:
418 https://docs.openstack.org/api-ref/block-storage/v3/
419 #create-a-backup
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530420 """
lkuchlane20e6a82018-05-08 11:28:46 +0300421
422 name = name or data_utils.rand_name(
423 self.__class__.__name__ + "-backup")
Martin Kopec4a140052020-10-16 16:26:55 +0000424 args = {'name': name,
425 'description': description,
426 'force': force,
427 'snapshot_id': snapshot_id,
428 'incremental': incremental,
429 'container': container}
430 args.update(kwargs)
lkuchlane20e6a82018-05-08 11:28:46 +0300431 backup = self.backups_client.create_backup(volume_id=volume_id,
432 **kwargs)['backup']
433 self.addCleanup(self.backups_client.delete_backup, backup['id'])
434 waiters.wait_for_volume_resource_status(self.backups_client,
435 backup['id'], 'available')
436 return backup
437
Martin Kopec4a140052020-10-16 16:26:55 +0000438 def restore_backup(self, backup_id, **kwargs):
439 """Restores a backup given by the backup_id
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530440
Martin Kopec4a140052020-10-16 16:26:55 +0000441 This wrapper utility restores a backup and waits until it is in
442 'available' state.
443
444 :param backup_id: UUID of a backup to restore
445 :param **kwargs: additional parameters per the documentation:
446 https://docs.openstack.org/api-ref/block-storage/v3/
447 #restore-a-backup
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530448 """
449
Martin Kopec4a140052020-10-16 16:26:55 +0000450 body = self.backups_client.restore_backup(backup_id, **kwargs)
451 restore = body['restore']
Sofia Enriquez404b55c2022-05-26 19:33:47 +0000452
453 using_pre_existing_volume = kwargs.get('volume_id', False)
454 if not using_pre_existing_volume:
455 self.addCleanup(self.volumes_client.delete_volume,
456 restore['volume_id'])
457
lkuchlane20e6a82018-05-08 11:28:46 +0300458 waiters.wait_for_volume_resource_status(self.backups_client,
459 backup_id, 'available')
460 waiters.wait_for_volume_resource_status(self.volumes_client,
461 restore['volume_id'],
462 'available')
463 self.assertEqual(backup_id, restore['backup_id'])
464 return restore
465
Martin Kopecbe8ba2c2020-12-17 21:33:32 +0000466 def rebuild_server(self, server_id, image=None, preserve_ephemeral=False,
467 wait=True, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530468 if image is None:
469 image = CONF.compute.image_ref
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530470 LOG.debug("Rebuilding server (id: %s, image: %s, preserve eph: %s)",
471 server_id, image, preserve_ephemeral)
472 self.servers_client.rebuild_server(
473 server_id=server_id,
474 image_ref=image,
475 preserve_ephemeral=preserve_ephemeral,
Martin Kopecbe8ba2c2020-12-17 21:33:32 +0000476 **kwargs)
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530477 if wait:
478 waiters.wait_for_server_status(self.servers_client,
479 server_id, 'ACTIVE')
480
lkuchlan73ed1f32017-07-06 16:22:12 +0300481 def create_volume_snapshot(self, volume_id, name=None, description=None,
Martin Kopeca17cca42020-10-17 16:57:51 +0000482 metadata=None, force=False, **kwargs):
483 """Creates volume's snapshot
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530484
Martin Kopeca17cca42020-10-17 16:57:51 +0000485 This wrapper utility creates volume snapshot and waits for it until
486 it is in 'available' state.
487
488 :param volume_id: UUID of a volume to create snapshot of
489 :param name: name of the snapshot, '$classname-snapshot' by default
490 :param description: description of the snapshot
491 :param metadata: metadata key and value pairs for the snapshot
492 :param force: whether snapshot even when the volume is attached
493 :param **kwargs: additional parameters per the doc
494 https://docs.openstack.org/api-ref/block-storage/v3/
495 #create-a-snapshot
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530496 """
497
lkuchlan73ed1f32017-07-06 16:22:12 +0300498 name = name or data_utils.rand_name(
499 self.__class__.__name__ + '-snapshot')
500 snapshot = self.snapshots_client.create_snapshot(
501 volume_id=volume_id,
502 force=force,
Martin Kopec20c87c72020-10-17 11:42:29 +0000503 name=name,
lkuchlan73ed1f32017-07-06 16:22:12 +0300504 description=description,
Martin Kopeca17cca42020-10-17 16:57:51 +0000505 metadata=metadata,
506 **kwargs)['snapshot']
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530507
lkuchlan73ed1f32017-07-06 16:22:12 +0300508 self.addCleanup(self.snapshots_client.wait_for_resource_deletion,
509 snapshot['id'])
Benny Kopilovd4d49b02021-08-10 18:54:01 +0300510 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
511 self.snapshots_client.delete_snapshot, snapshot['id'])
lkuchlan73ed1f32017-07-06 16:22:12 +0300512 waiters.wait_for_volume_resource_status(self.snapshots_client,
513 snapshot['id'], 'available')
Benny Kopilov11b28002017-12-19 12:46:19 +0200514 snapshot = self.snapshots_client.show_snapshot(
515 snapshot['id'])['snapshot']
lkuchlan73ed1f32017-07-06 16:22:12 +0300516 return snapshot
517
Soniya Vyasfd4dcf92021-02-17 18:12:43 +0530518 def cleanup_volume_type(self, volume_type):
Lee Yarwoodbe64e1a2019-04-09 14:02:12 +0100519 """Clean up a given volume type.
520
521 Ensuring all volumes associated to a type are first removed before
522 attempting to remove the type itself. This includes any image volume
523 cache volumes stored in a separate tenant to the original volumes
524 created from the type.
525 """
526 admin_volume_type_client = self.os_admin.volume_types_client_latest
527 admin_volumes_client = self.os_admin.volumes_client_latest
528 volumes = admin_volumes_client.list_volumes(
529 detail=True, params={'all_tenants': 1})['volumes']
530 type_name = volume_type['name']
531 for volume in [v for v in volumes if v['volume_type'] == type_name]:
532 test_utils.call_and_ignore_notfound_exc(
533 admin_volumes_client.delete_volume, volume['id'])
534 admin_volumes_client.wait_for_resource_deletion(volume['id'])
535 admin_volume_type_client.delete_volume_type(volume_type['id'])
536
Martin Kopec8e673a42020-10-18 17:33:02 +0000537 def create_volume_type(self, client=None, name=None, backend_name=None,
538 **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530539 """Creates volume type
540
541 In a multiple-storage back-end configuration,
542 each back end has a name (volume_backend_name).
543 The name of the back end is declared as an extra-specification
544 of a volume type (such as, volume_backend_name=LVM).
545 When a volume is created, the scheduler chooses an
546 appropriate back end to handle the request, according
547 to the volume type specified by the user.
548 The scheduler uses volume types to explicitly create volumes on
549 specific back ends.
550
551 Before using volume type, a volume type has to be declared
552 to Block Storage. In addition to that, an extra-specification
553 has to be created to link the volume type to a back end name.
554 """
555
scottda61f68ac2016-06-07 12:07:55 -0600556 if not client:
ghanshyam6c682ff2018-08-06 09:54:45 +0000557 client = self.os_admin.volume_types_client_latest
Matt Riedemann514495b2019-05-04 17:34:12 +0000558 if not name:
559 class_name = self.__class__.__name__
560 name = data_utils.rand_name(class_name + '-volume-type')
561 randomized_name = data_utils.rand_name('scenario-type-' + name)
scottda61f68ac2016-06-07 12:07:55 -0600562
563 LOG.debug("Creating a volume type: %s on backend %s",
564 randomized_name, backend_name)
Martin Kopec8e673a42020-10-18 17:33:02 +0000565 extra_specs = kwargs.pop("extra_specs", {})
scottda61f68ac2016-06-07 12:07:55 -0600566 if backend_name:
Martin Kopec8e673a42020-10-18 17:33:02 +0000567 extra_specs.update({"volume_backend_name": backend_name})
scottda61f68ac2016-06-07 12:07:55 -0600568
Martin Kopec8e673a42020-10-18 17:33:02 +0000569 volume_type_resp = client.create_volume_type(
570 name=randomized_name, extra_specs=extra_specs, **kwargs)
571 volume_type = volume_type_resp['volume_type']
572
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530573 self.assertIn('id', volume_type)
Soniya Vyasfd4dcf92021-02-17 18:12:43 +0530574 self.addCleanup(self.cleanup_volume_type, volume_type)
scottda61f68ac2016-06-07 12:07:55 -0600575 return volume_type
576
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500577 def create_security_group(self, security_group_rules_client=None,
578 project_id=None,
579 namestart='secgroup-smoke',
580 security_groups_client=None):
581 if security_group_rules_client is None:
582 security_group_rules_client = self.security_group_rules_client
583 if security_groups_client is None:
584 security_groups_client = self.security_groups_client
585 if project_id is None:
586 project_id = security_groups_client.project_id
587 secgroup = self.create_empty_security_group(
588 namestart=namestart, client=security_groups_client,
589 project_id=project_id)
Andrea Frittoli247058f2014-07-16 16:09:22 +0100590
591 # Add rules to the security group
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500592 rules = self.create_loginable_secgroup_rule(
593 security_group_rules_client=security_group_rules_client,
594 secgroup=secgroup,
595 security_groups_client=security_groups_client)
596 for rule in rules:
597 self.assertEqual(project_id, rule['project_id'])
598 self.assertEqual(secgroup['id'], rule['security_group_id'])
Andrea Frittoli247058f2014-07-16 16:09:22 +0100599 return secgroup
600
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500601 def create_empty_security_group(self, client=None, project_id=None,
602 namestart='secgroup-smoke'):
603 """Create a security group without rules.
604
605 Default rules will be created:
606 - IPv4 egress to any
607 - IPv6 egress to any
608 :param project_id: secgroup will be created in this project
609 :returns: the created security group
610 """
611
612 if client is None:
613 client = self.security_groups_client
614 if not project_id:
615 project_id = client.project_id
616 sg_name = data_utils.rand_name(namestart)
617 sg_desc = sg_name + " description"
618 sg_dict = dict(name=sg_name,
619 description=sg_desc)
620 sg_dict['project_id'] = project_id
621 result = client.create_security_group(**sg_dict)
622
623 secgroup = result['security_group']
624 self.assertEqual(secgroup['name'], sg_name)
625 self.assertEqual(project_id, secgroup['project_id'])
626 self.assertEqual(secgroup['description'], sg_desc)
627
628 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
629 client.delete_security_group, secgroup['id'])
630 return secgroup
631
632 def create_security_group_rule(self, secgroup=None,
633 sec_group_rules_client=None,
634 project_id=None,
635 security_groups_client=None, **kwargs):
636 """Create a rule from a dictionary of rule parameters.
637
638 Create a rule in a secgroup. if secgroup not defined will search for
639 default secgroup in project_id.
640 :param secgroup: the security group.
641 :param project_id: if secgroup not passed -- the tenant in which to
642 search for default secgroup
643 :param kwargs: a dictionary containing rule parameters:
644 for example, to allow incoming ssh:
645 rule = {
646 direction: 'ingress'
647 protocol:'tcp',
648 port_range_min: 22,
649 port_range_max: 22
650 }
651 """
652
653 if sec_group_rules_client is None:
654 sec_group_rules_client = self.security_group_rules_client
655 if security_groups_client is None:
656 security_groups_client = self.security_groups_client
657 if not project_id:
658 project_id = security_groups_client.project_id
659 if secgroup is None:
660 # Get default secgroup for project_id
661 default_secgroups = security_groups_client.list_security_groups(
662 name='default', project_id=project_id)['security_groups']
663 msg = "No default security group for project %s." % (project_id)
664 self.assertNotEmpty(default_secgroups, msg)
665 secgroup = default_secgroups[0]
666
667 ruleset = dict(security_group_id=secgroup['id'],
668 project_id=secgroup['project_id'])
669 ruleset.update(kwargs)
670
671 sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
672 sg_rule = sg_rule['security_group_rule']
673
674 self.assertEqual(secgroup['tenant_id'], sg_rule['tenant_id'])
675 self.assertEqual(secgroup['id'], sg_rule['security_group_id'])
676
677 return sg_rule
678
679 def create_loginable_secgroup_rule(self, security_group_rules_client=None,
680 secgroup=None,
Roman Popelka3b0ccb02022-03-24 10:25:19 +0100681 security_groups_client=None,
682 rulesets=None):
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500683 """Create loginable security group rule by neutron clients by default.
684
685 This function will create:
686 1. egress and ingress tcp port 22 allow rule in order to allow ssh
687 access for ipv4.
688 2. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6.
689 3. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4.
690 """
691
692 if security_group_rules_client is None:
693 security_group_rules_client = self.security_group_rules_client
694 if security_groups_client is None:
695 security_groups_client = self.security_groups_client
Roman Popelka3b0ccb02022-03-24 10:25:19 +0100696 if rulesets is None:
697 rulesets = [
698 dict(
699 # ssh
700 protocol='tcp',
701 port_range_min=22,
702 port_range_max=22,
703 ),
704 dict(
705 # ping
706 protocol='icmp',
707 ),
708 dict(
709 # ipv6-icmp for ping6
710 protocol='icmp',
711 ethertype='IPv6',
712 )
713 ]
714
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500715 rules = []
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500716 sec_group_rules_client = security_group_rules_client
717 for ruleset in rulesets:
718 for r_direction in ['ingress', 'egress']:
719 ruleset['direction'] = r_direction
720 try:
721 sg_rule = self.create_security_group_rule(
722 sec_group_rules_client=sec_group_rules_client,
723 secgroup=secgroup,
724 security_groups_client=security_groups_client,
725 **ruleset)
726 except lib_exc.Conflict as ex:
727 # if rule already exist - skip rule and continue
728 msg = 'Security group rule already exists'
729 if msg not in ex._error_string:
730 raise ex
731 else:
732 self.assertEqual(r_direction, sg_rule['direction'])
733 rules.append(sg_rule)
734
735 return rules
736
zhuflf52c7592017-05-25 13:55:24 +0800737 def get_remote_client(self, ip_address, username=None, private_key=None,
738 server=None):
JordanP3fe2dc32014-11-17 13:06:01 +0100739 """Get a SSH client to a remote server
740
Sergey Vilgelmeac094a2018-11-21 18:27:51 -0600741 :param ip_address: the server floating or fixed IP address to use
742 for ssh validation
743 :param username: name of the Linux account on the remote server
744 :param private_key: the SSH private key to use
745 :param server: server dict, used for debugging purposes
746 :return: a RemoteClient object
JordanP3fe2dc32014-11-17 13:06:01 +0100747 """
Adam Gandelmanc78c7572014-08-28 18:38:55 -0700748
Andrea Frittoli247058f2014-07-16 16:09:22 +0100749 if username is None:
lanoux283273b2015-12-04 03:01:54 -0800750 username = CONF.validation.image_ssh_user
wantwatering896300c2015-03-27 15:17:42 +0800751 # Set this with 'keypair' or others to log in with keypair or
752 # username/password.
lanoux5fc14522015-09-21 08:17:35 +0000753 if CONF.validation.auth_method == 'keypair':
wantwatering896300c2015-03-27 15:17:42 +0800754 password = None
755 if private_key is None:
756 private_key = self.keypair['private_key']
757 else:
lanoux283273b2015-12-04 03:01:54 -0800758 password = CONF.validation.image_ssh_password
wantwatering896300c2015-03-27 15:17:42 +0800759 private_key = None
zhuflf52c7592017-05-25 13:55:24 +0800760 linux_client = remote_client.RemoteClient(
761 ip_address, username, pkey=private_key, password=password,
762 server=server, servers_client=self.servers_client)
763 linux_client.validate_authentication()
Andrea Frittoli247058f2014-07-16 16:09:22 +0100764 return linux_client
765
Lukas Piwowarskib50eabe2020-11-05 15:15:38 +0000766 def image_create(self, name='scenario-img', **kwargs):
Martin Kopec02af6a42020-03-03 12:39:12 +0000767 img_path = CONF.scenario.img_file
768 if not os.path.exists(img_path):
Martin Kopec008950e2020-09-29 08:12:39 +0000769 lib_exc.InvalidConfiguration(
Martin Kopec02af6a42020-03-03 12:39:12 +0000770 'Starting Tempest 25.0.0 release, CONF.scenario.img_file need '
771 'a full path for the image. CONF.scenario.img_dir was '
772 'deprecated and will be removed in the next release. Till '
Martin Kopec008950e2020-09-29 08:12:39 +0000773 'Tempest 25.0.0, old behavior was maintained and kept working '
Martin Kopec02af6a42020-03-03 12:39:12 +0000774 'but starting Tempest 26.0.0, you need to specify the full '
775 'path in CONF.scenario.img_file config option.')
Alessandro Pilottib7c1daa2014-08-16 14:24:13 +0300776 img_container_format = CONF.scenario.img_container_format
777 img_disk_format = CONF.scenario.img_disk_format
Evgeny Antyshev7ba0d5f2015-04-28 13:18:07 +0000778 img_properties = CONF.scenario.img_properties
PranaliD2aa523c2016-06-07 03:54:34 -0400779 LOG.debug("paths: img: %s, container_format: %s, disk_format: %s, "
Martin Kopec02af6a42020-03-03 12:39:12 +0000780 "properties: %s",
Jordan Pittier525ec712016-12-07 17:51:26 +0100781 img_path, img_container_format, img_disk_format,
Martin Kopec02af6a42020-03-03 12:39:12 +0000782 img_properties)
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530783 if img_properties is None:
784 img_properties = {}
785 name = data_utils.rand_name('%s-' % name)
786 params = {
787 'name': name,
788 'container_format': img_container_format,
789 'disk_format': img_disk_format or img_container_format,
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700790 'visibility': 'private'
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530791 }
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700792 # Additional properties are flattened out in the v2 API.
793 if img_properties:
794 params.update(img_properties)
Lukas Piwowarskib50eabe2020-11-05 15:15:38 +0000795 params.update(kwargs)
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530796 body = self.image_client.create_image(**params)
797 image = body['image'] if 'image' in body else body
798 self.addCleanup(self.image_client.delete_image, image['id'])
799 self.assertEqual("queued", image['status'])
800 with open(img_path, 'rb') as image_file:
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700801 self.image_client.store_image_file(image['id'], image_file)
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530802 LOG.debug("image:%s", image['id'])
803 return image['id']
Andrea Frittoli247058f2014-07-16 16:09:22 +0100804
Soniya Vyas1b0cddc2021-01-29 17:28:19 +0530805 def log_console_output(self, servers=None, client=None, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530806 """Console log output"""
Matthew Treinish42a3f3a2014-09-04 15:04:53 -0400807 if not CONF.compute_feature_enabled.console_output:
808 LOG.debug('Console output not supported, cannot log')
809 return
Ihar Hrachyshkaa9dca2b2017-04-04 14:17:11 -0700810 client = client or self.servers_client
Andrea Frittoli247058f2014-07-16 16:09:22 +0100811 if not servers:
Ihar Hrachyshkaa9dca2b2017-04-04 14:17:11 -0700812 servers = client.list_servers()
Andrea Frittoli247058f2014-07-16 16:09:22 +0100813 servers = servers['servers']
814 for server in servers:
Attila Fazekas9a5a1122016-11-08 10:24:57 +0100815 try:
Ihar Hrachyshkaa9dca2b2017-04-04 14:17:11 -0700816 console_output = client.get_console_output(
Lukas Piwowarski91ded042020-10-29 15:15:25 +0000817 server['id'], **kwargs)['output']
Attila Fazekas9a5a1122016-11-08 10:24:57 +0100818 LOG.debug('Console output for %s\nbody=\n%s',
819 server['id'], console_output)
820 except lib_exc.NotFound:
Attila Fazekase1360482016-11-10 11:28:08 +0100821 LOG.debug("Server %s disappeared(deleted) while looking "
Attila Fazekas9a5a1122016-11-08 10:24:57 +0100822 "for the console log", server['id'])
Andrea Frittoli247058f2014-07-16 16:09:22 +0100823
Ken'ichi Ohmichi6e201f52014-10-01 04:21:39 +0000824 def _log_net_info(self, exc):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530825 """network debug is called as part of ssh init"""
Andrey Pavlov64723762015-04-29 06:24:58 +0300826 if not isinstance(exc, lib_exc.SSHTimeout):
Ken'ichi Ohmichi6e201f52014-10-01 04:21:39 +0000827 LOG.debug('Network information on a devstack host')
Ken'ichi Ohmichi6e201f52014-10-01 04:21:39 +0000828
Lukas Piwowarski9ad9ca22020-10-29 15:36:30 +0000829 def create_server_snapshot(self, server, name=None, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530830 """Creates server snapshot"""
nithya-ganesan882595e2014-07-29 18:51:07 +0000831 # Glance client
832 _image_client = self.image_client
833 # Compute client
Ghanshyamae76c122015-12-22 13:41:35 +0900834 _images_client = self.compute_images_client
nithya-ganesan882595e2014-07-29 18:51:07 +0000835 if name is None:
zhuflf9d95722016-10-19 16:06:17 +0800836 name = data_utils.rand_name(self.__class__.__name__ + 'snapshot')
nithya-ganesan882595e2014-07-29 18:51:07 +0000837 LOG.debug("Creating a snapshot image for server: %s", server['name'])
Lukas Piwowarski9ad9ca22020-10-29 15:36:30 +0000838 image = _images_client.create_image(server['id'], name=name, **kwargs)
Benny Kopilov7d2edc22022-06-30 17:22:14 +0300839 # microversion 2.45 and above returns image_id
840 image_id = image.get('image_id') or image.response['location'].split(
841 'images/')[1]
Yaroslav Lobankov2fea4052016-04-19 15:05:57 +0300842 waiters.wait_for_image_status(_image_client, image_id, 'active')
Jordan Pittierf672b7d2016-06-20 18:50:40 +0200843
844 self.addCleanup(_image_client.wait_for_resource_deletion,
845 image_id)
846 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
847 _image_client.delete_image, image_id)
848
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700849 # In glance v2 the additional properties are flattened.
850 snapshot_image = _image_client.show_image(image_id)
851 image_props = snapshot_image
Andrey Pavlovc8bd4b12015-08-17 10:20:17 +0300852
Matt Riedemann2aa19d42016-06-06 17:45:41 -0400853 bdm = image_props.get('block_device_mapping')
Andrey Pavlovc8bd4b12015-08-17 10:20:17 +0300854 if bdm:
855 bdm = json.loads(bdm)
856 if bdm and 'snapshot_id' in bdm[0]:
857 snapshot_id = bdm[0]['snapshot_id']
858 self.addCleanup(
859 self.snapshots_client.wait_for_resource_deletion,
860 snapshot_id)
Jordan Pittier9e227c52016-02-09 14:35:18 +0100861 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
862 self.snapshots_client.delete_snapshot,
863 snapshot_id)
lkuchlan52d7b0d2016-11-07 20:53:19 +0200864 waiters.wait_for_volume_resource_status(self.snapshots_client,
865 snapshot_id,
866 'available')
nithya-ganesan882595e2014-07-29 18:51:07 +0000867 image_name = snapshot_image['name']
868 self.assertEqual(name, image_name)
869 LOG.debug("Created snapshot image %s for server %s",
870 image_name, server['name'])
871 return snapshot_image
872
Milana Levye9a58a12023-02-21 12:55:20 +0000873 def nova_volume_attach(self, server, volume_to_attach,
874 volumes_client=None, servers_client=None,
875 **kwargs):
Soniya Vyasae631132020-08-28 13:37:12 +0530876 """Compute volume attach
877
878 This utility attaches volume from compute and waits for the
879 volume status to be 'in-use' state.
880 """
Milana Levye9a58a12023-02-21 12:55:20 +0000881 if volumes_client is None:
882 volumes_client = self.volumes_client
883 if servers_client is None:
884 servers_client = self.servers_client
885
886 volume = servers_client.attach_volume(
Lukas Piwowarski76819fa2020-10-29 13:46:07 +0000887 server['id'], volumeId=volume_to_attach['id'],
888 **kwargs)['volumeAttachment']
Jordan Pittier7cf64762015-10-14 15:01:12 +0200889 self.assertEqual(volume_to_attach['id'], volume['id'])
Milana Levye9a58a12023-02-21 12:55:20 +0000890 waiters.wait_for_volume_resource_status(volumes_client,
lkuchlan52d7b0d2016-11-07 20:53:19 +0200891 volume['id'], 'in-use')
Lukas Piwowarski76819fa2020-10-29 13:46:07 +0000892 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Milana Levye9a58a12023-02-21 12:55:20 +0000893 self.nova_volume_detach, server, volume,
894 servers_client)
Jordan Pittier7cf64762015-10-14 15:01:12 +0200895 # Return the updated volume after the attachment
Milana Levye9a58a12023-02-21 12:55:20 +0000896 return volumes_client.show_volume(volume['id'])['volume']
Masayuki Igawa1f0ad632014-08-05 13:36:56 +0900897
Milana Levye9a58a12023-02-21 12:55:20 +0000898 def nova_volume_detach(self, server, volume, servers_client=None):
Soniya Vyasae631132020-08-28 13:37:12 +0530899 """Compute volume detach
900
Lee Yarwood5423c532020-12-17 11:24:46 +0000901 This utility detaches the volume from the server and checks whether the
902 volume attachment has been removed from Nova.
Soniya Vyasae631132020-08-28 13:37:12 +0530903 """
Milana Levye9a58a12023-02-21 12:55:20 +0000904 if servers_client is None:
905 servers_client = self.servers_client
906
907 servers_client.detach_volume(server['id'], volume['id'])
Lee Yarwood5423c532020-12-17 11:24:46 +0000908 waiters.wait_for_volume_attachment_remove_from_server(
Milana Levye9a58a12023-02-21 12:55:20 +0000909 servers_client, server['id'], volume['id'])
Jordan Pittier7cf64762015-10-14 15:01:12 +0200910
Steven Hardyda2a8352014-10-02 12:52:20 +0100911 def ping_ip_address(self, ip_address, should_succeed=True,
zhufl0ec74c42017-11-15 14:02:28 +0800912 ping_timeout=None, mtu=None, server=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530913 """ping ip address"""
lanoux5fc14522015-09-21 08:17:35 +0000914 timeout = ping_timeout or CONF.validation.ping_timeout
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +0000915 cmd = ['ping', '-c1', '-w1']
916
917 if mtu:
918 cmd += [
919 # don't fragment
920 '-M', 'do',
921 # ping receives just the size of ICMP payload
922 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
923 ]
924 cmd.append(ip_address)
Aaron Rosena7df13b2014-09-23 09:45:45 -0700925
926 def ping():
927 proc = subprocess.Popen(cmd,
928 stdout=subprocess.PIPE,
929 stderr=subprocess.PIPE)
930 proc.communicate()
Shuquan Huang753629e2015-07-20 08:52:29 +0000931
Aaron Rosena7df13b2014-09-23 09:45:45 -0700932 return (proc.returncode == 0) == should_succeed
933
Jordan Pittier9e227c52016-02-09 14:35:18 +0100934 caller = test_utils.find_test_caller()
Shuquan Huang753629e2015-07-20 08:52:29 +0000935 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
John L. Villalovosa898aec2017-01-13 14:46:46 -0800936 ' expected result is %(should_succeed)s', {
Shuquan Huang753629e2015-07-20 08:52:29 +0000937 'caller': caller, 'ip': ip_address, 'timeout': timeout,
938 'should_succeed':
939 'reachable' if should_succeed else 'unreachable'
940 })
Jordan Pittier35a63752016-08-30 13:09:12 +0200941 result = test_utils.call_until_true(ping, timeout, 1)
Shuquan Huang753629e2015-07-20 08:52:29 +0000942 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
John L. Villalovosa898aec2017-01-13 14:46:46 -0800943 'ping result is %(result)s', {
Shuquan Huang753629e2015-07-20 08:52:29 +0000944 'caller': caller, 'ip': ip_address, 'timeout': timeout,
945 'result': 'expected' if result else 'unexpected'
946 })
zhufl0ec74c42017-11-15 14:02:28 +0800947 if server:
Soniya Vyas1b0cddc2021-01-29 17:28:19 +0530948 self.log_console_output([server])
Shuquan Huang753629e2015-07-20 08:52:29 +0000949 return result
Aaron Rosena7df13b2014-09-23 09:45:45 -0700950
Yair Friedae0e73d2014-11-24 11:56:26 +0200951 def check_vm_connectivity(self, ip_address,
952 username=None,
953 private_key=None,
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +0000954 should_connect=True,
zhufl0ec74c42017-11-15 14:02:28 +0800955 extra_msg="",
956 server=None,
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +0000957 mtu=None):
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +0000958 """Check server connectivity
959
Yair Friedae0e73d2014-11-24 11:56:26 +0200960 :param ip_address: server to test against
961 :param username: server's ssh username
962 :param private_key: server's ssh private key to be used
963 :param should_connect: True/False indicates positive/negative test
964 positive - attempt ping and ssh
965 negative - attempt ping and fail if succeed
zhufl0ec74c42017-11-15 14:02:28 +0800966 :param extra_msg: Message to help with debugging if ``ping_ip_address``
967 fails
968 :param server: The server whose console to log for debugging
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +0000969 :param mtu: network MTU to use for connectivity validation
Yair Friedae0e73d2014-11-24 11:56:26 +0200970
971 :raises: AssertError if the result of the connectivity check does
972 not match the value of the should_connect param
973 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530974
zhufl0ec74c42017-11-15 14:02:28 +0800975 LOG.debug('checking network connections to IP %s with user: %s',
976 ip_address, username)
Yair Friedae0e73d2014-11-24 11:56:26 +0200977 if should_connect:
978 msg = "Timed out waiting for %s to become reachable" % ip_address
979 else:
980 msg = "ip address %s is reachable" % ip_address
zhufl0ec74c42017-11-15 14:02:28 +0800981 if extra_msg:
982 msg = "%s\n%s" % (extra_msg, msg)
Yair Friedae0e73d2014-11-24 11:56:26 +0200983 self.assertTrue(self.ping_ip_address(ip_address,
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +0000984 should_succeed=should_connect,
zhufl0ec74c42017-11-15 14:02:28 +0800985 mtu=mtu, server=server),
Yair Friedae0e73d2014-11-24 11:56:26 +0200986 msg=msg)
987 if should_connect:
988 # no need to check ssh for negative connectivity
zhufl0ec74c42017-11-15 14:02:28 +0800989 try:
990 self.get_remote_client(ip_address, username, private_key,
991 server=server)
992 except Exception:
993 if not extra_msg:
994 extra_msg = 'Failed to ssh to %s' % ip_address
995 LOG.exception(extra_msg)
996 raise
Yair Friedae0e73d2014-11-24 11:56:26 +0200997
Ghanshyam Mann64281392021-03-24 18:48:38 -0500998 def get_server_port_id_and_ip4(self, server, ip_addr=None, **kwargs):
Yair Friedae0e73d2014-11-24 11:56:26 +0200999
Ghanshyam Mann64281392021-03-24 18:48:38 -05001000 if ip_addr and not kwargs.get('fixed_ips'):
1001 kwargs['fixed_ips'] = 'ip_address=%s' % ip_addr
1002 ports = self.os_admin.ports_client.list_ports(
1003 device_id=server['id'], **kwargs)['ports']
Lukas Piwowarskif759bc12020-11-05 10:51:29 +00001004
Ghanshyam Mann64281392021-03-24 18:48:38 -05001005 # A port can have more than one IP address in some cases.
1006 # If the network is dual-stack (IPv4 + IPv6), this port is associated
1007 # with 2 subnets
1008
1009 def _is_active(port):
1010 # NOTE(vsaienko) With Ironic, instances live on separate hardware
1011 # servers. Neutron does not bind ports for Ironic instances, as a
1012 # result the port remains in the DOWN state. This has been fixed
1013 # with the introduction of the networking-baremetal plugin but
1014 # it's not mandatory (and is not used on all stable branches).
1015 return (port['status'] == 'ACTIVE' or
1016 port.get('binding:vnic_type') == 'baremetal')
1017
1018 port_map = [(p["id"], fxip["ip_address"])
1019 for p in ports
1020 for fxip in p["fixed_ips"]
1021 if (netutils.is_valid_ipv4(fxip["ip_address"]) and
1022 _is_active(p))]
1023 inactive = [p for p in ports if p['status'] != 'ACTIVE']
1024 if inactive:
1025 LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
1026
1027 self.assertNotEmpty(port_map,
1028 "No IPv4 addresses found in: %s" % ports)
1029 self.assertEqual(len(port_map), 1,
1030 "Found multiple IPv4 addresses: %s. "
1031 "Unable to determine which port to target."
1032 % port_map)
1033 return port_map[0]
1034
1035 def create_floating_ip(self, server, external_network_id=None,
1036 port_id=None, client=None, **kwargs):
1037 """Create a floating IP and associates to a resource/port on Neutron"""
1038
1039 if not external_network_id:
1040 external_network_id = CONF.network.public_network_id
1041 if not client:
1042 client = self.floating_ips_client
1043 if not port_id:
1044 port_id, ip4 = self.get_server_port_id_and_ip4(server)
1045 else:
1046 ip4 = None
1047
1048 floatingip_kwargs = {
1049 'floating_network_id': external_network_id,
1050 'port_id': port_id,
1051 'tenant_id': server.get('project_id') or server['tenant_id'],
1052 'fixed_ip_address': ip4,
1053 }
1054 if CONF.network.subnet_id:
1055 floatingip_kwargs['subnet_id'] = CONF.network.subnet_id
1056
1057 floatingip_kwargs.update(kwargs)
1058 result = client.create_floatingip(**floatingip_kwargs)
1059 floating_ip = result['floatingip']
1060
Jordan Pittier9e227c52016-02-09 14:35:18 +01001061 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Ghanshyam Mann64281392021-03-24 18:48:38 -05001062 client.delete_floatingip,
Yair Friedae0e73d2014-11-24 11:56:26 +02001063 floating_ip['id'])
Ghanshyam Mann64281392021-03-24 18:48:38 -05001064 return floating_ip
1065
Dan Smith49c2b3b2023-04-26 15:52:22 -07001066 def get_floating_ip(self, server):
1067 """Attempt to get an existing floating ip or a server
1068
1069 If one exists, return it, else return None
1070 """
1071 port_id, ip4 = self.get_server_port_id_and_ip4(server)
1072 ips = self.floating_ips_client.list_floatingips(
1073 floating_network_id=CONF.network.public_network_id,
1074 port_id=port_id)
1075 try:
1076 return ips['floatingips'][0]['floating_ip_address']
1077 except (KeyError, IndexError):
1078 return None
1079
Ghanshyam Mann64281392021-03-24 18:48:38 -05001080 def associate_floating_ip(self, floating_ip, server):
1081 """Associate floating ip to server
1082
1083 This wrapper utility attaches the floating_ip for
1084 the respective port_id of server
1085 """
1086 port_id, _ = self.get_server_port_id_and_ip4(server)
1087 kwargs = dict(port_id=port_id)
1088 floating_ip = self.floating_ips_client.update_floatingip(
1089 floating_ip['id'], **kwargs)['floatingip']
1090 self.assertEqual(port_id, floating_ip['port_id'])
1091 return floating_ip
1092
1093 def disassociate_floating_ip(self, floating_ip):
1094 """Disassociates floating ip
1095
1096 This wrapper utility disassociates given floating ip.
1097 :param floating_ip: a dict which is a return value of
1098 floating_ips_client.create_floatingip method
1099 """
1100 kwargs = dict(port_id=None)
1101 floating_ip = self.floating_ips_client.update_floatingip(
1102 floating_ip['id'], **kwargs)['floatingip']
1103 self.assertIsNone(floating_ip['port_id'])
Yair Friedae0e73d2014-11-24 11:56:26 +02001104 return floating_ip
1105
Sean Dague20e98612016-01-06 14:33:28 -05001106 def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
Lukas Piwowarski25f7ba22020-10-29 14:01:34 +00001107 private_key=None, server=None, username=None,
Dan Smith30eb8782023-08-02 09:35:01 -07001108 fs='vfat'):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301109 """Creates timestamp
1110
1111 This wrapper utility does ssh, creates timestamp and returns the
1112 created timestamp.
1113 """
Sean Dague20e98612016-01-06 14:33:28 -05001114 ssh_client = self.get_remote_client(ip_address,
Slawek Kaplonski79d8b0f2018-07-30 22:43:41 +02001115 private_key=private_key,
Lukas Piwowarski25f7ba22020-10-29 14:01:34 +00001116 server=server,
1117 username=username)
1118
melanie witt2da632a2023-05-31 03:25:06 +00001119 # Default the directory in which to write the timestamp file to /tmp
1120 # and only use the mount_path as the target directory if we mounted
1121 # dev_name to mount_path.
1122 target_dir = '/tmp'
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001123 if dev_name is not None:
Lukas Piwowarski25f7ba22020-10-29 14:01:34 +00001124 ssh_client.make_fs(dev_name, fs=fs)
Ken'ichi Ohmichi4e5a69e2017-03-01 18:15:29 -08001125 ssh_client.exec_command('sudo mount /dev/%s %s' % (dev_name,
1126 mount_path))
melanie witt2da632a2023-05-31 03:25:06 +00001127 target_dir = mount_path
1128 cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % target_dir
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001129 ssh_client.exec_command(cmd_timestamp)
1130 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
melanie witt2da632a2023-05-31 03:25:06 +00001131 % target_dir)
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001132 if dev_name is not None:
Ken'ichi Ohmichi4e5a69e2017-03-01 18:15:29 -08001133 ssh_client.exec_command('sudo umount %s' % mount_path)
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001134 return timestamp
1135
Sean Dague20e98612016-01-06 14:33:28 -05001136 def get_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
Lukas Piwowarski2c230eb2020-10-30 10:09:18 +00001137 private_key=None, server=None, username=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301138 """Returns timestamp
1139
1140 This wrapper utility does ssh and returns the timestamp.
Lukas Piwowarski2c230eb2020-10-30 10:09:18 +00001141
1142 :param ip_address: The floating IP or fixed IP of the remote server
1143 :param dev_name: Name of the device that stores the timestamp
1144 :param mount_path: Path which should be used as mount point for
1145 dev_name
1146 :param private_key: The SSH private key to use for authentication
1147 :param server: Server dict, used for debugging purposes
1148 :param username: Name of the Linux account on the remote server
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301149 """
1150
Sean Dague20e98612016-01-06 14:33:28 -05001151 ssh_client = self.get_remote_client(ip_address,
Slawek Kaplonski79d8b0f2018-07-30 22:43:41 +02001152 private_key=private_key,
Lukas Piwowarski2c230eb2020-10-30 10:09:18 +00001153 server=server,
1154 username=username)
1155
melanie witt2da632a2023-05-31 03:25:06 +00001156 # Default the directory from which to read the timestamp file to /tmp
1157 # and only use the mount_path as the target directory if we mounted
1158 # dev_name to mount_path.
1159 target_dir = '/tmp'
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001160 if dev_name is not None:
Matt Riedemann076685a2015-09-30 14:38:16 -07001161 ssh_client.mount(dev_name, mount_path)
melanie witt2da632a2023-05-31 03:25:06 +00001162 target_dir = mount_path
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001163 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
melanie witt2da632a2023-05-31 03:25:06 +00001164 % target_dir)
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001165 if dev_name is not None:
Ken'ichi Ohmichi4e5a69e2017-03-01 18:15:29 -08001166 ssh_client.exec_command('sudo umount %s' % mount_path)
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001167 return timestamp
1168
Lukas Piwowarskib0642f92020-10-29 14:51:30 +00001169 def get_server_ip(self, server, **kwargs):
Sean Dague20e98612016-01-06 14:33:28 -05001170 """Get the server fixed or floating IP.
1171
1172 Based on the configuration we're in, return a correct ip
1173 address for validating that a guest is up.
Lukas Piwowarskib0642f92020-10-29 14:51:30 +00001174
1175 If CONF.validation.connect_method is floating, then
1176 a floating ip will be created passing kwargs as additional
1177 argument.
Sean Dague20e98612016-01-06 14:33:28 -05001178 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301179
Alexander Gubanovc8829f82015-11-12 10:35:13 +02001180 if CONF.validation.connect_method == 'floating':
Sean Dague20e98612016-01-06 14:33:28 -05001181 # The tests calling this method don't have a floating IP
zhufl0892cb22016-05-06 14:46:00 +08001182 # and can't make use of the validation resources. So the
Sean Dague20e98612016-01-06 14:33:28 -05001183 # method is creating the floating IP there.
Dan Smith49c2b3b2023-04-26 15:52:22 -07001184 fip = self.get_floating_ip(server)
1185 if fip:
1186 # Already have a floating ip, so use it instead of creating
1187 # another
1188 return fip
1189 else:
1190 return self.create_floating_ip(
1191 server, **kwargs)['floating_ip_address']
Sean Dague20e98612016-01-06 14:33:28 -05001192 elif CONF.validation.connect_method == 'fixed':
Matt Riedemanna7782552016-08-08 16:26:01 -04001193 # Determine the network name to look for based on config or creds
1194 # provider network resources.
1195 if CONF.validation.network_for_ssh:
1196 addresses = server['addresses'][
1197 CONF.validation.network_for_ssh]
1198 else:
zhufl7b4a7202017-09-28 10:29:27 +08001199 network = self.get_tenant_network()
Matt Riedemanna7782552016-08-08 16:26:01 -04001200 addresses = (server['addresses'][network['name']]
1201 if network else [])
Sean Dague20e98612016-01-06 14:33:28 -05001202 for address in addresses:
Federico Ressi2d6bcaa2018-04-11 12:37:36 +02001203 if (address['version'] == CONF.validation.ip_version_for_ssh and # noqa
1204 address['OS-EXT-IPS:type'] == 'fixed'):
Sean Dague20e98612016-01-06 14:33:28 -05001205 return address['addr']
zhufl955f82b2016-07-22 11:14:34 +08001206 raise exceptions.ServerUnreachable(server_id=server['id'])
Alexander Gubanovc8829f82015-11-12 10:35:13 +02001207 else:
Matthew Treinish4217a702016-10-07 17:27:11 -04001208 raise lib_exc.InvalidConfiguration()
Alexander Gubanovc8829f82015-11-12 10:35:13 +02001209
zhufl7bc916d2018-08-22 14:47:39 +08001210 @classmethod
1211 def get_host_for_server(cls, server_id):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301212 """Gets host of server"""
1213
zhufl7bc916d2018-08-22 14:47:39 +08001214 server_details = cls.os_admin.servers_client.show_server(server_id)
1215 return server_details['server']['OS-EXT-SRV-ATTR:host']
1216
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001217 def _get_bdm(self, source_id, source_type, delete_on_termination=False):
1218 bd_map_v2 = [{
1219 'uuid': source_id,
1220 'source_type': source_type,
1221 'destination_type': 'volume',
1222 'boot_index': 0,
1223 'delete_on_termination': delete_on_termination}]
1224 return {'block_device_mapping_v2': bd_map_v2}
1225
1226 def boot_instance_from_resource(self, source_id,
1227 source_type,
1228 keypair=None,
1229 security_group=None,
1230 delete_on_termination=False,
Martin Kopecbee673e2020-11-04 09:40:52 +00001231 name=None, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301232 """Boot instance from resource
1233
1234 This wrapper utility boots instance from resource with block device
1235 mapping with source info passed in arguments
1236 """
1237
Martin Kopecbee673e2020-11-04 09:40:52 +00001238 create_kwargs = dict({'image_id': ''})
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001239 if keypair:
Dan Smith49c2b3b2023-04-26 15:52:22 -07001240 create_kwargs['keypair'] = keypair
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001241 if security_group:
1242 create_kwargs['security_groups'] = [
1243 {'name': security_group['name']}]
1244 create_kwargs.update(self._get_bdm(
1245 source_id,
1246 source_type,
1247 delete_on_termination=delete_on_termination))
1248 if name:
1249 create_kwargs['name'] = name
Martin Kopecbee673e2020-11-04 09:40:52 +00001250 create_kwargs.update(kwargs)
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001251
Martin Kopecbee673e2020-11-04 09:40:52 +00001252 return self.create_server(**create_kwargs)
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001253
Martin Kopec0216b372020-11-04 09:32:05 +00001254 def create_volume_from_image(self, **kwargs):
1255 """Create volume from image.
1256
1257 :param image_id: ID of the image to create volume from,
1258 CONF.compute.image_ref by default
1259 :param name: name of the volume,
1260 '$classname-volume-origin' by default
1261 :param **kwargs: additional parameters
1262 """
1263 image_id = kwargs.pop('image_id', CONF.compute.image_ref)
1264 name = kwargs.pop('name', None)
1265 if not name:
1266 namestart = self.__class__.__name__ + '-volume-origin'
1267 name = data_utils.rand_name(namestart)
1268 return self.create_volume(name=name, imageRef=image_id, **kwargs)
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001269
Andrea Frittoli2e733b52014-07-16 14:12:11 +01001270
Andrea Frittoli4971fc82014-09-25 10:22:20 +01001271class NetworkScenarioTest(ScenarioTest):
Yair Fried1fc32a12014-08-04 09:11:30 +03001272 """Base class for network scenario tests.
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001273
Yair Fried1fc32a12014-08-04 09:11:30 +03001274 This class provide helpers for network scenario tests, using the neutron
1275 API. Helpers from ancestor which use the nova network API are overridden
1276 with the neutron API.
1277
1278 This Class also enforces using Neutron instead of novanetwork.
1279 Subclassed tests will be skipped if Neutron is not enabled
1280
1281 """
1282
1283 @classmethod
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001284 def skip_checks(cls):
1285 super(NetworkScenarioTest, cls).skip_checks()
Andrea Frittoli2ddc2632014-09-25 11:03:00 +01001286 if not CONF.service_available.neutron:
1287 raise cls.skipException('Neutron not available')
Yair Fried1fc32a12014-08-04 09:11:30 +03001288
Soniya Vyas3bdafd82021-02-22 18:59:27 +05301289 def create_network(self, networks_client=None,
1290 project_id=None,
1291 namestart='network-smoke-',
1292 port_security_enabled=True, **net_dict):
John Warren94d8faf2015-09-15 12:22:24 -04001293 if not networks_client:
1294 networks_client = self.networks_client
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001295 if not project_id:
1296 project_id = networks_client.project_id
Yair Fried1fc32a12014-08-04 09:11:30 +03001297 name = data_utils.rand_name(namestart)
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001298 network_kwargs = dict(name=name, project_id=project_id)
Lajos Katonac87a06b2019-01-04 13:21:48 +01001299 if net_dict:
1300 network_kwargs.update(net_dict)
Matt Riedemann039b2fe2016-09-15 16:12:24 -04001301 # Neutron disables port security by default so we have to check the
1302 # config before trying to create the network with port_security_enabled
1303 if CONF.network_feature_enabled.port_security:
1304 network_kwargs['port_security_enabled'] = port_security_enabled
Markus Zoeller156b5da2016-07-11 18:10:31 +02001305 result = networks_client.create_network(**network_kwargs)
Steve Heyman33735f22016-05-24 09:28:08 -05001306 network = result['network']
1307
1308 self.assertEqual(network['name'], name)
Jordan Pittier9e227c52016-02-09 14:35:18 +01001309 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
zhoubin508bf20b32017-02-03 09:39:14 +08001310 networks_client.delete_network,
Steve Heyman33735f22016-05-24 09:28:08 -05001311 network['id'])
Yair Fried1fc32a12014-08-04 09:11:30 +03001312 return network
1313
zhufl5b0a52f2017-10-24 15:48:20 +08001314 def create_subnet(self, network, subnets_client=None,
1315 namestart='subnet-smoke', **kwargs):
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001316 """Create a subnet for the given network
1317
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301318 This utility creates subnet for the given network
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001319 within the cidr block configured for tenant networks.
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301320
1321 :param **kwargs:
1322 See extra parameters below
1323
1324 :Keyword Arguments:
1325
1326 * *ip_version = ip version of the given network,
Soniya Vyas795ef252020-12-10 19:07:23 +05301327 use_default_subnetpool = default subnetpool to
1328 manage IPv6 addresses range.
Yair Fried1fc32a12014-08-04 09:11:30 +03001329 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301330
John Warren3961acd2015-10-02 14:38:53 -04001331 if not subnets_client:
1332 subnets_client = self.subnets_client
Yair Fried1fc32a12014-08-04 09:11:30 +03001333
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001334 def cidr_in_use(cidr, project_id):
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001335 """Check cidr existence
1336
yangjianfeng4ad346e2020-11-22 06:49:19 +00001337 :returns: True if subnet with cidr already exist in tenant or
1338 external False else
Yair Fried1fc32a12014-08-04 09:11:30 +03001339 """
yangjianfeng4ad346e2020-11-22 06:49:19 +00001340 tenant_subnets = self.os_admin.subnets_client.list_subnets(
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001341 project_id=project_id, cidr=cidr)['subnets']
yangjianfeng4ad346e2020-11-22 06:49:19 +00001342 external_nets = self.os_admin.networks_client.list_networks(
1343 **{"router:external": True})['networks']
1344 external_subnets = []
1345 for ext_net in external_nets:
1346 external_subnets.extend(
1347 self.os_admin.subnets_client.list_subnets(
1348 network_id=ext_net['id'], cidr=cidr)['subnets'])
1349 return len(tenant_subnets + external_subnets) != 0
Yair Fried1fc32a12014-08-04 09:11:30 +03001350
Soniya Vyas795ef252020-12-10 19:07:23 +05301351 def _make_create_subnet_request(namestart, network,
1352 ip_version, subnets_client, **kwargs):
Yair Fried1fc32a12014-08-04 09:11:30 +03001353
1354 subnet = dict(
1355 name=data_utils.rand_name(namestart),
Steve Heyman33735f22016-05-24 09:28:08 -05001356 network_id=network['id'],
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001357 project_id=network['project_id'],
Kirill Shileev14113572014-11-21 16:58:02 +03001358 ip_version=ip_version,
Yair Fried1fc32a12014-08-04 09:11:30 +03001359 **kwargs
1360 )
Soniya Vyas795ef252020-12-10 19:07:23 +05301361
1362 if ip_version == 6:
1363 subnet['ipv6_address_mode'] = 'slaac'
1364 subnet['ipv6_ra_mode'] = 'slaac'
1365
Yair Fried1fc32a12014-08-04 09:11:30 +03001366 try:
Soniya Vyas795ef252020-12-10 19:07:23 +05301367 return subnets_client.create_subnet(**subnet)
Masayuki Igawad9388762015-01-20 14:56:42 +09001368 except lib_exc.Conflict as e:
Soniya Vyas795ef252020-12-10 19:07:23 +05301369 if 'overlaps with another subnet' not in str(e):
Yair Fried1fc32a12014-08-04 09:11:30 +03001370 raise
Soniya Vyas795ef252020-12-10 19:07:23 +05301371
1372 result = None
1373 str_cidr = None
1374
1375 use_default_subnetpool = kwargs.get('use_default_subnetpool', False)
1376 ip_version = kwargs.pop('ip_version', 4)
1377
1378 if not use_default_subnetpool:
1379
1380 if ip_version == 6:
1381 tenant_cidr = netaddr.IPNetwork(
1382 CONF.network.project_network_v6_cidr)
1383 num_bits = CONF.network.project_network_v6_mask_bits
1384 else:
1385 tenant_cidr = netaddr.IPNetwork(
1386 CONF.network.project_network_cidr)
1387 num_bits = CONF.network.project_network_mask_bits
1388
1389 # Repeatedly attempt subnet creation with sequential cidr
1390 # blocks until an unallocated block is found.
1391 for subnet_cidr in tenant_cidr.subnet(num_bits):
1392 str_cidr = str(subnet_cidr)
1393 if cidr_in_use(str_cidr, project_id=network['project_id']):
1394 continue
1395 result = _make_create_subnet_request(
1396 namestart, network, ip_version, subnets_client,
1397 cidr=str_cidr, **kwargs)
1398
1399 if result is not None:
1400 break
1401
1402 else:
1403 result = _make_create_subnet_request(
1404 namestart, network, ip_version, subnets_client,
1405 **kwargs)
Yair Fried1fc32a12014-08-04 09:11:30 +03001406 self.assertIsNotNone(result, 'Unable to allocate tenant network')
Steve Heyman33735f22016-05-24 09:28:08 -05001407
1408 subnet = result['subnet']
Soniya Vyas795ef252020-12-10 19:07:23 +05301409 if str_cidr is not None:
1410 self.assertEqual(subnet['cidr'], str_cidr)
Steve Heyman33735f22016-05-24 09:28:08 -05001411
1412 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1413 subnets_client.delete_subnet, subnet['id'])
1414
Yair Fried1fc32a12014-08-04 09:11:30 +03001415 return subnet
1416
Soniya Vyasc37410f2021-02-24 15:26:27 +05301417 def get_network_by_name(self, network_name):
jeremy.zhang5870ff12017-05-25 11:24:23 +08001418 net = self.os_admin.networks_client.list_networks(
Jordan Pittier64e6b442017-02-20 19:29:02 +01001419 name=network_name)['networks']
Ferenc Horváth268ccce2017-06-08 12:39:02 +02001420 self.assertNotEmpty(net,
Adam Gandelman878a5fd2015-03-30 14:33:36 -07001421 "Unable to get network by name: %s" % network_name)
Steve Heyman33735f22016-05-24 09:28:08 -05001422 return net[0]
David Shrewsbury9bac3662014-08-07 15:07:01 -04001423
Yair Fried45f92952014-06-26 05:19:19 +03001424 def check_floating_ip_status(self, floating_ip, status):
Carl Baldwina754e2d2014-10-23 22:47:41 +00001425 """Verifies floatingip reaches the given status
Yair Fried45f92952014-06-26 05:19:19 +03001426
Steve Heyman33735f22016-05-24 09:28:08 -05001427 :param dict floating_ip: floating IP dict to check status
Yair Fried45f92952014-06-26 05:19:19 +03001428 :param status: target status
1429 :raises: AssertionError if status doesn't match
1430 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301431
Steve Heyman33735f22016-05-24 09:28:08 -05001432 floatingip_id = floating_ip['id']
1433
Carl Baldwina754e2d2014-10-23 22:47:41 +00001434 def refresh():
Martin Kopecf4b5df62020-01-27 09:44:29 +00001435 floating_ip = (self.floating_ips_client.
1436 show_floatingip(floatingip_id)['floatingip'])
1437 if status == floating_ip['status']:
1438 LOG.info("FloatingIP: {fp} is at status: {st}"
1439 .format(fp=floating_ip, st=status))
1440 return status == floating_ip['status']
Carl Baldwina754e2d2014-10-23 22:47:41 +00001441
zhufl4dda94e2017-03-14 16:14:46 +08001442 if not test_utils.call_until_true(refresh,
1443 CONF.network.build_timeout,
1444 CONF.network.build_interval):
1445 floating_ip = self.floating_ips_client.show_floatingip(
1446 floatingip_id)['floatingip']
1447 self.assertEqual(status, floating_ip['status'],
1448 message="FloatingIP: {fp} is at status: {cst}. "
1449 "failed to reach status: {st}"
1450 .format(fp=floating_ip, cst=floating_ip['status'],
1451 st=status))
Yair Fried45f92952014-06-26 05:19:19 +03001452
zhufl420a0192017-09-28 11:04:50 +08001453 def check_tenant_network_connectivity(self, server,
1454 username,
1455 private_key,
1456 should_connect=True,
1457 servers_for_debug=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301458 """Checks tenant network connectivity"""
Sean Dagueed6e5862016-04-04 10:49:13 -04001459 if not CONF.network.project_networks_reachable:
Yair Fried1fc32a12014-08-04 09:11:30 +03001460 msg = 'Tenant networks not configured to be reachable.'
1461 LOG.info(msg)
1462 return
1463 # The target login is assumed to have been configured for
1464 # key-based authentication by cloud-init.
1465 try:
Béla Vancsicsb6dfa082017-03-01 10:44:58 +01001466 for ip_addresses in server['addresses'].values():
Yair Fried1fc32a12014-08-04 09:11:30 +03001467 for ip_address in ip_addresses:
ghanshyam807211c2014-12-18 13:21:22 +09001468 self.check_vm_connectivity(ip_address['addr'],
Yair Friedae0e73d2014-11-24 11:56:26 +02001469 username,
1470 private_key,
1471 should_connect=should_connect)
Yair Fried1fc32a12014-08-04 09:11:30 +03001472 except Exception as e:
1473 LOG.exception('Tenant network connectivity check failed')
Soniya Vyas1b0cddc2021-01-29 17:28:19 +05301474 self.log_console_output(servers_for_debug)
Ken'ichi Ohmichi6e201f52014-10-01 04:21:39 +00001475 self._log_net_info(e)
Yair Fried1fc32a12014-08-04 09:11:30 +03001476 raise
1477
zhufle9877c62017-10-13 09:38:19 +08001478 def check_remote_connectivity(self, source, dest, should_succeed=True,
Claudiu Belu33c3e602014-08-28 16:38:01 +03001479 nic=None, protocol='icmp'):
1480 """check server connectivity via source ssh connection
YAMAMOTO Takashi4c3ebb02017-01-25 16:04:30 +09001481
Claudiu Belu33c3e602014-08-28 16:38:01 +03001482 :param source: RemoteClient: an ssh connection from which to execute
1483 the check
1484 :param dest: an IP to check connectivity against
1485 :param should_succeed: boolean should connection succeed or not
1486 :param nic: specific network interface to test connectivity from
1487 :param protocol: the protocol used to test connectivity with.
1488 :returns: True, if the connection succeeded and it was expected to
1489 succeed. False otherwise.
Yair Fried1fc32a12014-08-04 09:11:30 +03001490 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301491
Claudiu Belu33c3e602014-08-28 16:38:01 +03001492 method_name = '%s_check' % protocol
1493 connectivity_checker = getattr(source, method_name)
1494
1495 def connect_remote():
Yair Fried1fc32a12014-08-04 09:11:30 +03001496 try:
Claudiu Belu33c3e602014-08-28 16:38:01 +03001497 connectivity_checker(dest, nic=nic)
Andrey Pavlov64723762015-04-29 06:24:58 +03001498 except lib_exc.SSHExecCommandFailed:
Claudiu Belu33c3e602014-08-28 16:38:01 +03001499 LOG.warning('Failed to check %(protocol)s connectivity for '
1500 'IP %(dest)s via a ssh connection from: %(src)s.',
1501 dict(protocol=protocol, dest=dest,
1502 src=source.ssh_client.host))
Yair Fried1fc32a12014-08-04 09:11:30 +03001503 return not should_succeed
1504 return should_succeed
1505
Claudiu Belu33c3e602014-08-28 16:38:01 +03001506 result = test_utils.call_until_true(connect_remote,
zhufle9877c62017-10-13 09:38:19 +08001507 CONF.validation.ping_timeout, 1)
Ihar Hrachyshkaf9fda2d2017-11-06 13:16:09 -08001508 if result:
1509 return
1510
YAMAMOTO Takashi4c3ebb02017-01-25 16:04:30 +09001511 source_host = source.ssh_client.host
1512 if should_succeed:
1513 msg = "Timed out waiting for %s to become reachable from %s" \
1514 % (dest, source_host)
1515 else:
1516 msg = "%s is reachable from %s" % (dest, source_host)
Soniya Vyas1b0cddc2021-01-29 17:28:19 +05301517 self.log_console_output()
Ihar Hrachyshkaf9fda2d2017-11-06 13:16:09 -08001518 self.fail(msg)
YAMAMOTO Takashi4c3ebb02017-01-25 16:04:30 +09001519
Soniya Vyas73555df2021-03-04 19:05:42 +05301520 def get_router(self, client=None, project_id=None, **kwargs):
Yair Fried1fc32a12014-08-04 09:11:30 +03001521 """Retrieve a router for the given tenant id.
1522
1523 If a public router has been configured, it will be returned.
1524
1525 If a public router has not been configured, but a public
1526 network has, a tenant router will be created and returned that
1527 routes traffic to the public network.
1528 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301529
Yair Frieddb6c9e92014-08-06 08:53:13 +03001530 if not client:
Ken'ichi Ohmichie35f4722015-12-22 04:57:11 +00001531 client = self.routers_client
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001532 if not project_id:
1533 project_id = client.project_id
Yair Fried1fc32a12014-08-04 09:11:30 +03001534 router_id = CONF.network.public_router_id
1535 network_id = CONF.network.public_network_id
1536 if router_id:
David Kranzca4c7e72015-05-27 11:39:19 -04001537 body = client.show_router(router_id)
Steve Heyman33735f22016-05-24 09:28:08 -05001538 return body['router']
Yair Fried1fc32a12014-08-04 09:11:30 +03001539 elif network_id:
Martin Kopec0090a102020-11-03 13:50:19 +00001540 name = kwargs.pop('name', None)
1541 if not name:
1542 namestart = self.__class__.__name__ + '-router'
1543 name = data_utils.rand_name(namestart)
1544
1545 ext_gw_info = kwargs.pop('external_gateway_info', None)
1546 if not ext_gw_info:
1547 ext_gw_info = dict(network_id=network_id)
zhufl3484f992017-10-10 16:18:29 +08001548 router = client.create_router(
Martin Kopec0090a102020-11-03 13:50:19 +00001549 name=name,
1550 admin_state_up=kwargs.get('admin_state_up', True),
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001551 project_id=project_id,
Martin Kopec0090a102020-11-03 13:50:19 +00001552 external_gateway_info=ext_gw_info,
1553 **kwargs)['router']
zhufl3484f992017-10-10 16:18:29 +08001554 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1555 client.delete_router, router['id'])
Yair Fried1fc32a12014-08-04 09:11:30 +03001556 return router
1557 else:
1558 raise Exception("Neither of 'public_router_id' or "
1559 "'public_network_id' has been defined.")
1560
Ghanshyam Mann071d1542021-03-24 19:10:47 -05001561 def setup_network_subnet_with_router(
1562 self, networks_client=None,
1563 routers_client=None, subnets_client=None,
1564 project_id=None, dns_nameservers=None,
1565 port_security_enabled=True, **net_dict):
Yair Fried1fc32a12014-08-04 09:11:30 +03001566 """Create a network with a subnet connected to a router.
1567
David Shrewsbury9bac3662014-08-07 15:07:01 -04001568 The baremetal driver is a special case since all nodes are
1569 on the same shared network.
1570
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001571 :param project_id: id of project to create resources in.
Yair Fried413bf2d2014-11-19 17:07:11 +02001572 :param dns_nameservers: list of dns servers to send to subnet.
Lajos Katonac87a06b2019-01-04 13:21:48 +01001573 :param port_security_enabled: whether or not port_security is enabled
elajkate453fc22019-06-13 15:03:43 +02001574 :param net_dict: a dict containing experimental network information in
Lajos Katonac87a06b2019-01-04 13:21:48 +01001575 a form like this: {'provider:network_type': 'vlan',
1576 'provider:physical_network': 'foo',
1577 'provider:segmentation_id': '42'}
Yair Fried1fc32a12014-08-04 09:11:30 +03001578 :returns: network, subnet, router
1579 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301580
Thiago Paiva66cded22016-08-15 14:55:58 -03001581 if CONF.network.shared_physical_network:
David Shrewsbury9bac3662014-08-07 15:07:01 -04001582 # NOTE(Shrews): This exception is for environments where tenant
1583 # credential isolation is available, but network separation is
1584 # not (the current baremetal case). Likely can be removed when
1585 # test account mgmt is reworked:
1586 # https://blueprints.launchpad.net/tempest/+spec/test-accounts
Adam Gandelman878a5fd2015-03-30 14:33:36 -07001587 if not CONF.compute.fixed_network_name:
1588 m = 'fixed_network_name must be specified in config'
Matthew Treinish4217a702016-10-07 17:27:11 -04001589 raise lib_exc.InvalidConfiguration(m)
Soniya Vyasc37410f2021-02-24 15:26:27 +05301590 network = self.get_network_by_name(
David Shrewsbury9bac3662014-08-07 15:07:01 -04001591 CONF.compute.fixed_network_name)
1592 router = None
1593 subnet = None
1594 else:
Soniya Vyas3bdafd82021-02-22 18:59:27 +05301595 network = self.create_network(
Ken'ichi Ohmichi43e7fcf2016-04-04 11:59:13 -07001596 networks_client=networks_client,
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001597 project_id=project_id,
Lajos Katonac87a06b2019-01-04 13:21:48 +01001598 port_security_enabled=port_security_enabled,
1599 **net_dict)
Soniya Vyas73555df2021-03-04 19:05:42 +05301600 router = self.get_router(client=routers_client,
1601 project_id=project_id)
Ken'ichi Ohmichi43e7fcf2016-04-04 11:59:13 -07001602 subnet_kwargs = dict(network=network,
zhufl5b0a52f2017-10-24 15:48:20 +08001603 subnets_client=subnets_client)
Yair Fried413bf2d2014-11-19 17:07:11 +02001604 # use explicit check because empty list is a valid option
1605 if dns_nameservers is not None:
1606 subnet_kwargs['dns_nameservers'] = dns_nameservers
zhufl5b0a52f2017-10-24 15:48:20 +08001607 subnet = self.create_subnet(**subnet_kwargs)
Steve Heyman33735f22016-05-24 09:28:08 -05001608 if not routers_client:
1609 routers_client = self.routers_client
1610 router_id = router['id']
1611 routers_client.add_router_interface(router_id,
1612 subnet_id=subnet['id'])
1613
1614 # save a cleanup job to remove this association between
1615 # router and subnet
1616 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1617 routers_client.remove_router_interface, router_id,
1618 subnet_id=subnet['id'])
Yair Fried1fc32a12014-08-04 09:11:30 +03001619 return network, subnet, router
1620
1621
Masayuki Igawa1f0ad632014-08-05 13:36:56 +09001622class EncryptionScenarioTest(ScenarioTest):
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001623 """Base class for encryption scenario tests"""
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001624
David Kranz4cc852b2015-03-09 14:57:11 -04001625 @classmethod
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001626 def setup_clients(cls):
1627 super(EncryptionScenarioTest, cls).setup_clients()
ghanshyam6c682ff2018-08-06 09:54:45 +00001628 cls.admin_volume_types_client = cls.os_admin.volume_types_client_latest
ghanshyam3bd0d2b2017-03-23 01:57:28 +00001629 cls.admin_encryption_types_client =\
ghanshyam6c682ff2018-08-06 09:54:45 +00001630 cls.os_admin.encryption_types_client_latest
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001631
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001632 def create_encryption_type(self, client=None, type_id=None, provider=None,
1633 key_size=None, cipher=None,
1634 control_location=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301635 """Creates an encryption type for volume"""
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001636 if not client:
Ken'ichi Ohmichia6ebf622016-08-25 11:52:27 -07001637 client = self.admin_encryption_types_client
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001638 if not type_id:
1639 volume_type = self.create_volume_type()
Masayuki Igawa1f0ad632014-08-05 13:36:56 +09001640 type_id = volume_type['id']
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001641 LOG.debug("Creating an encryption type for volume type: %s", type_id)
Masayuki Igawa1f0ad632014-08-05 13:36:56 +09001642 client.create_encryption_type(
1643 type_id, provider=provider, key_size=key_size, cipher=cipher,
jeremy.zhangb6f67f62018-02-11 09:28:52 +08001644 control_location=control_location)
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001645
lkuchlan3023e752017-06-08 12:53:13 +03001646 def create_encrypted_volume(self, encryption_provider, volume_type,
1647 key_size=256, cipher='aes-xts-plain64',
Ghanshyam Mann51c0f9a2023-07-21 14:09:40 -05001648 control_location='front-end',
1649 wait_until='available'):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301650 """Creates an encrypted volume"""
lkuchlan3023e752017-06-08 12:53:13 +03001651 volume_type = self.create_volume_type(name=volume_type)
1652 self.create_encryption_type(type_id=volume_type['id'],
1653 provider=encryption_provider,
1654 key_size=key_size,
1655 cipher=cipher,
1656 control_location=control_location)
Ghanshyam Mann51c0f9a2023-07-21 14:09:40 -05001657 return self.create_volume(volume_type=volume_type['name'],
1658 wait_until=wait_until)
lkuchlan3023e752017-06-08 12:53:13 +03001659
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001660
Masayuki Igawa0870db52015-09-18 21:08:36 +09001661class ObjectStorageScenarioTest(ScenarioTest):
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001662 """Provide harness to do Object Storage scenario tests.
Chris Dent0d494112014-08-26 13:48:30 +01001663
1664 Subclasses implement the tests that use the methods provided by this
1665 class.
1666 """
1667
Ghanshyam Mann64281392021-03-24 18:48:38 -05001668 credentials = ['primary']
1669
Chris Dent0d494112014-08-26 13:48:30 +01001670 @classmethod
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001671 def skip_checks(cls):
Masayuki Igawa0870db52015-09-18 21:08:36 +09001672 super(ObjectStorageScenarioTest, cls).skip_checks()
Chris Dent0d494112014-08-26 13:48:30 +01001673 if not CONF.service_available.swift:
1674 skip_msg = ("%s skipped as swift is not available" %
1675 cls.__name__)
1676 raise cls.skipException(skip_msg)
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001677
1678 @classmethod
1679 def setup_credentials(cls):
Masayuki Igawa60ea6c52014-10-15 17:32:14 +09001680 cls.set_network_resources()
Masayuki Igawa0870db52015-09-18 21:08:36 +09001681 super(ObjectStorageScenarioTest, cls).setup_credentials()
Matthew Treinish4a596932015-03-06 20:37:01 -05001682 operator_role = CONF.object_storage.operator_role
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +01001683 cls.os_operator = cls.get_client_manager(roles=[operator_role])
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001684
1685 @classmethod
1686 def setup_clients(cls):
Masayuki Igawa0870db52015-09-18 21:08:36 +09001687 super(ObjectStorageScenarioTest, cls).setup_clients()
Chris Dent0d494112014-08-26 13:48:30 +01001688 # Clients for Swift
Matthew Treinish8f268292015-02-24 20:01:36 -05001689 cls.account_client = cls.os_operator.account_client
1690 cls.container_client = cls.os_operator.container_client
1691 cls.object_client = cls.os_operator.object_client
Chris Dent0d494112014-08-26 13:48:30 +01001692
Chris Dentde456a12014-09-10 12:41:15 +01001693 def get_swift_stat(self):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301694 """Get swift status for our user account."""
Chris Dent0d494112014-08-26 13:48:30 +01001695 self.account_client.list_account_containers()
1696 LOG.debug('Swift status information obtained successfully')
1697
Chris Dentde456a12014-09-10 12:41:15 +01001698 def create_container(self, container_name=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301699 """Creates container"""
Chris Dent0d494112014-08-26 13:48:30 +01001700 name = container_name or data_utils.rand_name(
1701 'swift-scenario-container')
ghanshyameed40312017-09-15 18:30:04 +03001702 self.container_client.update_container(name)
Chris Dent0d494112014-08-26 13:48:30 +01001703 # look for the container to assure it is created
Chris Dentde456a12014-09-10 12:41:15 +01001704 self.list_and_check_container_objects(name)
Jordan Pittier525ec712016-12-07 17:51:26 +01001705 LOG.debug('Container %s created', name)
Jordan Pittier9e227c52016-02-09 14:35:18 +01001706 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Chris Dent1d4313a2014-10-28 12:16:48 +00001707 self.container_client.delete_container,
1708 name)
Chris Dent0d494112014-08-26 13:48:30 +01001709 return name
1710
Chris Dentde456a12014-09-10 12:41:15 +01001711 def delete_container(self, container_name):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301712 """Deletes container"""
Chris Dent0d494112014-08-26 13:48:30 +01001713 self.container_client.delete_container(container_name)
Jordan Pittier525ec712016-12-07 17:51:26 +01001714 LOG.debug('Container %s deleted', container_name)
Chris Dent0d494112014-08-26 13:48:30 +01001715
Chris Dentde456a12014-09-10 12:41:15 +01001716 def upload_object_to_container(self, container_name, obj_name=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301717 """Uploads object to container"""
Chris Dent0d494112014-08-26 13:48:30 +01001718 obj_name = obj_name or data_utils.rand_name('swift-scenario-object')
Jordan Pittierb84f2d42016-12-21 19:02:15 +01001719 obj_data = data_utils.random_bytes()
Chris Dent0d494112014-08-26 13:48:30 +01001720 self.object_client.create_object(container_name, obj_name, obj_data)
Jordan Pittier9e227c52016-02-09 14:35:18 +01001721 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Chris Dent1d4313a2014-10-28 12:16:48 +00001722 self.object_client.delete_object,
1723 container_name,
1724 obj_name)
Chris Dent0d494112014-08-26 13:48:30 +01001725 return obj_name, obj_data
1726
Chris Dentde456a12014-09-10 12:41:15 +01001727 def delete_object(self, container_name, filename):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301728 """Deletes object"""
Chris Dent0d494112014-08-26 13:48:30 +01001729 self.object_client.delete_object(container_name, filename)
Chris Dentde456a12014-09-10 12:41:15 +01001730 self.list_and_check_container_objects(container_name,
1731 not_present_obj=[filename])
Chris Dent0d494112014-08-26 13:48:30 +01001732
Chris Dentde456a12014-09-10 12:41:15 +01001733 def list_and_check_container_objects(self, container_name,
1734 present_obj=None,
1735 not_present_obj=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301736 """List and verify objects for a given container
1737
1738 This utility lists objects for a given container
1739 and asserts which are present and
1740 which are not
1741 """
1742
Ghanshyam2a180b82014-06-16 13:54:22 +09001743 if present_obj is None:
1744 present_obj = []
1745 if not_present_obj is None:
1746 not_present_obj = []
ghanshyam871b1a82017-09-14 02:56:16 +03001747 _, object_list = self.container_client.list_container_objects(
Chris Dent0d494112014-08-26 13:48:30 +01001748 container_name)
1749 if present_obj:
1750 for obj in present_obj:
1751 self.assertIn(obj, object_list)
1752 if not_present_obj:
1753 for obj in not_present_obj:
1754 self.assertNotIn(obj, object_list)
1755
Chris Dentde456a12014-09-10 12:41:15 +01001756 def download_and_verify(self, container_name, obj_name, expected_data):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301757 """Asserts the object and expected data to verify if they are same"""
Chris Dent0d494112014-08-26 13:48:30 +01001758 _, obj = self.object_client.get_object(container_name, obj_name)
1759 self.assertEqual(obj, expected_data)