blob: d51e7e5d8bd0b88a492231cdc87d159176525855 [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
melanie witta0b161b2023-12-01 23:41:44 +000019import re
20import shutil
Sean Dague6dbc6da2013-05-08 17:49:46 -040021import subprocess
melanie witta0b161b2023-12-01 23:41:44 +000022import tarfile
23import tempfile
Sean Dague6dbc6da2013-05-08 17:49:46 -040024
Sean Dague6dbc6da2013-05-08 17:49:46 -040025import netaddr
Soniya Vyas795ef252020-12-10 19:07:23 +053026
Doug Hellmann583ce2c2015-03-11 14:55:46 +000027from oslo_log import log
Andrey Pavlovc8bd4b12015-08-17 10:20:17 +030028from oslo_serialization import jsonutils as json
Yatin Kumbhareee4924c2016-06-09 15:12:06 +053029from oslo_utils import netutils
Sean Dague6dbc6da2013-05-08 17:49:46 -040030
lanoux5fc14522015-09-21 08:17:35 +000031from tempest.common import compute
Masayuki Igawa4ded9f02014-02-17 15:05:59 +090032from tempest.common.utils.linux import remote_client
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +000033from tempest.common.utils import net_utils
Ken'ichi Ohmichi0eb153c2015-07-13 02:18:25 +000034from tempest.common import waiters
Matthew Treinish6c072292014-01-29 19:15:52 +000035from tempest import config
Giulio Fidente92f77192013-08-26 17:13:28 +020036from tempest import exceptions
Ghanshyam Mann09c4eb92019-06-04 13:07:12 +000037from tempest.lib.common import api_version_utils
Ken'ichi Ohmichibe4fb502017-03-10 10:04:48 -080038from tempest.lib.common.utils import data_utils
Jordan Pittier9e227c52016-02-09 14:35:18 +010039from tempest.lib.common.utils import test_utils
Andrea Frittoli (andreaf)db9672e2016-02-23 14:07:24 -050040from tempest.lib import exceptions as lib_exc
Sean Dague6dbc6da2013-05-08 17:49:46 -040041import tempest.test
Sean Dague6dbc6da2013-05-08 17:49:46 -040042
Matthew Treinish6c072292014-01-29 19:15:52 +000043CONF = config.CONF
Sean Dague6dbc6da2013-05-08 17:49:46 -040044
Attila Fazekasfb7552a2013-08-27 13:02:26 +020045LOG = log.getLogger(__name__)
46
Ghanshyam Mann09c4eb92019-06-04 13:07:12 +000047LATEST_MICROVERSION = 'latest'
48
Sean Dague6dbc6da2013-05-08 17:49:46 -040049
Andrea Frittoli2e733b52014-07-16 14:12:11 +010050class ScenarioTest(tempest.test.BaseTestCase):
Andrea Frittoli486ede72014-09-25 11:50:05 +010051 """Base class for scenario tests. Uses tempest own clients. """
Andrea Frittoli2e733b52014-07-16 14:12:11 +010052
Ghanshyam Mann64281392021-03-24 18:48:38 -050053 credentials = ['primary', 'admin']
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +000054
Ghanshyam Mann09c4eb92019-06-04 13:07:12 +000055 compute_min_microversion = None
56 compute_max_microversion = LATEST_MICROVERSION
57 volume_min_microversion = None
58 volume_max_microversion = LATEST_MICROVERSION
59 placement_min_microversion = None
60 placement_max_microversion = LATEST_MICROVERSION
61
62 @classmethod
63 def skip_checks(cls):
64 super(ScenarioTest, cls).skip_checks()
65 api_version_utils.check_skip_with_microversion(
66 cls.compute_min_microversion, cls.compute_max_microversion,
67 CONF.compute.min_microversion, CONF.compute.max_microversion)
68 api_version_utils.check_skip_with_microversion(
69 cls.volume_min_microversion, cls.volume_max_microversion,
70 CONF.volume.min_microversion, CONF.volume.max_microversion)
71 api_version_utils.check_skip_with_microversion(
72 cls.placement_min_microversion, cls.placement_max_microversion,
73 CONF.placement.min_microversion, CONF.placement.max_microversion)
74
75 @classmethod
76 def resource_setup(cls):
77 super(ScenarioTest, cls).resource_setup()
78 cls.compute_request_microversion = (
79 api_version_utils.select_request_microversion(
80 cls.compute_min_microversion,
81 CONF.compute.min_microversion))
82 cls.volume_request_microversion = (
83 api_version_utils.select_request_microversion(
84 cls.volume_min_microversion,
85 CONF.volume.min_microversion))
86 cls.placement_request_microversion = (
87 api_version_utils.select_request_microversion(
88 cls.placement_min_microversion,
89 CONF.placement.min_microversion))
90
Ghanshyam Mann18b45d72021-12-07 12:37:29 -060091 cls.setup_api_microversion_fixture(
92 compute_microversion=cls.compute_request_microversion,
93 volume_microversion=cls.volume_request_microversion,
94 placement_microversion=cls.placement_request_microversion)
Ghanshyam Mann09c4eb92019-06-04 13:07:12 +000095
Dan Smith49c2b3b2023-04-26 15:52:22 -070096 @classmethod
97 def setup_credentials(cls):
98 # Setting network=True, subnet=True creates a default network
99 cls.set_network_resources(
100 network=True,
101 subnet=True,
102 router=True,
103 dhcp=True)
104 super(ScenarioTest, cls).setup_credentials()
105
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530106 def setup_compute_client(cls):
Ghanshyam Mann1072f502021-03-24 19:15:22 -0500107 """Compute client"""
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530108 cls.compute_images_client = cls.os_primary.compute_images_client
109 cls.keypairs_client = cls.os_primary.keypairs_client
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530110 cls.servers_client = cls.os_primary.servers_client
111 cls.interface_client = cls.os_primary.interfaces_client
Ghanshyam Mann1072f502021-03-24 19:15:22 -0500112 cls.flavors_client = cls.os_primary.flavors_client
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530113
114 def setup_network_client(cls):
115 """Neutron network client"""
116 cls.networks_client = cls.os_primary.networks_client
117 cls.ports_client = cls.os_primary.ports_client
118 cls.routers_client = cls.os_primary.routers_client
119 cls.subnets_client = cls.os_primary.subnets_client
120 cls.floating_ips_client = cls.os_primary.floating_ips_client
121 cls.security_groups_client = cls.os_primary.security_groups_client
122 cls.security_group_rules_client = (
123 cls.os_primary.security_group_rules_client)
124
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +0000125 @classmethod
126 def setup_clients(cls):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530127 """This setup the service clients for the tests"""
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +0000128 super(ScenarioTest, cls).setup_clients()
Jordan Pittier1d2e40f2016-01-05 18:49:14 +0100129 if CONF.service_available.glance:
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700130 if CONF.image_feature_enabled.api_v2:
jeremy.zhang0343be52017-05-25 21:29:57 +0800131 cls.image_client = cls.os_primary.image_client_v2
Matt Riedemann2aa19d42016-06-06 17:45:41 -0400132 else:
Matthew Treinish4217a702016-10-07 17:27:11 -0400133 raise lib_exc.InvalidConfiguration(
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700134 'api_v2 must be True in [image-feature-enabled].')
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530135
136 cls.setup_compute_client(cls)
137 cls.setup_network_client(cls)
Andrea Frittolia6b30152017-08-04 10:46:10 +0100138 if CONF.service_available.cinder:
139 cls.volumes_client = cls.os_primary.volumes_client_latest
140 cls.snapshots_client = cls.os_primary.snapshots_client_latest
lkuchlane20e6a82018-05-08 11:28:46 +0300141 cls.backups_client = cls.os_primary.backups_client_latest
Ivan Kolodyazhnybcfc32e2015-08-06 13:31:36 +0300142
Jordan Pittierf672b7d2016-06-20 18:50:40 +0200143 # ## Test functions library
Jordan Pittierf672b7d2016-06-20 18:50:40 +0200144 # The create_[resource] functions only return body and discard the
145 # resp part which is not used in scenario tests
Andrea Frittoli247058f2014-07-16 16:09:22 +0100146
zhufl1e446b52017-10-16 16:54:57 +0800147 def create_port(self, network_id, client=None, **kwargs):
Martin Kopec9c874412020-12-17 20:43:26 +0000148 """Creates port for the respective network_id
149
150 :param network_id: the id of the network
151 :param client: the client to use, defaults to self.ports_client
152 :param kwargs: additional arguments such as:
153 - namestart - a string to generate a name for the port from
154 - default is self.__class__.__name__
155 - 'binding:vnic_type' - defaults to CONF.network.port_vnic_type
156 - 'binding:profile' - defaults to CONF.network.port_profile
157 """
Eliad Cohenbec2d4d2022-09-14 17:52:59 +0000158
Lenny Verkhovsky136376f2016-06-29 14:33:34 +0300159 if not client:
160 client = self.ports_client
Martin Kopec9c874412020-12-17 20:43:26 +0000161 name = data_utils.rand_name(
Martin Kopec213d0a42023-11-30 10:28:14 +0100162 prefix=CONF.resource_name_prefix,
163 name=kwargs.pop('namestart', self.__class__.__name__))
Edan David408a97b2018-01-15 03:52:15 -0500164 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
165 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
166 if CONF.network.port_profile and 'binding:profile' not in kwargs:
167 kwargs['binding:profile'] = CONF.network.port_profile
Lenny Verkhovsky136376f2016-06-29 14:33:34 +0300168 result = client.create_port(
169 name=name,
170 network_id=network_id,
171 **kwargs)
Soniya Vyas0123f522020-09-24 17:43:26 +0530172 self.assertIsNotNone(result, 'Unable to allocate port')
Eliad Cohenbec2d4d2022-09-14 17:52:59 +0000173 port_id = result['port']['id']
Lenny Verkhovsky136376f2016-06-29 14:33:34 +0300174 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Eliad Cohenbec2d4d2022-09-14 17:52:59 +0000175 client.delete_port, port_id)
176 port = waiters.wait_for_port_status(
177 client=client, port_id=port_id, status="DOWN")
178 return port["port"]
Lenny Verkhovsky136376f2016-06-29 14:33:34 +0300179
Martin Kopec30b4d532020-10-16 12:02:43 +0000180 def create_keypair(self, client=None, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530181 """Creates keypair
182
183 Keypair is a public key of OpenSSH key pair used for accessing
184 and create servers
185 Keypair can also be created by a private key for the same purpose
186 Here, the keys are randomly generated[public/private]
187 """
Yair Frieddb6c9e92014-08-06 08:53:13 +0300188 if not client:
189 client = self.keypairs_client
Martin Kopec30b4d532020-10-16 12:02:43 +0000190 if not kwargs.get('name'):
Martin Kopec213d0a42023-11-30 10:28:14 +0100191 kwargs['name'] = data_utils.rand_name(
192 prefix=CONF.resource_name_prefix,
193 name=self.__class__.__name__)
Andrea Frittoli247058f2014-07-16 16:09:22 +0100194 # We don't need to create a keypair by pubkey in scenario
Martin Kopec30b4d532020-10-16 12:02:43 +0000195 body = client.create_keypair(**kwargs)
196 self.addCleanup(client.delete_keypair, kwargs['name'])
ghanshyamdee01f22015-08-17 11:41:47 +0900197 return body['keypair']
Andrea Frittoli247058f2014-07-16 16:09:22 +0100198
Anusha Ramineni9aaef8b2016-01-19 10:56:40 +0530199 def create_server(self, name=None, image_id=None, flavor=None,
Dan Smith65744692023-05-04 09:06:41 -0700200 validatable=None, wait_until='ACTIVE',
Jordan Pittierf672b7d2016-06-20 18:50:40 +0200201 clients=None, **kwargs):
lanoux5fc14522015-09-21 08:17:35 +0000202 """Wrapper utility that returns a test server.
Andrea Frittoli247058f2014-07-16 16:09:22 +0100203
lanoux5fc14522015-09-21 08:17:35 +0000204 This wrapper utility calls the common create test server and
205 returns a test server. The purpose of this wrapper is to minimize
206 the impact on the code of the tests already using this
207 function.
Noam Angel6e309952019-01-27 05:52:40 +0000208
209 :param **kwargs:
210 See extra parameters below
211
212 :Keyword Arguments:
213 * *vnic_type* (``string``) --
214 used when launching instances with pre-configured ports.
215 Examples:
216 normal: a traditional virtual port that is either attached
217 to a linux bridge or an openvswitch bridge on a
218 compute node.
219 direct: an SR-IOV port that is directly attached to a VM
220 macvtap: an SR-IOV port that is attached to a VM via a macvtap
221 device.
Tom Stappaerts27fd5cb2020-11-26 12:07:47 +0100222 direct-physical: an SR-IOV port that is directly attached to a
223 VM using physical instead of virtual
224 functions.
225 baremetal: a baremetal port directly attached to a baremetal
226 node.
227 virtio-forwarder: an SR-IOV port that is indirectly attached
228 to a VM using a low-latency vhost-user
229 forwarding process.
Noam Angel6e309952019-01-27 05:52:40 +0000230 Defaults to ``CONF.network.port_vnic_type``.
231 * *port_profile* (``dict``) --
232 This attribute is a dictionary that can be used (with admin
233 credentials) to supply information influencing the binding of
234 the port.
235 example: port_profile = "capabilities:[switchdev]"
236 Defaults to ``CONF.network.port_profile``.
Martin Kopec9c874412020-12-17 20:43:26 +0000237 * *create_port_body* (``dict``) --
238 This attribute is a dictionary of additional arguments to be
239 passed to create_port method.
Andrea Frittoli247058f2014-07-16 16:09:22 +0100240 """
Andrea Frittoli247058f2014-07-16 16:09:22 +0100241
lanoux5fc14522015-09-21 08:17:35 +0000242 # NOTE(jlanoux): As a first step, ssh checks in the scenario
243 # tests need to be run regardless of the run_validation and
244 # validatable parameters and thus until the ssh validation job
245 # becomes voting in CI. The test resources management and IP
246 # association are taken care of in the scenario tests.
247 # Therefore, the validatable parameter is set to false in all
248 # those tests. In this way create_server just return a standard
249 # server and the scenario tests always perform ssh checks.
250
251 # Needed for the cross_tenant_traffic test:
252 if clients is None:
jeremy.zhang0343be52017-05-25 21:29:57 +0800253 clients = self.os_primary
lanoux5fc14522015-09-21 08:17:35 +0000254
zhufl24208c22016-10-25 15:23:48 +0800255 if name is None:
Martin Kopec213d0a42023-11-30 10:28:14 +0100256 name = data_utils.rand_name(
257 prefix=CONF.resource_name_prefix,
258 name=self.__class__.__name__ + "-server")
zhufl24208c22016-10-25 15:23:48 +0800259
Noam Angel6e309952019-01-27 05:52:40 +0000260 vnic_type = kwargs.pop('vnic_type', CONF.network.port_vnic_type)
261 profile = kwargs.pop('port_profile', CONF.network.port_profile)
lanoux5fc14522015-09-21 08:17:35 +0000262
Lenny Verkhovskyfe3a03f2018-02-28 10:19:37 +0000263 # If vnic_type or profile are configured create port for
lanoux5fc14522015-09-21 08:17:35 +0000264 # every network
Lenny Verkhovskyfe3a03f2018-02-28 10:19:37 +0000265 if vnic_type or profile:
lanoux5fc14522015-09-21 08:17:35 +0000266 ports = []
Martin Kopec9c874412020-12-17 20:43:26 +0000267 create_port_body = kwargs.pop('create_port_body', {})
Lenny Verkhovsky69363502016-07-17 16:33:33 +0300268
Lenny Verkhovskyfe3a03f2018-02-28 10:19:37 +0000269 if vnic_type:
270 create_port_body['binding:vnic_type'] = vnic_type
271
272 if profile:
273 create_port_body['binding:profile'] = profile
274
lanoux5fc14522015-09-21 08:17:35 +0000275 if kwargs:
276 # Convert security group names to security group ids
277 # to pass to create_port
278 if 'security_groups' in kwargs:
Thiago Paiva66cded22016-08-15 14:55:58 -0300279 security_groups = \
John Warrenf9606e92015-12-10 12:12:42 -0500280 clients.security_groups_client.list_security_groups(
lanoux5fc14522015-09-21 08:17:35 +0000281 ).get('security_groups')
282 sec_dict = dict([(s['name'], s['id'])
afazekas40fcb9b2019-03-08 11:25:11 +0100283 for s in security_groups])
lanoux5fc14522015-09-21 08:17:35 +0000284
285 sec_groups_names = [s['name'] for s in kwargs.pop(
286 'security_groups')]
287 security_groups_ids = [sec_dict[s]
288 for s in sec_groups_names]
289
290 if security_groups_ids:
291 create_port_body[
292 'security_groups'] = security_groups_ids
Lenny Verkhovsky69363502016-07-17 16:33:33 +0300293 networks = kwargs.pop('networks', [])
294 else:
295 networks = []
lanoux5fc14522015-09-21 08:17:35 +0000296
297 # If there are no networks passed to us we look up
Lenny Verkhovsky136376f2016-06-29 14:33:34 +0300298 # for the project's private networks and create a port.
299 # The same behaviour as we would expect when passing
300 # the call to the clients with no networks
lanoux5fc14522015-09-21 08:17:35 +0000301 if not networks:
302 networks = clients.networks_client.list_networks(
Lenny Verkhovsky136376f2016-06-29 14:33:34 +0300303 **{'router:external': False, 'fields': 'id'})['networks']
304
305 # It's net['uuid'] if networks come from kwargs
306 # and net['id'] if they come from
307 # clients.networks_client.list_networks
lanoux5fc14522015-09-21 08:17:35 +0000308 for net in networks:
Lenny Verkhovsky97f7cea2016-08-15 13:29:48 +0000309 net_id = net.get('uuid', net.get('id'))
Lenny Verkhovsky69363502016-07-17 16:33:33 +0300310 if 'port' not in net:
zhufl1e446b52017-10-16 16:54:57 +0800311 port = self.create_port(network_id=net_id,
312 client=clients.ports_client,
313 **create_port_body)
Lenny Verkhovsky69363502016-07-17 16:33:33 +0300314 ports.append({'port': port['id']})
315 else:
316 ports.append({'port': net['port']})
lanoux5fc14522015-09-21 08:17:35 +0000317 if ports:
318 kwargs['networks'] = ports
319 self.ports = ports
320
321 tenant_network = self.get_tenant_network()
322
Marc Koderer979e4942016-12-08 10:07:59 +0100323 if CONF.compute.compute_volume_common_az:
324 kwargs.setdefault('availability_zone',
325 CONF.compute.compute_volume_common_az)
326
Dan Smith65744692023-05-04 09:06:41 -0700327 kwargs['validatable'] = bool(validatable)
Dan Smith49c2b3b2023-04-26 15:52:22 -0700328 keypair = kwargs.pop('keypair', None)
Dan Smith65744692023-05-04 09:06:41 -0700329 if wait_until == 'SSHABLE' and (
330 kwargs.get('validation_resources') is None):
Dan Smith49c2b3b2023-04-26 15:52:22 -0700331 # NOTE(danms): We should do this whether valdiation is enabled or
332 # not to consistently provide the resources to the
333 # create_test_server() function. If validation is disabled, then
334 # get_test_validation_resources() is basically a no-op for
335 # performance.
336 validation_resources = self.get_test_validation_resources(
337 self.os_primary)
338 if keypair:
339 validation_resources = copy.deepcopy(validation_resources)
340 validation_resources.update(
341 keypair=keypair)
Dan Smith65744692023-05-04 09:06:41 -0700342 kwargs.update({
343 'validatable': (validatable if validatable is not None
344 else True),
345 'validation_resources': validation_resources})
Dan Smith49c2b3b2023-04-26 15:52:22 -0700346 if keypair:
347 kwargs.update({'key_name': keypair['name']})
348
Ferenc Horváthbce1fcf2017-06-07 11:19:51 +0200349 body, _ = compute.create_test_server(
lanoux5fc14522015-09-21 08:17:35 +0000350 clients,
351 tenant_network=tenant_network,
352 wait_until=wait_until,
Anusha Ramineni9aaef8b2016-01-19 10:56:40 +0530353 name=name, flavor=flavor,
354 image_id=image_id, **kwargs)
lanoux5fc14522015-09-21 08:17:35 +0000355
Jordan Pittierf672b7d2016-06-20 18:50:40 +0200356 self.addCleanup(waiters.wait_for_server_termination,
357 clients.servers_client, body['id'])
358 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
359 clients.servers_client.delete_server, body['id'])
lanoux5fc14522015-09-21 08:17:35 +0000360 server = clients.servers_client.show_server(body['id'])['server']
Andrea Frittoli247058f2014-07-16 16:09:22 +0100361 return server
362
Markus Zoeller3d2a21c2015-02-27 12:04:22 +0100363 def create_volume(self, size=None, name=None, snapshot_id=None,
Benny Kopilov7beb2d02022-04-12 20:33:53 +0300364 imageRef=None, volume_type=None, wait_until='available',
Milana Levye9a58a12023-02-21 12:55:20 +0000365 client=None, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530366 """Creates volume
367
368 This wrapper utility creates volume and waits for volume to be
Benny Kopilov7beb2d02022-04-12 20:33:53 +0300369 in 'available' state by default. If wait_until is None, means no wait.
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530370 This method returns the volume's full representation by GET request.
371 """
Milana Levye9a58a12023-02-21 12:55:20 +0000372 if client is None:
373 client = self.volumes_client
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530374
Ken'ichi Ohmichiadb905e2016-08-26 15:16:23 -0700375 if size is None:
376 size = CONF.volume.volume_size
Nuno Santosb746d992016-11-17 15:41:55 -0500377 if imageRef:
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700378 image = self.image_client.show_image(imageRef)
zhufl66275c22018-03-28 15:32:14 +0800379 min_disk = image.get('min_disk')
Nuno Santosb746d992016-11-17 15:41:55 -0500380 size = max(size, min_disk)
Andrea Frittoli247058f2014-07-16 16:09:22 +0100381 if name is None:
Martin Kopec213d0a42023-11-30 10:28:14 +0100382 name = data_utils.rand_name(
383 prefix=CONF.resource_name_prefix,
384 name=self.__class__.__name__ + "-volume")
Martin Kopecd3ad5e92020-10-16 14:45:09 +0000385 kwargs.update({'name': name,
386 'snapshot_id': snapshot_id,
387 'imageRef': imageRef,
388 'volume_type': volume_type,
389 'size': size})
Marc Koderer979e4942016-12-08 10:07:59 +0100390
391 if CONF.compute.compute_volume_common_az:
392 kwargs.setdefault('availability_zone',
393 CONF.compute.compute_volume_common_az)
394
Milana Levye9a58a12023-02-21 12:55:20 +0000395 volume = client.create_volume(**kwargs)['volume']
Matt Riedemanne85c2702014-09-10 11:50:13 -0700396
Milana Levye9a58a12023-02-21 12:55:20 +0000397 self.addCleanup(client.wait_for_resource_deletion,
Jordan Pittier5e1741c2016-03-02 18:25:51 +0100398 volume['id'])
Jordan Pittier9e227c52016-02-09 14:35:18 +0100399 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Milana Levye9a58a12023-02-21 12:55:20 +0000400 client.delete_volume, volume['id'])
lkuchlan5cbc00a2017-03-26 11:49:54 +0300401 self.assertEqual(name, volume['name'])
Benny Kopilov7beb2d02022-04-12 20:33:53 +0300402 if wait_until:
Milana Levye9a58a12023-02-21 12:55:20 +0000403 waiters.wait_for_volume_resource_status(client,
Benny Kopilov7beb2d02022-04-12 20:33:53 +0300404 volume['id'], wait_until)
405 # The volume retrieved on creation has a non-up-to-date status.
406 # Retrieval after it becomes active ensures correct details.
Milana Levye9a58a12023-02-21 12:55:20 +0000407 volume = client.show_volume(volume['id'])['volume']
408
Andrea Frittoli247058f2014-07-16 16:09:22 +0100409 return volume
410
lkuchlane20e6a82018-05-08 11:28:46 +0300411 def create_backup(self, volume_id, name=None, description=None,
412 force=False, snapshot_id=None, incremental=False,
Martin Kopec4a140052020-10-16 16:26:55 +0000413 container=None, **kwargs):
414 """Creates a backup of the given volume_id or snapshot_id
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530415
Martin Kopec4a140052020-10-16 16:26:55 +0000416 This wrapper utility creates a backup and waits until it is in
417 'available' state.
418
419 :param volume_id: UUID of the volume to back up
420 :param name: backup name, '$classname-backup' by default
421 :param description: Description of the backup, None by default
422 :param force: boolean whether to backup even if the volume is attached
423 False by default
424 :param snapshot_id: UUID of the source snapshot to back up
425 None by default
426 :param incremental: boolean, False by default
427 :param container: a container name, None by default
428 :param **kwargs: additional parameters per the documentation:
429 https://docs.openstack.org/api-ref/block-storage/v3/
430 #create-a-backup
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530431 """
lkuchlane20e6a82018-05-08 11:28:46 +0300432
433 name = name or data_utils.rand_name(
Martin Kopec213d0a42023-11-30 10:28:14 +0100434 prefix=CONF.resource_name_prefix,
435 name=self.__class__.__name__ + "-backup")
Martin Kopec4a140052020-10-16 16:26:55 +0000436 args = {'name': name,
437 'description': description,
438 'force': force,
439 'snapshot_id': snapshot_id,
440 'incremental': incremental,
441 'container': container}
442 args.update(kwargs)
lkuchlane20e6a82018-05-08 11:28:46 +0300443 backup = self.backups_client.create_backup(volume_id=volume_id,
Yosi Ben Shimonbce267e2024-02-06 15:00:25 +0200444 **args)['backup']
lkuchlane20e6a82018-05-08 11:28:46 +0300445 self.addCleanup(self.backups_client.delete_backup, backup['id'])
446 waiters.wait_for_volume_resource_status(self.backups_client,
447 backup['id'], 'available')
448 return backup
449
Martin Kopec4a140052020-10-16 16:26:55 +0000450 def restore_backup(self, backup_id, **kwargs):
451 """Restores a backup given by the backup_id
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530452
Martin Kopec4a140052020-10-16 16:26:55 +0000453 This wrapper utility restores a backup and waits until it is in
454 'available' state.
455
456 :param backup_id: UUID of a backup to restore
457 :param **kwargs: additional parameters per the documentation:
458 https://docs.openstack.org/api-ref/block-storage/v3/
459 #restore-a-backup
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530460 """
461
Martin Kopec4a140052020-10-16 16:26:55 +0000462 body = self.backups_client.restore_backup(backup_id, **kwargs)
463 restore = body['restore']
Sofia Enriquez404b55c2022-05-26 19:33:47 +0000464
465 using_pre_existing_volume = kwargs.get('volume_id', False)
466 if not using_pre_existing_volume:
467 self.addCleanup(self.volumes_client.delete_volume,
468 restore['volume_id'])
469
lkuchlane20e6a82018-05-08 11:28:46 +0300470 waiters.wait_for_volume_resource_status(self.backups_client,
471 backup_id, 'available')
472 waiters.wait_for_volume_resource_status(self.volumes_client,
473 restore['volume_id'],
474 'available')
475 self.assertEqual(backup_id, restore['backup_id'])
476 return restore
477
Martin Kopecbe8ba2c2020-12-17 21:33:32 +0000478 def rebuild_server(self, server_id, image=None, preserve_ephemeral=False,
479 wait=True, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530480 if image is None:
481 image = CONF.compute.image_ref
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530482 LOG.debug("Rebuilding server (id: %s, image: %s, preserve eph: %s)",
483 server_id, image, preserve_ephemeral)
484 self.servers_client.rebuild_server(
485 server_id=server_id,
486 image_ref=image,
487 preserve_ephemeral=preserve_ephemeral,
Martin Kopecbe8ba2c2020-12-17 21:33:32 +0000488 **kwargs)
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530489 if wait:
490 waiters.wait_for_server_status(self.servers_client,
491 server_id, 'ACTIVE')
492
lkuchlan73ed1f32017-07-06 16:22:12 +0300493 def create_volume_snapshot(self, volume_id, name=None, description=None,
Martin Kopeca17cca42020-10-17 16:57:51 +0000494 metadata=None, force=False, **kwargs):
495 """Creates volume's snapshot
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530496
Martin Kopeca17cca42020-10-17 16:57:51 +0000497 This wrapper utility creates volume snapshot and waits for it until
498 it is in 'available' state.
499
500 :param volume_id: UUID of a volume to create snapshot of
501 :param name: name of the snapshot, '$classname-snapshot' by default
502 :param description: description of the snapshot
503 :param metadata: metadata key and value pairs for the snapshot
504 :param force: whether snapshot even when the volume is attached
505 :param **kwargs: additional parameters per the doc
506 https://docs.openstack.org/api-ref/block-storage/v3/
507 #create-a-snapshot
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530508 """
509
lkuchlan73ed1f32017-07-06 16:22:12 +0300510 name = name or data_utils.rand_name(
Martin Kopec213d0a42023-11-30 10:28:14 +0100511 prefix=CONF.resource_name_prefix,
512 name=self.__class__.__name__ + '-snapshot')
lkuchlan73ed1f32017-07-06 16:22:12 +0300513 snapshot = self.snapshots_client.create_snapshot(
514 volume_id=volume_id,
515 force=force,
Martin Kopec20c87c72020-10-17 11:42:29 +0000516 name=name,
lkuchlan73ed1f32017-07-06 16:22:12 +0300517 description=description,
Martin Kopeca17cca42020-10-17 16:57:51 +0000518 metadata=metadata,
519 **kwargs)['snapshot']
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530520
lkuchlan73ed1f32017-07-06 16:22:12 +0300521 self.addCleanup(self.snapshots_client.wait_for_resource_deletion,
522 snapshot['id'])
Benny Kopilovd4d49b02021-08-10 18:54:01 +0300523 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
524 self.snapshots_client.delete_snapshot, snapshot['id'])
lkuchlan73ed1f32017-07-06 16:22:12 +0300525 waiters.wait_for_volume_resource_status(self.snapshots_client,
526 snapshot['id'], 'available')
Benny Kopilov11b28002017-12-19 12:46:19 +0200527 snapshot = self.snapshots_client.show_snapshot(
528 snapshot['id'])['snapshot']
lkuchlan73ed1f32017-07-06 16:22:12 +0300529 return snapshot
530
Soniya Vyasfd4dcf92021-02-17 18:12:43 +0530531 def cleanup_volume_type(self, volume_type):
Lee Yarwoodbe64e1a2019-04-09 14:02:12 +0100532 """Clean up a given volume type.
533
534 Ensuring all volumes associated to a type are first removed before
535 attempting to remove the type itself. This includes any image volume
536 cache volumes stored in a separate tenant to the original volumes
537 created from the type.
538 """
539 admin_volume_type_client = self.os_admin.volume_types_client_latest
540 admin_volumes_client = self.os_admin.volumes_client_latest
541 volumes = admin_volumes_client.list_volumes(
542 detail=True, params={'all_tenants': 1})['volumes']
543 type_name = volume_type['name']
544 for volume in [v for v in volumes if v['volume_type'] == type_name]:
545 test_utils.call_and_ignore_notfound_exc(
546 admin_volumes_client.delete_volume, volume['id'])
547 admin_volumes_client.wait_for_resource_deletion(volume['id'])
548 admin_volume_type_client.delete_volume_type(volume_type['id'])
549
Martin Kopec8e673a42020-10-18 17:33:02 +0000550 def create_volume_type(self, client=None, name=None, backend_name=None,
551 **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530552 """Creates volume type
553
554 In a multiple-storage back-end configuration,
555 each back end has a name (volume_backend_name).
556 The name of the back end is declared as an extra-specification
557 of a volume type (such as, volume_backend_name=LVM).
558 When a volume is created, the scheduler chooses an
559 appropriate back end to handle the request, according
560 to the volume type specified by the user.
561 The scheduler uses volume types to explicitly create volumes on
562 specific back ends.
563
564 Before using volume type, a volume type has to be declared
565 to Block Storage. In addition to that, an extra-specification
566 has to be created to link the volume type to a back end name.
567 """
568
scottda61f68ac2016-06-07 12:07:55 -0600569 if not client:
ghanshyam6c682ff2018-08-06 09:54:45 +0000570 client = self.os_admin.volume_types_client_latest
Matt Riedemann514495b2019-05-04 17:34:12 +0000571 if not name:
572 class_name = self.__class__.__name__
Martin Kopec213d0a42023-11-30 10:28:14 +0100573 name = data_utils.rand_name(
574 prefix=CONF.resource_name_prefix,
575 name=class_name + '-volume-type')
576 randomized_name = data_utils.rand_name(
577 prefix=CONF.resource_name_prefix, name='scenario-type-' + name)
scottda61f68ac2016-06-07 12:07:55 -0600578
579 LOG.debug("Creating a volume type: %s on backend %s",
580 randomized_name, backend_name)
Martin Kopec8e673a42020-10-18 17:33:02 +0000581 extra_specs = kwargs.pop("extra_specs", {})
scottda61f68ac2016-06-07 12:07:55 -0600582 if backend_name:
Martin Kopec8e673a42020-10-18 17:33:02 +0000583 extra_specs.update({"volume_backend_name": backend_name})
scottda61f68ac2016-06-07 12:07:55 -0600584
Martin Kopec8e673a42020-10-18 17:33:02 +0000585 volume_type_resp = client.create_volume_type(
586 name=randomized_name, extra_specs=extra_specs, **kwargs)
587 volume_type = volume_type_resp['volume_type']
588
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530589 self.assertIn('id', volume_type)
Soniya Vyasfd4dcf92021-02-17 18:12:43 +0530590 self.addCleanup(self.cleanup_volume_type, volume_type)
scottda61f68ac2016-06-07 12:07:55 -0600591 return volume_type
592
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500593 def create_security_group(self, security_group_rules_client=None,
594 project_id=None,
595 namestart='secgroup-smoke',
596 security_groups_client=None):
597 if security_group_rules_client is None:
598 security_group_rules_client = self.security_group_rules_client
599 if security_groups_client is None:
600 security_groups_client = self.security_groups_client
601 if project_id is None:
602 project_id = security_groups_client.project_id
603 secgroup = self.create_empty_security_group(
604 namestart=namestart, client=security_groups_client,
605 project_id=project_id)
Andrea Frittoli247058f2014-07-16 16:09:22 +0100606
607 # Add rules to the security group
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500608 rules = self.create_loginable_secgroup_rule(
609 security_group_rules_client=security_group_rules_client,
610 secgroup=secgroup,
611 security_groups_client=security_groups_client)
612 for rule in rules:
613 self.assertEqual(project_id, rule['project_id'])
614 self.assertEqual(secgroup['id'], rule['security_group_id'])
Andrea Frittoli247058f2014-07-16 16:09:22 +0100615 return secgroup
616
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500617 def create_empty_security_group(self, client=None, project_id=None,
618 namestart='secgroup-smoke'):
619 """Create a security group without rules.
620
621 Default rules will be created:
622 - IPv4 egress to any
623 - IPv6 egress to any
624 :param project_id: secgroup will be created in this project
625 :returns: the created security group
626 """
627
628 if client is None:
629 client = self.security_groups_client
630 if not project_id:
631 project_id = client.project_id
Martin Kopec213d0a42023-11-30 10:28:14 +0100632 sg_name = data_utils.rand_name(
633 prefix=CONF.resource_name_prefix, name=namestart)
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500634 sg_desc = sg_name + " description"
635 sg_dict = dict(name=sg_name,
636 description=sg_desc)
637 sg_dict['project_id'] = project_id
638 result = client.create_security_group(**sg_dict)
639
640 secgroup = result['security_group']
641 self.assertEqual(secgroup['name'], sg_name)
642 self.assertEqual(project_id, secgroup['project_id'])
643 self.assertEqual(secgroup['description'], sg_desc)
644
645 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
646 client.delete_security_group, secgroup['id'])
647 return secgroup
648
649 def create_security_group_rule(self, secgroup=None,
650 sec_group_rules_client=None,
651 project_id=None,
652 security_groups_client=None, **kwargs):
653 """Create a rule from a dictionary of rule parameters.
654
655 Create a rule in a secgroup. if secgroup not defined will search for
656 default secgroup in project_id.
657 :param secgroup: the security group.
658 :param project_id: if secgroup not passed -- the tenant in which to
659 search for default secgroup
660 :param kwargs: a dictionary containing rule parameters:
661 for example, to allow incoming ssh:
662 rule = {
663 direction: 'ingress'
664 protocol:'tcp',
665 port_range_min: 22,
666 port_range_max: 22
667 }
668 """
669
670 if sec_group_rules_client is None:
671 sec_group_rules_client = self.security_group_rules_client
672 if security_groups_client is None:
673 security_groups_client = self.security_groups_client
674 if not project_id:
675 project_id = security_groups_client.project_id
676 if secgroup is None:
677 # Get default secgroup for project_id
678 default_secgroups = security_groups_client.list_security_groups(
679 name='default', project_id=project_id)['security_groups']
680 msg = "No default security group for project %s." % (project_id)
681 self.assertNotEmpty(default_secgroups, msg)
682 secgroup = default_secgroups[0]
683
684 ruleset = dict(security_group_id=secgroup['id'],
685 project_id=secgroup['project_id'])
686 ruleset.update(kwargs)
687
688 sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
689 sg_rule = sg_rule['security_group_rule']
690
691 self.assertEqual(secgroup['tenant_id'], sg_rule['tenant_id'])
692 self.assertEqual(secgroup['id'], sg_rule['security_group_id'])
693
694 return sg_rule
695
696 def create_loginable_secgroup_rule(self, security_group_rules_client=None,
697 secgroup=None,
Roman Popelka3b0ccb02022-03-24 10:25:19 +0100698 security_groups_client=None,
699 rulesets=None):
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500700 """Create loginable security group rule by neutron clients by default.
701
702 This function will create:
703 1. egress and ingress tcp port 22 allow rule in order to allow ssh
704 access for ipv4.
705 2. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6.
706 3. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4.
707 """
708
709 if security_group_rules_client is None:
710 security_group_rules_client = self.security_group_rules_client
711 if security_groups_client is None:
712 security_groups_client = self.security_groups_client
Roman Popelka3b0ccb02022-03-24 10:25:19 +0100713 if rulesets is None:
714 rulesets = [
715 dict(
716 # ssh
717 protocol='tcp',
718 port_range_min=22,
719 port_range_max=22,
720 ),
721 dict(
722 # ping
723 protocol='icmp',
724 ),
725 dict(
726 # ipv6-icmp for ping6
727 protocol='icmp',
728 ethertype='IPv6',
729 )
730 ]
731
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500732 rules = []
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500733 sec_group_rules_client = security_group_rules_client
734 for ruleset in rulesets:
735 for r_direction in ['ingress', 'egress']:
736 ruleset['direction'] = r_direction
737 try:
738 sg_rule = self.create_security_group_rule(
739 sec_group_rules_client=sec_group_rules_client,
740 secgroup=secgroup,
741 security_groups_client=security_groups_client,
742 **ruleset)
743 except lib_exc.Conflict as ex:
744 # if rule already exist - skip rule and continue
745 msg = 'Security group rule already exists'
746 if msg not in ex._error_string:
747 raise ex
748 else:
749 self.assertEqual(r_direction, sg_rule['direction'])
750 rules.append(sg_rule)
751
752 return rules
753
jskundad7445982023-10-18 13:49:02 +0200754 def create_and_add_security_group_to_server(self, server):
755 """Create a security group and add it to the server.
756
757 :param server: The server to add the security group to.
758 :return: The security group was added to the server.
759 """
760
761 secgroup = self.create_security_group()
762 self.servers_client.add_security_group(server['id'],
763 name=secgroup['name'])
764 self.addCleanup(self.servers_client.remove_security_group,
765 server['id'], name=secgroup['name'])
766
767 def wait_for_secgroup_add():
768 body = (self.servers_client.show_server(server['id'])
769 ['server'])
770 return {'name': secgroup['name']} in body['security_groups']
771
772 if not test_utils.call_until_true(wait_for_secgroup_add,
773 CONF.compute.build_timeout,
774 CONF.compute.build_interval):
775 msg = ('Timed out waiting for adding security group %s to server '
776 '%s' % (secgroup['id'], server['id']))
777 raise lib_exc.TimeoutException(msg)
778
zhuflf52c7592017-05-25 13:55:24 +0800779 def get_remote_client(self, ip_address, username=None, private_key=None,
780 server=None):
JordanP3fe2dc32014-11-17 13:06:01 +0100781 """Get a SSH client to a remote server
782
Sergey Vilgelmeac094a2018-11-21 18:27:51 -0600783 :param ip_address: the server floating or fixed IP address to use
784 for ssh validation
785 :param username: name of the Linux account on the remote server
786 :param private_key: the SSH private key to use
787 :param server: server dict, used for debugging purposes
788 :return: a RemoteClient object
JordanP3fe2dc32014-11-17 13:06:01 +0100789 """
Adam Gandelmanc78c7572014-08-28 18:38:55 -0700790
Andrea Frittoli247058f2014-07-16 16:09:22 +0100791 if username is None:
lanoux283273b2015-12-04 03:01:54 -0800792 username = CONF.validation.image_ssh_user
wantwatering896300c2015-03-27 15:17:42 +0800793 # Set this with 'keypair' or others to log in with keypair or
794 # username/password.
lanoux5fc14522015-09-21 08:17:35 +0000795 if CONF.validation.auth_method == 'keypair':
wantwatering896300c2015-03-27 15:17:42 +0800796 password = None
797 if private_key is None:
798 private_key = self.keypair['private_key']
799 else:
lanoux283273b2015-12-04 03:01:54 -0800800 password = CONF.validation.image_ssh_password
wantwatering896300c2015-03-27 15:17:42 +0800801 private_key = None
zhuflf52c7592017-05-25 13:55:24 +0800802 linux_client = remote_client.RemoteClient(
803 ip_address, username, pkey=private_key, password=password,
804 server=server, servers_client=self.servers_client)
805 linux_client.validate_authentication()
Andrea Frittoli247058f2014-07-16 16:09:22 +0100806 return linux_client
807
Lukas Piwowarskib50eabe2020-11-05 15:15:38 +0000808 def image_create(self, name='scenario-img', **kwargs):
Martin Kopec02af6a42020-03-03 12:39:12 +0000809 img_path = CONF.scenario.img_file
810 if not os.path.exists(img_path):
Martin Kopec008950e2020-09-29 08:12:39 +0000811 lib_exc.InvalidConfiguration(
Martin Kopec02af6a42020-03-03 12:39:12 +0000812 'Starting Tempest 25.0.0 release, CONF.scenario.img_file need '
813 'a full path for the image. CONF.scenario.img_dir was '
814 'deprecated and will be removed in the next release. Till '
Martin Kopec008950e2020-09-29 08:12:39 +0000815 'Tempest 25.0.0, old behavior was maintained and kept working '
Martin Kopec02af6a42020-03-03 12:39:12 +0000816 'but starting Tempest 26.0.0, you need to specify the full '
817 'path in CONF.scenario.img_file config option.')
Alessandro Pilottib7c1daa2014-08-16 14:24:13 +0300818 img_container_format = CONF.scenario.img_container_format
819 img_disk_format = CONF.scenario.img_disk_format
Evgeny Antyshev7ba0d5f2015-04-28 13:18:07 +0000820 img_properties = CONF.scenario.img_properties
PranaliD2aa523c2016-06-07 03:54:34 -0400821 LOG.debug("paths: img: %s, container_format: %s, disk_format: %s, "
Martin Kopec02af6a42020-03-03 12:39:12 +0000822 "properties: %s",
Jordan Pittier525ec712016-12-07 17:51:26 +0100823 img_path, img_container_format, img_disk_format,
Martin Kopec02af6a42020-03-03 12:39:12 +0000824 img_properties)
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530825 if img_properties is None:
826 img_properties = {}
Martin Kopec213d0a42023-11-30 10:28:14 +0100827 name = data_utils.rand_name(
828 prefix=CONF.resource_name_prefix, name='%s-' % name)
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530829 params = {
830 'name': name,
831 'container_format': img_container_format,
832 'disk_format': img_disk_format or img_container_format,
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700833 'visibility': 'private'
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530834 }
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700835 # Additional properties are flattened out in the v2 API.
836 if img_properties:
837 params.update(img_properties)
Lukas Piwowarskib50eabe2020-11-05 15:15:38 +0000838 params.update(kwargs)
melanie witta0b161b2023-12-01 23:41:44 +0000839
840 # This code is basically copying the devstack code that extracts and
841 # uploads split kernel/ramdisk images.
842 if tarfile.is_tarfile(img_path):
843 extract_dir = os.path.join(tempfile.gettempdir(), 'images', name)
844 self.addCleanup(shutil.rmtree, extract_dir)
845 os.makedirs(extract_dir)
846 with tarfile.open(img_path) as tar:
847 tar.extractall(extract_dir, filter='data')
848 filenames = os.listdir(extract_dir)
849 for fname in filenames:
850 if re.search(r'(.*-vmlinuz.*|aki-.*/image$)', fname):
851 kernel_img_path = os.path.join(extract_dir, fname)
852 elif re.search(r'(.*-initrd.*|ari-.*/image$)', fname):
853 ramdisk_img_path = os.path.join(extract_dir, fname)
854 elif re.search(f'(.*\\.img$|ami-.*/image$)', fname):
855 img_path = os.path.join(extract_dir, fname)
856 # Create the kernel image.
857 kparams = {
858 'name': name + '-kernel',
859 'container_format': 'aki',
860 'disk_format': 'aki',
861 'visibility': 'private'
862 }
863 body = self.image_client.create_image(**kparams)
864 image = body['image'] if 'image' in body else body
865 kernel_id = image['id']
866 self.addCleanup(self.image_client.delete_image, kernel_id)
867 self.assertEqual("queued", image['status'])
868 with open(kernel_img_path, 'rb') as image_file:
869 self.image_client.store_image_file(kernel_id, image_file)
870 LOG.debug("image:%s", kernel_id)
871 # Create the ramdisk image.
872 rparams = {
873 'name': name + '-ramdisk',
874 'container_format': 'ari',
875 'disk_format': 'ari',
876 'visibility': 'private'
877 }
878 body = self.image_client.create_image(**rparams)
879 image = body['image'] if 'image' in body else body
880 ramdisk_id = image['id']
881 self.addCleanup(self.image_client.delete_image, ramdisk_id)
882 self.assertEqual("queued", image['status'])
883 with open(ramdisk_img_path, 'rb') as image_file:
884 self.image_client.store_image_file(ramdisk_id, image_file)
885 LOG.debug("image:%s", ramdisk_id)
886 # Set the kernel_id, ramdisk_id, container format, disk format for
887 # the split image.
888 params['kernel_id'] = kernel_id
889 params['ramdisk_id'] = ramdisk_id
890 params['container_format'] = 'ami'
891 params['disk_format'] = 'ami'
892
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530893 body = self.image_client.create_image(**params)
894 image = body['image'] if 'image' in body else body
895 self.addCleanup(self.image_client.delete_image, image['id'])
896 self.assertEqual("queued", image['status'])
897 with open(img_path, 'rb') as image_file:
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700898 self.image_client.store_image_file(image['id'], image_file)
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530899 LOG.debug("image:%s", image['id'])
900 return image['id']
Andrea Frittoli247058f2014-07-16 16:09:22 +0100901
Soniya Vyas1b0cddc2021-01-29 17:28:19 +0530902 def log_console_output(self, servers=None, client=None, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530903 """Console log output"""
Matthew Treinish42a3f3a2014-09-04 15:04:53 -0400904 if not CONF.compute_feature_enabled.console_output:
905 LOG.debug('Console output not supported, cannot log')
906 return
Ihar Hrachyshkaa9dca2b2017-04-04 14:17:11 -0700907 client = client or self.servers_client
Andrea Frittoli247058f2014-07-16 16:09:22 +0100908 if not servers:
Ihar Hrachyshkaa9dca2b2017-04-04 14:17:11 -0700909 servers = client.list_servers()
Andrea Frittoli247058f2014-07-16 16:09:22 +0100910 servers = servers['servers']
911 for server in servers:
Attila Fazekas9a5a1122016-11-08 10:24:57 +0100912 try:
Ihar Hrachyshkaa9dca2b2017-04-04 14:17:11 -0700913 console_output = client.get_console_output(
Lukas Piwowarski91ded042020-10-29 15:15:25 +0000914 server['id'], **kwargs)['output']
Attila Fazekas9a5a1122016-11-08 10:24:57 +0100915 LOG.debug('Console output for %s\nbody=\n%s',
916 server['id'], console_output)
917 except lib_exc.NotFound:
Attila Fazekase1360482016-11-10 11:28:08 +0100918 LOG.debug("Server %s disappeared(deleted) while looking "
Attila Fazekas9a5a1122016-11-08 10:24:57 +0100919 "for the console log", server['id'])
Andrea Frittoli247058f2014-07-16 16:09:22 +0100920
Ken'ichi Ohmichi6e201f52014-10-01 04:21:39 +0000921 def _log_net_info(self, exc):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530922 """network debug is called as part of ssh init"""
Andrey Pavlov64723762015-04-29 06:24:58 +0300923 if not isinstance(exc, lib_exc.SSHTimeout):
Ken'ichi Ohmichi6e201f52014-10-01 04:21:39 +0000924 LOG.debug('Network information on a devstack host')
Ken'ichi Ohmichi6e201f52014-10-01 04:21:39 +0000925
Lukas Piwowarski9ad9ca22020-10-29 15:36:30 +0000926 def create_server_snapshot(self, server, name=None, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530927 """Creates server snapshot"""
nithya-ganesan882595e2014-07-29 18:51:07 +0000928 # Glance client
929 _image_client = self.image_client
930 # Compute client
Ghanshyamae76c122015-12-22 13:41:35 +0900931 _images_client = self.compute_images_client
nithya-ganesan882595e2014-07-29 18:51:07 +0000932 if name is None:
Martin Kopec213d0a42023-11-30 10:28:14 +0100933 name = data_utils.rand_name(
934 prefix=CONF.resource_name_prefix,
935 name=self.__class__.__name__ + 'snapshot')
nithya-ganesan882595e2014-07-29 18:51:07 +0000936 LOG.debug("Creating a snapshot image for server: %s", server['name'])
Lukas Piwowarski9ad9ca22020-10-29 15:36:30 +0000937 image = _images_client.create_image(server['id'], name=name, **kwargs)
Benny Kopilov7d2edc22022-06-30 17:22:14 +0300938 # microversion 2.45 and above returns image_id
939 image_id = image.get('image_id') or image.response['location'].split(
940 'images/')[1]
Yaroslav Lobankov2fea4052016-04-19 15:05:57 +0300941 waiters.wait_for_image_status(_image_client, image_id, 'active')
Jordan Pittierf672b7d2016-06-20 18:50:40 +0200942
943 self.addCleanup(_image_client.wait_for_resource_deletion,
944 image_id)
945 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
946 _image_client.delete_image, image_id)
947
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700948 # In glance v2 the additional properties are flattened.
949 snapshot_image = _image_client.show_image(image_id)
950 image_props = snapshot_image
Andrey Pavlovc8bd4b12015-08-17 10:20:17 +0300951
Matt Riedemann2aa19d42016-06-06 17:45:41 -0400952 bdm = image_props.get('block_device_mapping')
Andrey Pavlovc8bd4b12015-08-17 10:20:17 +0300953 if bdm:
954 bdm = json.loads(bdm)
955 if bdm and 'snapshot_id' in bdm[0]:
956 snapshot_id = bdm[0]['snapshot_id']
957 self.addCleanup(
958 self.snapshots_client.wait_for_resource_deletion,
959 snapshot_id)
Jordan Pittier9e227c52016-02-09 14:35:18 +0100960 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
961 self.snapshots_client.delete_snapshot,
962 snapshot_id)
lkuchlan52d7b0d2016-11-07 20:53:19 +0200963 waiters.wait_for_volume_resource_status(self.snapshots_client,
964 snapshot_id,
965 'available')
nithya-ganesan882595e2014-07-29 18:51:07 +0000966 image_name = snapshot_image['name']
967 self.assertEqual(name, image_name)
968 LOG.debug("Created snapshot image %s for server %s",
969 image_name, server['name'])
970 return snapshot_image
971
Milana Levye9a58a12023-02-21 12:55:20 +0000972 def nova_volume_attach(self, server, volume_to_attach,
973 volumes_client=None, servers_client=None,
974 **kwargs):
Soniya Vyasae631132020-08-28 13:37:12 +0530975 """Compute volume attach
976
977 This utility attaches volume from compute and waits for the
978 volume status to be 'in-use' state.
979 """
Milana Levye9a58a12023-02-21 12:55:20 +0000980 if volumes_client is None:
981 volumes_client = self.volumes_client
982 if servers_client is None:
983 servers_client = self.servers_client
984
985 volume = servers_client.attach_volume(
Lukas Piwowarski76819fa2020-10-29 13:46:07 +0000986 server['id'], volumeId=volume_to_attach['id'],
987 **kwargs)['volumeAttachment']
Jordan Pittier7cf64762015-10-14 15:01:12 +0200988 self.assertEqual(volume_to_attach['id'], volume['id'])
Milana Levye9a58a12023-02-21 12:55:20 +0000989 waiters.wait_for_volume_resource_status(volumes_client,
lkuchlan52d7b0d2016-11-07 20:53:19 +0200990 volume['id'], 'in-use')
Lukas Piwowarski76819fa2020-10-29 13:46:07 +0000991 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Milana Levye9a58a12023-02-21 12:55:20 +0000992 self.nova_volume_detach, server, volume,
993 servers_client)
Jordan Pittier7cf64762015-10-14 15:01:12 +0200994 # Return the updated volume after the attachment
Milana Levye9a58a12023-02-21 12:55:20 +0000995 return volumes_client.show_volume(volume['id'])['volume']
Masayuki Igawa1f0ad632014-08-05 13:36:56 +0900996
Milana Levye9a58a12023-02-21 12:55:20 +0000997 def nova_volume_detach(self, server, volume, servers_client=None):
Soniya Vyasae631132020-08-28 13:37:12 +0530998 """Compute volume detach
999
Lee Yarwood5423c532020-12-17 11:24:46 +00001000 This utility detaches the volume from the server and checks whether the
1001 volume attachment has been removed from Nova.
Soniya Vyasae631132020-08-28 13:37:12 +05301002 """
Milana Levye9a58a12023-02-21 12:55:20 +00001003 if servers_client is None:
1004 servers_client = self.servers_client
1005
1006 servers_client.detach_volume(server['id'], volume['id'])
Lee Yarwood5423c532020-12-17 11:24:46 +00001007 waiters.wait_for_volume_attachment_remove_from_server(
Milana Levye9a58a12023-02-21 12:55:20 +00001008 servers_client, server['id'], volume['id'])
Jordan Pittier7cf64762015-10-14 15:01:12 +02001009
Steven Hardyda2a8352014-10-02 12:52:20 +01001010 def ping_ip_address(self, ip_address, should_succeed=True,
zhufl0ec74c42017-11-15 14:02:28 +08001011 ping_timeout=None, mtu=None, server=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301012 """ping ip address"""
lanoux5fc14522015-09-21 08:17:35 +00001013 timeout = ping_timeout or CONF.validation.ping_timeout
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +00001014 cmd = ['ping', '-c1', '-w1']
1015
1016 if mtu:
1017 cmd += [
1018 # don't fragment
1019 '-M', 'do',
1020 # ping receives just the size of ICMP payload
1021 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
1022 ]
1023 cmd.append(ip_address)
Aaron Rosena7df13b2014-09-23 09:45:45 -07001024
1025 def ping():
1026 proc = subprocess.Popen(cmd,
1027 stdout=subprocess.PIPE,
1028 stderr=subprocess.PIPE)
1029 proc.communicate()
Shuquan Huang753629e2015-07-20 08:52:29 +00001030
Aaron Rosena7df13b2014-09-23 09:45:45 -07001031 return (proc.returncode == 0) == should_succeed
1032
Jordan Pittier9e227c52016-02-09 14:35:18 +01001033 caller = test_utils.find_test_caller()
Shuquan Huang753629e2015-07-20 08:52:29 +00001034 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
John L. Villalovosa898aec2017-01-13 14:46:46 -08001035 ' expected result is %(should_succeed)s', {
Shuquan Huang753629e2015-07-20 08:52:29 +00001036 'caller': caller, 'ip': ip_address, 'timeout': timeout,
1037 'should_succeed':
1038 'reachable' if should_succeed else 'unreachable'
1039 })
Jordan Pittier35a63752016-08-30 13:09:12 +02001040 result = test_utils.call_until_true(ping, timeout, 1)
Shuquan Huang753629e2015-07-20 08:52:29 +00001041 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
John L. Villalovosa898aec2017-01-13 14:46:46 -08001042 'ping result is %(result)s', {
Shuquan Huang753629e2015-07-20 08:52:29 +00001043 'caller': caller, 'ip': ip_address, 'timeout': timeout,
1044 'result': 'expected' if result else 'unexpected'
1045 })
zhufl0ec74c42017-11-15 14:02:28 +08001046 if server:
Soniya Vyas1b0cddc2021-01-29 17:28:19 +05301047 self.log_console_output([server])
Shuquan Huang753629e2015-07-20 08:52:29 +00001048 return result
Aaron Rosena7df13b2014-09-23 09:45:45 -07001049
Yair Friedae0e73d2014-11-24 11:56:26 +02001050 def check_vm_connectivity(self, ip_address,
1051 username=None,
1052 private_key=None,
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +00001053 should_connect=True,
zhufl0ec74c42017-11-15 14:02:28 +08001054 extra_msg="",
1055 server=None,
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +00001056 mtu=None):
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001057 """Check server connectivity
1058
Yair Friedae0e73d2014-11-24 11:56:26 +02001059 :param ip_address: server to test against
1060 :param username: server's ssh username
1061 :param private_key: server's ssh private key to be used
1062 :param should_connect: True/False indicates positive/negative test
1063 positive - attempt ping and ssh
1064 negative - attempt ping and fail if succeed
zhufl0ec74c42017-11-15 14:02:28 +08001065 :param extra_msg: Message to help with debugging if ``ping_ip_address``
1066 fails
1067 :param server: The server whose console to log for debugging
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +00001068 :param mtu: network MTU to use for connectivity validation
Yair Friedae0e73d2014-11-24 11:56:26 +02001069
1070 :raises: AssertError if the result of the connectivity check does
1071 not match the value of the should_connect param
1072 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301073
zhufl0ec74c42017-11-15 14:02:28 +08001074 LOG.debug('checking network connections to IP %s with user: %s',
1075 ip_address, username)
Yair Friedae0e73d2014-11-24 11:56:26 +02001076 if should_connect:
1077 msg = "Timed out waiting for %s to become reachable" % ip_address
1078 else:
1079 msg = "ip address %s is reachable" % ip_address
zhufl0ec74c42017-11-15 14:02:28 +08001080 if extra_msg:
1081 msg = "%s\n%s" % (extra_msg, msg)
Yair Friedae0e73d2014-11-24 11:56:26 +02001082 self.assertTrue(self.ping_ip_address(ip_address,
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +00001083 should_succeed=should_connect,
zhufl0ec74c42017-11-15 14:02:28 +08001084 mtu=mtu, server=server),
Yair Friedae0e73d2014-11-24 11:56:26 +02001085 msg=msg)
1086 if should_connect:
1087 # no need to check ssh for negative connectivity
zhufl0ec74c42017-11-15 14:02:28 +08001088 try:
1089 self.get_remote_client(ip_address, username, private_key,
1090 server=server)
1091 except Exception:
1092 if not extra_msg:
1093 extra_msg = 'Failed to ssh to %s' % ip_address
1094 LOG.exception(extra_msg)
1095 raise
Yair Friedae0e73d2014-11-24 11:56:26 +02001096
Ghanshyam Mann64281392021-03-24 18:48:38 -05001097 def get_server_port_id_and_ip4(self, server, ip_addr=None, **kwargs):
Yair Friedae0e73d2014-11-24 11:56:26 +02001098
Ghanshyam Mann64281392021-03-24 18:48:38 -05001099 if ip_addr and not kwargs.get('fixed_ips'):
1100 kwargs['fixed_ips'] = 'ip_address=%s' % ip_addr
1101 ports = self.os_admin.ports_client.list_ports(
1102 device_id=server['id'], **kwargs)['ports']
Lukas Piwowarskif759bc12020-11-05 10:51:29 +00001103
Ghanshyam Mann64281392021-03-24 18:48:38 -05001104 # A port can have more than one IP address in some cases.
1105 # If the network is dual-stack (IPv4 + IPv6), this port is associated
1106 # with 2 subnets
1107
1108 def _is_active(port):
1109 # NOTE(vsaienko) With Ironic, instances live on separate hardware
1110 # servers. Neutron does not bind ports for Ironic instances, as a
1111 # result the port remains in the DOWN state. This has been fixed
1112 # with the introduction of the networking-baremetal plugin but
1113 # it's not mandatory (and is not used on all stable branches).
1114 return (port['status'] == 'ACTIVE' or
1115 port.get('binding:vnic_type') == 'baremetal')
1116
1117 port_map = [(p["id"], fxip["ip_address"])
1118 for p in ports
1119 for fxip in p["fixed_ips"]
1120 if (netutils.is_valid_ipv4(fxip["ip_address"]) and
1121 _is_active(p))]
1122 inactive = [p for p in ports if p['status'] != 'ACTIVE']
1123 if inactive:
1124 LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
1125
1126 self.assertNotEmpty(port_map,
1127 "No IPv4 addresses found in: %s" % ports)
1128 self.assertEqual(len(port_map), 1,
1129 "Found multiple IPv4 addresses: %s. "
1130 "Unable to determine which port to target."
1131 % port_map)
1132 return port_map[0]
1133
1134 def create_floating_ip(self, server, external_network_id=None,
1135 port_id=None, client=None, **kwargs):
1136 """Create a floating IP and associates to a resource/port on Neutron"""
1137
1138 if not external_network_id:
1139 external_network_id = CONF.network.public_network_id
1140 if not client:
1141 client = self.floating_ips_client
1142 if not port_id:
1143 port_id, ip4 = self.get_server_port_id_and_ip4(server)
1144 else:
1145 ip4 = None
1146
1147 floatingip_kwargs = {
1148 'floating_network_id': external_network_id,
1149 'port_id': port_id,
1150 'tenant_id': server.get('project_id') or server['tenant_id'],
1151 'fixed_ip_address': ip4,
1152 }
1153 if CONF.network.subnet_id:
1154 floatingip_kwargs['subnet_id'] = CONF.network.subnet_id
1155
1156 floatingip_kwargs.update(kwargs)
1157 result = client.create_floatingip(**floatingip_kwargs)
1158 floating_ip = result['floatingip']
1159
Jordan Pittier9e227c52016-02-09 14:35:18 +01001160 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Ghanshyam Mann64281392021-03-24 18:48:38 -05001161 client.delete_floatingip,
Yair Friedae0e73d2014-11-24 11:56:26 +02001162 floating_ip['id'])
Ghanshyam Mann64281392021-03-24 18:48:38 -05001163 return floating_ip
1164
Dan Smith49c2b3b2023-04-26 15:52:22 -07001165 def get_floating_ip(self, server):
1166 """Attempt to get an existing floating ip or a server
1167
1168 If one exists, return it, else return None
1169 """
1170 port_id, ip4 = self.get_server_port_id_and_ip4(server)
1171 ips = self.floating_ips_client.list_floatingips(
1172 floating_network_id=CONF.network.public_network_id,
1173 port_id=port_id)
1174 try:
1175 return ips['floatingips'][0]['floating_ip_address']
1176 except (KeyError, IndexError):
1177 return None
1178
Ghanshyam Mann64281392021-03-24 18:48:38 -05001179 def associate_floating_ip(self, floating_ip, server):
1180 """Associate floating ip to server
1181
1182 This wrapper utility attaches the floating_ip for
1183 the respective port_id of server
1184 """
1185 port_id, _ = self.get_server_port_id_and_ip4(server)
1186 kwargs = dict(port_id=port_id)
1187 floating_ip = self.floating_ips_client.update_floatingip(
1188 floating_ip['id'], **kwargs)['floatingip']
1189 self.assertEqual(port_id, floating_ip['port_id'])
1190 return floating_ip
1191
1192 def disassociate_floating_ip(self, floating_ip):
1193 """Disassociates floating ip
1194
1195 This wrapper utility disassociates given floating ip.
1196 :param floating_ip: a dict which is a return value of
1197 floating_ips_client.create_floatingip method
1198 """
1199 kwargs = dict(port_id=None)
1200 floating_ip = self.floating_ips_client.update_floatingip(
1201 floating_ip['id'], **kwargs)['floatingip']
1202 self.assertIsNone(floating_ip['port_id'])
Yair Friedae0e73d2014-11-24 11:56:26 +02001203 return floating_ip
1204
jskundad7445982023-10-18 13:49:02 +02001205 def create_file(self, ip_address, path, private_key=None, server=None,
1206 username=None):
1207 """Create a file on a remote server"""
1208 ssh_client = self.get_remote_client(ip_address,
1209 private_key=private_key,
1210 server=server,
1211 username=username)
1212 ssh_client.exec_command('sudo mkdir -p %s' % path)
1213
Sean Dague20e98612016-01-06 14:33:28 -05001214 def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
Lukas Piwowarski25f7ba22020-10-29 14:01:34 +00001215 private_key=None, server=None, username=None,
Dan Smith30eb8782023-08-02 09:35:01 -07001216 fs='vfat'):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301217 """Creates timestamp
1218
1219 This wrapper utility does ssh, creates timestamp and returns the
1220 created timestamp.
1221 """
Sean Dague20e98612016-01-06 14:33:28 -05001222 ssh_client = self.get_remote_client(ip_address,
Slawek Kaplonski79d8b0f2018-07-30 22:43:41 +02001223 private_key=private_key,
Lukas Piwowarski25f7ba22020-10-29 14:01:34 +00001224 server=server,
1225 username=username)
1226
melanie witt2da632a2023-05-31 03:25:06 +00001227 # Default the directory in which to write the timestamp file to /tmp
1228 # and only use the mount_path as the target directory if we mounted
1229 # dev_name to mount_path.
1230 target_dir = '/tmp'
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001231 if dev_name is not None:
Lukas Piwowarski25f7ba22020-10-29 14:01:34 +00001232 ssh_client.make_fs(dev_name, fs=fs)
Ken'ichi Ohmichi4e5a69e2017-03-01 18:15:29 -08001233 ssh_client.exec_command('sudo mount /dev/%s %s' % (dev_name,
1234 mount_path))
melanie witt2da632a2023-05-31 03:25:06 +00001235 target_dir = mount_path
1236 cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % target_dir
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001237 ssh_client.exec_command(cmd_timestamp)
1238 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
melanie witt2da632a2023-05-31 03:25:06 +00001239 % target_dir)
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001240 if dev_name is not None:
Ken'ichi Ohmichi4e5a69e2017-03-01 18:15:29 -08001241 ssh_client.exec_command('sudo umount %s' % mount_path)
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001242 return timestamp
1243
Sean Dague20e98612016-01-06 14:33:28 -05001244 def get_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
Lukas Piwowarski2c230eb2020-10-30 10:09:18 +00001245 private_key=None, server=None, username=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301246 """Returns timestamp
1247
1248 This wrapper utility does ssh and returns the timestamp.
Lukas Piwowarski2c230eb2020-10-30 10:09:18 +00001249
1250 :param ip_address: The floating IP or fixed IP of the remote server
1251 :param dev_name: Name of the device that stores the timestamp
1252 :param mount_path: Path which should be used as mount point for
1253 dev_name
1254 :param private_key: The SSH private key to use for authentication
1255 :param server: Server dict, used for debugging purposes
1256 :param username: Name of the Linux account on the remote server
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301257 """
1258
Sean Dague20e98612016-01-06 14:33:28 -05001259 ssh_client = self.get_remote_client(ip_address,
Slawek Kaplonski79d8b0f2018-07-30 22:43:41 +02001260 private_key=private_key,
Lukas Piwowarski2c230eb2020-10-30 10:09:18 +00001261 server=server,
1262 username=username)
1263
melanie witt2da632a2023-05-31 03:25:06 +00001264 # Default the directory from which to read the timestamp file to /tmp
1265 # and only use the mount_path as the target directory if we mounted
1266 # dev_name to mount_path.
1267 target_dir = '/tmp'
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001268 if dev_name is not None:
Matt Riedemann076685a2015-09-30 14:38:16 -07001269 ssh_client.mount(dev_name, mount_path)
melanie witt2da632a2023-05-31 03:25:06 +00001270 target_dir = mount_path
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001271 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
melanie witt2da632a2023-05-31 03:25:06 +00001272 % target_dir)
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001273 if dev_name is not None:
Ken'ichi Ohmichi4e5a69e2017-03-01 18:15:29 -08001274 ssh_client.exec_command('sudo umount %s' % mount_path)
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001275 return timestamp
1276
Lukas Piwowarskib0642f92020-10-29 14:51:30 +00001277 def get_server_ip(self, server, **kwargs):
Sean Dague20e98612016-01-06 14:33:28 -05001278 """Get the server fixed or floating IP.
1279
1280 Based on the configuration we're in, return a correct ip
1281 address for validating that a guest is up.
Lukas Piwowarskib0642f92020-10-29 14:51:30 +00001282
1283 If CONF.validation.connect_method is floating, then
1284 a floating ip will be created passing kwargs as additional
1285 argument.
Sean Dague20e98612016-01-06 14:33:28 -05001286 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301287
Alexander Gubanovc8829f82015-11-12 10:35:13 +02001288 if CONF.validation.connect_method == 'floating':
Sean Dague20e98612016-01-06 14:33:28 -05001289 # The tests calling this method don't have a floating IP
zhufl0892cb22016-05-06 14:46:00 +08001290 # and can't make use of the validation resources. So the
Sean Dague20e98612016-01-06 14:33:28 -05001291 # method is creating the floating IP there.
Dan Smith49c2b3b2023-04-26 15:52:22 -07001292 fip = self.get_floating_ip(server)
1293 if fip:
1294 # Already have a floating ip, so use it instead of creating
1295 # another
1296 return fip
1297 else:
1298 return self.create_floating_ip(
1299 server, **kwargs)['floating_ip_address']
Sean Dague20e98612016-01-06 14:33:28 -05001300 elif CONF.validation.connect_method == 'fixed':
Matt Riedemanna7782552016-08-08 16:26:01 -04001301 # Determine the network name to look for based on config or creds
1302 # provider network resources.
1303 if CONF.validation.network_for_ssh:
1304 addresses = server['addresses'][
1305 CONF.validation.network_for_ssh]
1306 else:
zhufl7b4a7202017-09-28 10:29:27 +08001307 network = self.get_tenant_network()
Matt Riedemanna7782552016-08-08 16:26:01 -04001308 addresses = (server['addresses'][network['name']]
1309 if network else [])
Sean Dague20e98612016-01-06 14:33:28 -05001310 for address in addresses:
Federico Ressi2d6bcaa2018-04-11 12:37:36 +02001311 if (address['version'] == CONF.validation.ip_version_for_ssh and # noqa
1312 address['OS-EXT-IPS:type'] == 'fixed'):
Sean Dague20e98612016-01-06 14:33:28 -05001313 return address['addr']
zhufl955f82b2016-07-22 11:14:34 +08001314 raise exceptions.ServerUnreachable(server_id=server['id'])
Alexander Gubanovc8829f82015-11-12 10:35:13 +02001315 else:
Matthew Treinish4217a702016-10-07 17:27:11 -04001316 raise lib_exc.InvalidConfiguration()
Alexander Gubanovc8829f82015-11-12 10:35:13 +02001317
zhufl7bc916d2018-08-22 14:47:39 +08001318 @classmethod
1319 def get_host_for_server(cls, server_id):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301320 """Gets host of server"""
1321
zhufl7bc916d2018-08-22 14:47:39 +08001322 server_details = cls.os_admin.servers_client.show_server(server_id)
1323 return server_details['server']['OS-EXT-SRV-ATTR:host']
1324
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001325 def _get_bdm(self, source_id, source_type, delete_on_termination=False):
1326 bd_map_v2 = [{
1327 'uuid': source_id,
1328 'source_type': source_type,
1329 'destination_type': 'volume',
1330 'boot_index': 0,
1331 'delete_on_termination': delete_on_termination}]
1332 return {'block_device_mapping_v2': bd_map_v2}
1333
1334 def boot_instance_from_resource(self, source_id,
1335 source_type,
1336 keypair=None,
1337 security_group=None,
1338 delete_on_termination=False,
Martin Kopecbee673e2020-11-04 09:40:52 +00001339 name=None, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301340 """Boot instance from resource
1341
1342 This wrapper utility boots instance from resource with block device
1343 mapping with source info passed in arguments
1344 """
1345
Martin Kopecbee673e2020-11-04 09:40:52 +00001346 create_kwargs = dict({'image_id': ''})
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001347 if keypair:
Dan Smith49c2b3b2023-04-26 15:52:22 -07001348 create_kwargs['keypair'] = keypair
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001349 if security_group:
1350 create_kwargs['security_groups'] = [
1351 {'name': security_group['name']}]
1352 create_kwargs.update(self._get_bdm(
1353 source_id,
1354 source_type,
1355 delete_on_termination=delete_on_termination))
1356 if name:
1357 create_kwargs['name'] = name
Martin Kopecbee673e2020-11-04 09:40:52 +00001358 create_kwargs.update(kwargs)
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001359
Martin Kopecbee673e2020-11-04 09:40:52 +00001360 return self.create_server(**create_kwargs)
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001361
Martin Kopec0216b372020-11-04 09:32:05 +00001362 def create_volume_from_image(self, **kwargs):
1363 """Create volume from image.
1364
1365 :param image_id: ID of the image to create volume from,
1366 CONF.compute.image_ref by default
1367 :param name: name of the volume,
1368 '$classname-volume-origin' by default
1369 :param **kwargs: additional parameters
1370 """
1371 image_id = kwargs.pop('image_id', CONF.compute.image_ref)
1372 name = kwargs.pop('name', None)
1373 if not name:
1374 namestart = self.__class__.__name__ + '-volume-origin'
Martin Kopec213d0a42023-11-30 10:28:14 +01001375 name = data_utils.rand_name(
1376 prefix=CONF.resource_name_prefix, name=namestart)
Martin Kopec0216b372020-11-04 09:32:05 +00001377 return self.create_volume(name=name, imageRef=image_id, **kwargs)
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001378
Andrea Frittoli2e733b52014-07-16 14:12:11 +01001379
Andrea Frittoli4971fc82014-09-25 10:22:20 +01001380class NetworkScenarioTest(ScenarioTest):
Yair Fried1fc32a12014-08-04 09:11:30 +03001381 """Base class for network scenario tests.
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001382
Yair Fried1fc32a12014-08-04 09:11:30 +03001383 This class provide helpers for network scenario tests, using the neutron
1384 API. Helpers from ancestor which use the nova network API are overridden
1385 with the neutron API.
1386
1387 This Class also enforces using Neutron instead of novanetwork.
1388 Subclassed tests will be skipped if Neutron is not enabled
1389
1390 """
1391
1392 @classmethod
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001393 def skip_checks(cls):
1394 super(NetworkScenarioTest, cls).skip_checks()
Andrea Frittoli2ddc2632014-09-25 11:03:00 +01001395 if not CONF.service_available.neutron:
1396 raise cls.skipException('Neutron not available')
Yair Fried1fc32a12014-08-04 09:11:30 +03001397
Soniya Vyas3bdafd82021-02-22 18:59:27 +05301398 def create_network(self, networks_client=None,
1399 project_id=None,
1400 namestart='network-smoke-',
1401 port_security_enabled=True, **net_dict):
John Warren94d8faf2015-09-15 12:22:24 -04001402 if not networks_client:
1403 networks_client = self.networks_client
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001404 if not project_id:
1405 project_id = networks_client.project_id
Martin Kopec213d0a42023-11-30 10:28:14 +01001406 name = data_utils.rand_name(
1407 prefix=CONF.resource_name_prefix, name=namestart)
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001408 network_kwargs = dict(name=name, project_id=project_id)
Lajos Katonac87a06b2019-01-04 13:21:48 +01001409 if net_dict:
1410 network_kwargs.update(net_dict)
Matt Riedemann039b2fe2016-09-15 16:12:24 -04001411 # Neutron disables port security by default so we have to check the
1412 # config before trying to create the network with port_security_enabled
1413 if CONF.network_feature_enabled.port_security:
1414 network_kwargs['port_security_enabled'] = port_security_enabled
Markus Zoeller156b5da2016-07-11 18:10:31 +02001415 result = networks_client.create_network(**network_kwargs)
Steve Heyman33735f22016-05-24 09:28:08 -05001416 network = result['network']
1417
1418 self.assertEqual(network['name'], name)
Jordan Pittier9e227c52016-02-09 14:35:18 +01001419 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
zhoubin508bf20b32017-02-03 09:39:14 +08001420 networks_client.delete_network,
Steve Heyman33735f22016-05-24 09:28:08 -05001421 network['id'])
Yair Fried1fc32a12014-08-04 09:11:30 +03001422 return network
1423
zhufl5b0a52f2017-10-24 15:48:20 +08001424 def create_subnet(self, network, subnets_client=None,
1425 namestart='subnet-smoke', **kwargs):
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001426 """Create a subnet for the given network
1427
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301428 This utility creates subnet for the given network
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001429 within the cidr block configured for tenant networks.
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301430
1431 :param **kwargs:
1432 See extra parameters below
1433
1434 :Keyword Arguments:
1435
1436 * *ip_version = ip version of the given network,
Soniya Vyas795ef252020-12-10 19:07:23 +05301437 use_default_subnetpool = default subnetpool to
1438 manage IPv6 addresses range.
Yair Fried1fc32a12014-08-04 09:11:30 +03001439 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301440
John Warren3961acd2015-10-02 14:38:53 -04001441 if not subnets_client:
1442 subnets_client = self.subnets_client
Yair Fried1fc32a12014-08-04 09:11:30 +03001443
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001444 def cidr_in_use(cidr, project_id):
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001445 """Check cidr existence
1446
yangjianfeng4ad346e2020-11-22 06:49:19 +00001447 :returns: True if subnet with cidr already exist in tenant or
1448 external False else
Yair Fried1fc32a12014-08-04 09:11:30 +03001449 """
yangjianfeng4ad346e2020-11-22 06:49:19 +00001450 tenant_subnets = self.os_admin.subnets_client.list_subnets(
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001451 project_id=project_id, cidr=cidr)['subnets']
yangjianfeng4ad346e2020-11-22 06:49:19 +00001452 external_nets = self.os_admin.networks_client.list_networks(
1453 **{"router:external": True})['networks']
1454 external_subnets = []
1455 for ext_net in external_nets:
1456 external_subnets.extend(
1457 self.os_admin.subnets_client.list_subnets(
1458 network_id=ext_net['id'], cidr=cidr)['subnets'])
1459 return len(tenant_subnets + external_subnets) != 0
Yair Fried1fc32a12014-08-04 09:11:30 +03001460
Soniya Vyas795ef252020-12-10 19:07:23 +05301461 def _make_create_subnet_request(namestart, network,
1462 ip_version, subnets_client, **kwargs):
Yair Fried1fc32a12014-08-04 09:11:30 +03001463
1464 subnet = dict(
Martin Kopec213d0a42023-11-30 10:28:14 +01001465 name=data_utils.rand_name(
1466 prefix=CONF.resource_name_prefix, name=namestart),
Steve Heyman33735f22016-05-24 09:28:08 -05001467 network_id=network['id'],
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001468 project_id=network['project_id'],
Kirill Shileev14113572014-11-21 16:58:02 +03001469 ip_version=ip_version,
Yair Fried1fc32a12014-08-04 09:11:30 +03001470 **kwargs
1471 )
Soniya Vyas795ef252020-12-10 19:07:23 +05301472
1473 if ip_version == 6:
1474 subnet['ipv6_address_mode'] = 'slaac'
1475 subnet['ipv6_ra_mode'] = 'slaac'
1476
Yair Fried1fc32a12014-08-04 09:11:30 +03001477 try:
Soniya Vyas795ef252020-12-10 19:07:23 +05301478 return subnets_client.create_subnet(**subnet)
Masayuki Igawad9388762015-01-20 14:56:42 +09001479 except lib_exc.Conflict as e:
Soniya Vyas795ef252020-12-10 19:07:23 +05301480 if 'overlaps with another subnet' not in str(e):
Yair Fried1fc32a12014-08-04 09:11:30 +03001481 raise
Soniya Vyas795ef252020-12-10 19:07:23 +05301482
1483 result = None
1484 str_cidr = None
1485
1486 use_default_subnetpool = kwargs.get('use_default_subnetpool', False)
1487 ip_version = kwargs.pop('ip_version', 4)
1488
1489 if not use_default_subnetpool:
1490
1491 if ip_version == 6:
1492 tenant_cidr = netaddr.IPNetwork(
1493 CONF.network.project_network_v6_cidr)
1494 num_bits = CONF.network.project_network_v6_mask_bits
1495 else:
1496 tenant_cidr = netaddr.IPNetwork(
1497 CONF.network.project_network_cidr)
1498 num_bits = CONF.network.project_network_mask_bits
1499
1500 # Repeatedly attempt subnet creation with sequential cidr
1501 # blocks until an unallocated block is found.
1502 for subnet_cidr in tenant_cidr.subnet(num_bits):
1503 str_cidr = str(subnet_cidr)
1504 if cidr_in_use(str_cidr, project_id=network['project_id']):
1505 continue
1506 result = _make_create_subnet_request(
1507 namestart, network, ip_version, subnets_client,
1508 cidr=str_cidr, **kwargs)
1509
1510 if result is not None:
1511 break
1512
1513 else:
1514 result = _make_create_subnet_request(
1515 namestart, network, ip_version, subnets_client,
1516 **kwargs)
Yair Fried1fc32a12014-08-04 09:11:30 +03001517 self.assertIsNotNone(result, 'Unable to allocate tenant network')
Steve Heyman33735f22016-05-24 09:28:08 -05001518
1519 subnet = result['subnet']
Soniya Vyas795ef252020-12-10 19:07:23 +05301520 if str_cidr is not None:
1521 self.assertEqual(subnet['cidr'], str_cidr)
Steve Heyman33735f22016-05-24 09:28:08 -05001522
1523 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1524 subnets_client.delete_subnet, subnet['id'])
1525
Yair Fried1fc32a12014-08-04 09:11:30 +03001526 return subnet
1527
Soniya Vyasc37410f2021-02-24 15:26:27 +05301528 def get_network_by_name(self, network_name):
jeremy.zhang5870ff12017-05-25 11:24:23 +08001529 net = self.os_admin.networks_client.list_networks(
Jordan Pittier64e6b442017-02-20 19:29:02 +01001530 name=network_name)['networks']
Ferenc Horváth268ccce2017-06-08 12:39:02 +02001531 self.assertNotEmpty(net,
Adam Gandelman878a5fd2015-03-30 14:33:36 -07001532 "Unable to get network by name: %s" % network_name)
Steve Heyman33735f22016-05-24 09:28:08 -05001533 return net[0]
David Shrewsbury9bac3662014-08-07 15:07:01 -04001534
Yair Fried45f92952014-06-26 05:19:19 +03001535 def check_floating_ip_status(self, floating_ip, status):
Carl Baldwina754e2d2014-10-23 22:47:41 +00001536 """Verifies floatingip reaches the given status
Yair Fried45f92952014-06-26 05:19:19 +03001537
Steve Heyman33735f22016-05-24 09:28:08 -05001538 :param dict floating_ip: floating IP dict to check status
Yair Fried45f92952014-06-26 05:19:19 +03001539 :param status: target status
1540 :raises: AssertionError if status doesn't match
1541 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301542
Steve Heyman33735f22016-05-24 09:28:08 -05001543 floatingip_id = floating_ip['id']
1544
Carl Baldwina754e2d2014-10-23 22:47:41 +00001545 def refresh():
Martin Kopecf4b5df62020-01-27 09:44:29 +00001546 floating_ip = (self.floating_ips_client.
1547 show_floatingip(floatingip_id)['floatingip'])
1548 if status == floating_ip['status']:
1549 LOG.info("FloatingIP: {fp} is at status: {st}"
1550 .format(fp=floating_ip, st=status))
1551 return status == floating_ip['status']
Carl Baldwina754e2d2014-10-23 22:47:41 +00001552
zhufl4dda94e2017-03-14 16:14:46 +08001553 if not test_utils.call_until_true(refresh,
1554 CONF.network.build_timeout,
1555 CONF.network.build_interval):
1556 floating_ip = self.floating_ips_client.show_floatingip(
1557 floatingip_id)['floatingip']
1558 self.assertEqual(status, floating_ip['status'],
1559 message="FloatingIP: {fp} is at status: {cst}. "
1560 "failed to reach status: {st}"
1561 .format(fp=floating_ip, cst=floating_ip['status'],
1562 st=status))
Yair Fried45f92952014-06-26 05:19:19 +03001563
zhufl420a0192017-09-28 11:04:50 +08001564 def check_tenant_network_connectivity(self, server,
1565 username,
1566 private_key,
1567 should_connect=True,
1568 servers_for_debug=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301569 """Checks tenant network connectivity"""
Sean Dagueed6e5862016-04-04 10:49:13 -04001570 if not CONF.network.project_networks_reachable:
Yair Fried1fc32a12014-08-04 09:11:30 +03001571 msg = 'Tenant networks not configured to be reachable.'
1572 LOG.info(msg)
1573 return
1574 # The target login is assumed to have been configured for
1575 # key-based authentication by cloud-init.
1576 try:
Béla Vancsicsb6dfa082017-03-01 10:44:58 +01001577 for ip_addresses in server['addresses'].values():
Yair Fried1fc32a12014-08-04 09:11:30 +03001578 for ip_address in ip_addresses:
ghanshyam807211c2014-12-18 13:21:22 +09001579 self.check_vm_connectivity(ip_address['addr'],
Yair Friedae0e73d2014-11-24 11:56:26 +02001580 username,
1581 private_key,
1582 should_connect=should_connect)
Yair Fried1fc32a12014-08-04 09:11:30 +03001583 except Exception as e:
1584 LOG.exception('Tenant network connectivity check failed')
Soniya Vyas1b0cddc2021-01-29 17:28:19 +05301585 self.log_console_output(servers_for_debug)
Ken'ichi Ohmichi6e201f52014-10-01 04:21:39 +00001586 self._log_net_info(e)
Yair Fried1fc32a12014-08-04 09:11:30 +03001587 raise
1588
zhufle9877c62017-10-13 09:38:19 +08001589 def check_remote_connectivity(self, source, dest, should_succeed=True,
Claudiu Belu33c3e602014-08-28 16:38:01 +03001590 nic=None, protocol='icmp'):
1591 """check server connectivity via source ssh connection
YAMAMOTO Takashi4c3ebb02017-01-25 16:04:30 +09001592
Claudiu Belu33c3e602014-08-28 16:38:01 +03001593 :param source: RemoteClient: an ssh connection from which to execute
1594 the check
1595 :param dest: an IP to check connectivity against
1596 :param should_succeed: boolean should connection succeed or not
1597 :param nic: specific network interface to test connectivity from
1598 :param protocol: the protocol used to test connectivity with.
1599 :returns: True, if the connection succeeded and it was expected to
1600 succeed. False otherwise.
Yair Fried1fc32a12014-08-04 09:11:30 +03001601 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301602
Claudiu Belu33c3e602014-08-28 16:38:01 +03001603 method_name = '%s_check' % protocol
1604 connectivity_checker = getattr(source, method_name)
1605
1606 def connect_remote():
Yair Fried1fc32a12014-08-04 09:11:30 +03001607 try:
Claudiu Belu33c3e602014-08-28 16:38:01 +03001608 connectivity_checker(dest, nic=nic)
Andrey Pavlov64723762015-04-29 06:24:58 +03001609 except lib_exc.SSHExecCommandFailed:
Claudiu Belu33c3e602014-08-28 16:38:01 +03001610 LOG.warning('Failed to check %(protocol)s connectivity for '
1611 'IP %(dest)s via a ssh connection from: %(src)s.',
1612 dict(protocol=protocol, dest=dest,
1613 src=source.ssh_client.host))
Yair Fried1fc32a12014-08-04 09:11:30 +03001614 return not should_succeed
1615 return should_succeed
1616
Claudiu Belu33c3e602014-08-28 16:38:01 +03001617 result = test_utils.call_until_true(connect_remote,
zhufle9877c62017-10-13 09:38:19 +08001618 CONF.validation.ping_timeout, 1)
Ihar Hrachyshkaf9fda2d2017-11-06 13:16:09 -08001619 if result:
1620 return
1621
YAMAMOTO Takashi4c3ebb02017-01-25 16:04:30 +09001622 source_host = source.ssh_client.host
1623 if should_succeed:
1624 msg = "Timed out waiting for %s to become reachable from %s" \
1625 % (dest, source_host)
1626 else:
1627 msg = "%s is reachable from %s" % (dest, source_host)
Soniya Vyas1b0cddc2021-01-29 17:28:19 +05301628 self.log_console_output()
Ihar Hrachyshkaf9fda2d2017-11-06 13:16:09 -08001629 self.fail(msg)
YAMAMOTO Takashi4c3ebb02017-01-25 16:04:30 +09001630
Soniya Vyas73555df2021-03-04 19:05:42 +05301631 def get_router(self, client=None, project_id=None, **kwargs):
Yair Fried1fc32a12014-08-04 09:11:30 +03001632 """Retrieve a router for the given tenant id.
1633
1634 If a public router has been configured, it will be returned.
1635
1636 If a public router has not been configured, but a public
1637 network has, a tenant router will be created and returned that
1638 routes traffic to the public network.
1639 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301640
Yair Frieddb6c9e92014-08-06 08:53:13 +03001641 if not client:
Ken'ichi Ohmichie35f4722015-12-22 04:57:11 +00001642 client = self.routers_client
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001643 if not project_id:
1644 project_id = client.project_id
Yair Fried1fc32a12014-08-04 09:11:30 +03001645 router_id = CONF.network.public_router_id
1646 network_id = CONF.network.public_network_id
1647 if router_id:
David Kranzca4c7e72015-05-27 11:39:19 -04001648 body = client.show_router(router_id)
Steve Heyman33735f22016-05-24 09:28:08 -05001649 return body['router']
Yair Fried1fc32a12014-08-04 09:11:30 +03001650 elif network_id:
Martin Kopec0090a102020-11-03 13:50:19 +00001651 name = kwargs.pop('name', None)
1652 if not name:
1653 namestart = self.__class__.__name__ + '-router'
Martin Kopec213d0a42023-11-30 10:28:14 +01001654 name = data_utils.rand_name(
1655 prefix=CONF.resource_name_prefix, name=namestart)
Martin Kopec0090a102020-11-03 13:50:19 +00001656
1657 ext_gw_info = kwargs.pop('external_gateway_info', None)
1658 if not ext_gw_info:
1659 ext_gw_info = dict(network_id=network_id)
zhufl3484f992017-10-10 16:18:29 +08001660 router = client.create_router(
Martin Kopec0090a102020-11-03 13:50:19 +00001661 name=name,
1662 admin_state_up=kwargs.get('admin_state_up', True),
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001663 project_id=project_id,
Martin Kopec0090a102020-11-03 13:50:19 +00001664 external_gateway_info=ext_gw_info,
1665 **kwargs)['router']
zhufl3484f992017-10-10 16:18:29 +08001666 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1667 client.delete_router, router['id'])
Yair Fried1fc32a12014-08-04 09:11:30 +03001668 return router
1669 else:
1670 raise Exception("Neither of 'public_router_id' or "
1671 "'public_network_id' has been defined.")
1672
Ghanshyam Mann071d1542021-03-24 19:10:47 -05001673 def setup_network_subnet_with_router(
1674 self, networks_client=None,
1675 routers_client=None, subnets_client=None,
1676 project_id=None, dns_nameservers=None,
1677 port_security_enabled=True, **net_dict):
Yair Fried1fc32a12014-08-04 09:11:30 +03001678 """Create a network with a subnet connected to a router.
1679
David Shrewsbury9bac3662014-08-07 15:07:01 -04001680 The baremetal driver is a special case since all nodes are
1681 on the same shared network.
1682
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001683 :param project_id: id of project to create resources in.
Yair Fried413bf2d2014-11-19 17:07:11 +02001684 :param dns_nameservers: list of dns servers to send to subnet.
Lajos Katonac87a06b2019-01-04 13:21:48 +01001685 :param port_security_enabled: whether or not port_security is enabled
elajkate453fc22019-06-13 15:03:43 +02001686 :param net_dict: a dict containing experimental network information in
Lajos Katonac87a06b2019-01-04 13:21:48 +01001687 a form like this: {'provider:network_type': 'vlan',
1688 'provider:physical_network': 'foo',
1689 'provider:segmentation_id': '42'}
Yair Fried1fc32a12014-08-04 09:11:30 +03001690 :returns: network, subnet, router
1691 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301692
Thiago Paiva66cded22016-08-15 14:55:58 -03001693 if CONF.network.shared_physical_network:
David Shrewsbury9bac3662014-08-07 15:07:01 -04001694 # NOTE(Shrews): This exception is for environments where tenant
1695 # credential isolation is available, but network separation is
1696 # not (the current baremetal case). Likely can be removed when
1697 # test account mgmt is reworked:
1698 # https://blueprints.launchpad.net/tempest/+spec/test-accounts
Adam Gandelman878a5fd2015-03-30 14:33:36 -07001699 if not CONF.compute.fixed_network_name:
1700 m = 'fixed_network_name must be specified in config'
Matthew Treinish4217a702016-10-07 17:27:11 -04001701 raise lib_exc.InvalidConfiguration(m)
Soniya Vyasc37410f2021-02-24 15:26:27 +05301702 network = self.get_network_by_name(
David Shrewsbury9bac3662014-08-07 15:07:01 -04001703 CONF.compute.fixed_network_name)
1704 router = None
1705 subnet = None
1706 else:
Soniya Vyas3bdafd82021-02-22 18:59:27 +05301707 network = self.create_network(
Ken'ichi Ohmichi43e7fcf2016-04-04 11:59:13 -07001708 networks_client=networks_client,
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001709 project_id=project_id,
Lajos Katonac87a06b2019-01-04 13:21:48 +01001710 port_security_enabled=port_security_enabled,
1711 **net_dict)
Soniya Vyas73555df2021-03-04 19:05:42 +05301712 router = self.get_router(client=routers_client,
1713 project_id=project_id)
Ken'ichi Ohmichi43e7fcf2016-04-04 11:59:13 -07001714 subnet_kwargs = dict(network=network,
zhufl5b0a52f2017-10-24 15:48:20 +08001715 subnets_client=subnets_client)
Yair Fried413bf2d2014-11-19 17:07:11 +02001716 # use explicit check because empty list is a valid option
1717 if dns_nameservers is not None:
1718 subnet_kwargs['dns_nameservers'] = dns_nameservers
zhufl5b0a52f2017-10-24 15:48:20 +08001719 subnet = self.create_subnet(**subnet_kwargs)
Steve Heyman33735f22016-05-24 09:28:08 -05001720 if not routers_client:
1721 routers_client = self.routers_client
1722 router_id = router['id']
1723 routers_client.add_router_interface(router_id,
1724 subnet_id=subnet['id'])
1725
1726 # save a cleanup job to remove this association between
1727 # router and subnet
1728 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1729 routers_client.remove_router_interface, router_id,
1730 subnet_id=subnet['id'])
Yair Fried1fc32a12014-08-04 09:11:30 +03001731 return network, subnet, router
1732
1733
Masayuki Igawa1f0ad632014-08-05 13:36:56 +09001734class EncryptionScenarioTest(ScenarioTest):
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001735 """Base class for encryption scenario tests"""
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001736
David Kranz4cc852b2015-03-09 14:57:11 -04001737 @classmethod
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001738 def setup_clients(cls):
1739 super(EncryptionScenarioTest, cls).setup_clients()
ghanshyam6c682ff2018-08-06 09:54:45 +00001740 cls.admin_volume_types_client = cls.os_admin.volume_types_client_latest
ghanshyam3bd0d2b2017-03-23 01:57:28 +00001741 cls.admin_encryption_types_client =\
ghanshyam6c682ff2018-08-06 09:54:45 +00001742 cls.os_admin.encryption_types_client_latest
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001743
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001744 def create_encryption_type(self, client=None, type_id=None, provider=None,
1745 key_size=None, cipher=None,
1746 control_location=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301747 """Creates an encryption type for volume"""
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001748 if not client:
Ken'ichi Ohmichia6ebf622016-08-25 11:52:27 -07001749 client = self.admin_encryption_types_client
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001750 if not type_id:
1751 volume_type = self.create_volume_type()
Masayuki Igawa1f0ad632014-08-05 13:36:56 +09001752 type_id = volume_type['id']
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001753 LOG.debug("Creating an encryption type for volume type: %s", type_id)
Masayuki Igawa1f0ad632014-08-05 13:36:56 +09001754 client.create_encryption_type(
1755 type_id, provider=provider, key_size=key_size, cipher=cipher,
jeremy.zhangb6f67f62018-02-11 09:28:52 +08001756 control_location=control_location)
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001757
lkuchlan3023e752017-06-08 12:53:13 +03001758 def create_encrypted_volume(self, encryption_provider, volume_type,
1759 key_size=256, cipher='aes-xts-plain64',
Ghanshyam Mann51c0f9a2023-07-21 14:09:40 -05001760 control_location='front-end',
1761 wait_until='available'):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301762 """Creates an encrypted volume"""
lkuchlan3023e752017-06-08 12:53:13 +03001763 volume_type = self.create_volume_type(name=volume_type)
1764 self.create_encryption_type(type_id=volume_type['id'],
1765 provider=encryption_provider,
1766 key_size=key_size,
1767 cipher=cipher,
1768 control_location=control_location)
Ghanshyam Mann51c0f9a2023-07-21 14:09:40 -05001769 return self.create_volume(volume_type=volume_type['name'],
1770 wait_until=wait_until)
lkuchlan3023e752017-06-08 12:53:13 +03001771
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001772
Masayuki Igawa0870db52015-09-18 21:08:36 +09001773class ObjectStorageScenarioTest(ScenarioTest):
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001774 """Provide harness to do Object Storage scenario tests.
Chris Dent0d494112014-08-26 13:48:30 +01001775
1776 Subclasses implement the tests that use the methods provided by this
1777 class.
1778 """
1779
Ghanshyam Mann64281392021-03-24 18:48:38 -05001780 credentials = ['primary']
1781
Chris Dent0d494112014-08-26 13:48:30 +01001782 @classmethod
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001783 def skip_checks(cls):
Masayuki Igawa0870db52015-09-18 21:08:36 +09001784 super(ObjectStorageScenarioTest, cls).skip_checks()
Chris Dent0d494112014-08-26 13:48:30 +01001785 if not CONF.service_available.swift:
1786 skip_msg = ("%s skipped as swift is not available" %
1787 cls.__name__)
1788 raise cls.skipException(skip_msg)
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001789
1790 @classmethod
1791 def setup_credentials(cls):
Masayuki Igawa60ea6c52014-10-15 17:32:14 +09001792 cls.set_network_resources()
Masayuki Igawa0870db52015-09-18 21:08:36 +09001793 super(ObjectStorageScenarioTest, cls).setup_credentials()
Matthew Treinish4a596932015-03-06 20:37:01 -05001794 operator_role = CONF.object_storage.operator_role
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +01001795 cls.os_operator = cls.get_client_manager(roles=[operator_role])
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001796
1797 @classmethod
1798 def setup_clients(cls):
Masayuki Igawa0870db52015-09-18 21:08:36 +09001799 super(ObjectStorageScenarioTest, cls).setup_clients()
Chris Dent0d494112014-08-26 13:48:30 +01001800 # Clients for Swift
Matthew Treinish8f268292015-02-24 20:01:36 -05001801 cls.account_client = cls.os_operator.account_client
1802 cls.container_client = cls.os_operator.container_client
1803 cls.object_client = cls.os_operator.object_client
Chris Dent0d494112014-08-26 13:48:30 +01001804
Chris Dentde456a12014-09-10 12:41:15 +01001805 def get_swift_stat(self):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301806 """Get swift status for our user account."""
Chris Dent0d494112014-08-26 13:48:30 +01001807 self.account_client.list_account_containers()
1808 LOG.debug('Swift status information obtained successfully')
1809
Chris Dentde456a12014-09-10 12:41:15 +01001810 def create_container(self, container_name=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301811 """Creates container"""
Chris Dent0d494112014-08-26 13:48:30 +01001812 name = container_name or data_utils.rand_name(
Martin Kopec213d0a42023-11-30 10:28:14 +01001813 prefix=CONF.resource_name_prefix, name='swift-scenario-container')
ghanshyameed40312017-09-15 18:30:04 +03001814 self.container_client.update_container(name)
Chris Dent0d494112014-08-26 13:48:30 +01001815 # look for the container to assure it is created
Chris Dentde456a12014-09-10 12:41:15 +01001816 self.list_and_check_container_objects(name)
Jordan Pittier525ec712016-12-07 17:51:26 +01001817 LOG.debug('Container %s created', name)
Jordan Pittier9e227c52016-02-09 14:35:18 +01001818 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Chris Dent1d4313a2014-10-28 12:16:48 +00001819 self.container_client.delete_container,
1820 name)
Chris Dent0d494112014-08-26 13:48:30 +01001821 return name
1822
Chris Dentde456a12014-09-10 12:41:15 +01001823 def delete_container(self, container_name):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301824 """Deletes container"""
Chris Dent0d494112014-08-26 13:48:30 +01001825 self.container_client.delete_container(container_name)
Jordan Pittier525ec712016-12-07 17:51:26 +01001826 LOG.debug('Container %s deleted', container_name)
Chris Dent0d494112014-08-26 13:48:30 +01001827
Chris Dentde456a12014-09-10 12:41:15 +01001828 def upload_object_to_container(self, container_name, obj_name=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301829 """Uploads object to container"""
Martin Kopec213d0a42023-11-30 10:28:14 +01001830 obj_name = obj_name or data_utils.rand_name(
1831 prefix=CONF.resource_name_prefix, name='swift-scenario-object')
Jordan Pittierb84f2d42016-12-21 19:02:15 +01001832 obj_data = data_utils.random_bytes()
Chris Dent0d494112014-08-26 13:48:30 +01001833 self.object_client.create_object(container_name, obj_name, obj_data)
Jordan Pittier9e227c52016-02-09 14:35:18 +01001834 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Chris Dent1d4313a2014-10-28 12:16:48 +00001835 self.object_client.delete_object,
1836 container_name,
1837 obj_name)
Chris Dent0d494112014-08-26 13:48:30 +01001838 return obj_name, obj_data
1839
Chris Dentde456a12014-09-10 12:41:15 +01001840 def delete_object(self, container_name, filename):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301841 """Deletes object"""
Chris Dent0d494112014-08-26 13:48:30 +01001842 self.object_client.delete_object(container_name, filename)
Chris Dentde456a12014-09-10 12:41:15 +01001843 self.list_and_check_container_objects(container_name,
1844 not_present_obj=[filename])
Chris Dent0d494112014-08-26 13:48:30 +01001845
Chris Dentde456a12014-09-10 12:41:15 +01001846 def list_and_check_container_objects(self, container_name,
1847 present_obj=None,
1848 not_present_obj=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301849 """List and verify objects for a given container
1850
1851 This utility lists objects for a given container
1852 and asserts which are present and
1853 which are not
1854 """
1855
Ghanshyam2a180b82014-06-16 13:54:22 +09001856 if present_obj is None:
1857 present_obj = []
1858 if not_present_obj is None:
1859 not_present_obj = []
ghanshyam871b1a82017-09-14 02:56:16 +03001860 _, object_list = self.container_client.list_container_objects(
Chris Dent0d494112014-08-26 13:48:30 +01001861 container_name)
1862 if present_obj:
1863 for obj in present_obj:
1864 self.assertIn(obj, object_list)
1865 if not_present_obj:
1866 for obj in not_present_obj:
1867 self.assertNotIn(obj, object_list)
1868
Chris Dentde456a12014-09-10 12:41:15 +01001869 def download_and_verify(self, container_name, obj_name, expected_data):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301870 """Asserts the object and expected data to verify if they are same"""
Chris Dent0d494112014-08-26 13:48:30 +01001871 _, obj = self.object_client.get_object(container_name, obj_name)
1872 self.assertEqual(obj, expected_data)