blob: 3105e85d1b500268905c254f7eadb64534cd6495 [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
zhuflf52c7592017-05-25 13:55:24 +0800754 def get_remote_client(self, ip_address, username=None, private_key=None,
755 server=None):
JordanP3fe2dc32014-11-17 13:06:01 +0100756 """Get a SSH client to a remote server
757
Sergey Vilgelmeac094a2018-11-21 18:27:51 -0600758 :param ip_address: the server floating or fixed IP address to use
759 for ssh validation
760 :param username: name of the Linux account on the remote server
761 :param private_key: the SSH private key to use
762 :param server: server dict, used for debugging purposes
763 :return: a RemoteClient object
JordanP3fe2dc32014-11-17 13:06:01 +0100764 """
Adam Gandelmanc78c7572014-08-28 18:38:55 -0700765
Andrea Frittoli247058f2014-07-16 16:09:22 +0100766 if username is None:
lanoux283273b2015-12-04 03:01:54 -0800767 username = CONF.validation.image_ssh_user
wantwatering896300c2015-03-27 15:17:42 +0800768 # Set this with 'keypair' or others to log in with keypair or
769 # username/password.
lanoux5fc14522015-09-21 08:17:35 +0000770 if CONF.validation.auth_method == 'keypair':
wantwatering896300c2015-03-27 15:17:42 +0800771 password = None
772 if private_key is None:
773 private_key = self.keypair['private_key']
774 else:
lanoux283273b2015-12-04 03:01:54 -0800775 password = CONF.validation.image_ssh_password
wantwatering896300c2015-03-27 15:17:42 +0800776 private_key = None
zhuflf52c7592017-05-25 13:55:24 +0800777 linux_client = remote_client.RemoteClient(
778 ip_address, username, pkey=private_key, password=password,
779 server=server, servers_client=self.servers_client)
Pavlo Shchelokovskyydb18e732023-08-09 13:51:48 +0300780 try:
781 linux_client.validate_authentication()
782 except Exception as e:
783 message = ('Initializing SSH connection to %(ip)s failed. '
784 'Error: %(error)s' % {'ip': ip_address,
785 'error': e})
786 caller = test_utils.find_test_caller()
787 if caller:
788 message = '(%s) %s' % (caller, message)
789 LOG.exception(message)
790 servers = (server,) if server else None
791 self.log_console_output(servers=servers)
792 raise
793
Andrea Frittoli247058f2014-07-16 16:09:22 +0100794 return linux_client
795
Lukas Piwowarskib50eabe2020-11-05 15:15:38 +0000796 def image_create(self, name='scenario-img', **kwargs):
Martin Kopec02af6a42020-03-03 12:39:12 +0000797 img_path = CONF.scenario.img_file
798 if not os.path.exists(img_path):
Martin Kopec008950e2020-09-29 08:12:39 +0000799 lib_exc.InvalidConfiguration(
Martin Kopec02af6a42020-03-03 12:39:12 +0000800 'Starting Tempest 25.0.0 release, CONF.scenario.img_file need '
801 'a full path for the image. CONF.scenario.img_dir was '
802 'deprecated and will be removed in the next release. Till '
Martin Kopec008950e2020-09-29 08:12:39 +0000803 'Tempest 25.0.0, old behavior was maintained and kept working '
Martin Kopec02af6a42020-03-03 12:39:12 +0000804 'but starting Tempest 26.0.0, you need to specify the full '
805 'path in CONF.scenario.img_file config option.')
Alessandro Pilottib7c1daa2014-08-16 14:24:13 +0300806 img_container_format = CONF.scenario.img_container_format
807 img_disk_format = CONF.scenario.img_disk_format
Evgeny Antyshev7ba0d5f2015-04-28 13:18:07 +0000808 img_properties = CONF.scenario.img_properties
PranaliD2aa523c2016-06-07 03:54:34 -0400809 LOG.debug("paths: img: %s, container_format: %s, disk_format: %s, "
Martin Kopec02af6a42020-03-03 12:39:12 +0000810 "properties: %s",
Jordan Pittier525ec712016-12-07 17:51:26 +0100811 img_path, img_container_format, img_disk_format,
Martin Kopec02af6a42020-03-03 12:39:12 +0000812 img_properties)
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530813 if img_properties is None:
814 img_properties = {}
Martin Kopec213d0a42023-11-30 10:28:14 +0100815 name = data_utils.rand_name(
816 prefix=CONF.resource_name_prefix, name='%s-' % name)
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530817 params = {
818 'name': name,
819 'container_format': img_container_format,
820 'disk_format': img_disk_format or img_container_format,
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700821 'visibility': 'private'
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530822 }
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700823 # Additional properties are flattened out in the v2 API.
824 if img_properties:
825 params.update(img_properties)
Lukas Piwowarskib50eabe2020-11-05 15:15:38 +0000826 params.update(kwargs)
melanie witta0b161b2023-12-01 23:41:44 +0000827
828 # This code is basically copying the devstack code that extracts and
829 # uploads split kernel/ramdisk images.
830 if tarfile.is_tarfile(img_path):
831 extract_dir = os.path.join(tempfile.gettempdir(), 'images', name)
832 self.addCleanup(shutil.rmtree, extract_dir)
833 os.makedirs(extract_dir)
834 with tarfile.open(img_path) as tar:
835 tar.extractall(extract_dir, filter='data')
836 filenames = os.listdir(extract_dir)
837 for fname in filenames:
838 if re.search(r'(.*-vmlinuz.*|aki-.*/image$)', fname):
839 kernel_img_path = os.path.join(extract_dir, fname)
840 elif re.search(r'(.*-initrd.*|ari-.*/image$)', fname):
841 ramdisk_img_path = os.path.join(extract_dir, fname)
842 elif re.search(f'(.*\\.img$|ami-.*/image$)', fname):
843 img_path = os.path.join(extract_dir, fname)
844 # Create the kernel image.
845 kparams = {
846 'name': name + '-kernel',
847 'container_format': 'aki',
848 'disk_format': 'aki',
849 'visibility': 'private'
850 }
851 body = self.image_client.create_image(**kparams)
852 image = body['image'] if 'image' in body else body
853 kernel_id = image['id']
854 self.addCleanup(self.image_client.delete_image, kernel_id)
855 self.assertEqual("queued", image['status'])
856 with open(kernel_img_path, 'rb') as image_file:
857 self.image_client.store_image_file(kernel_id, image_file)
858 LOG.debug("image:%s", kernel_id)
859 # Create the ramdisk image.
860 rparams = {
861 'name': name + '-ramdisk',
862 'container_format': 'ari',
863 'disk_format': 'ari',
864 'visibility': 'private'
865 }
866 body = self.image_client.create_image(**rparams)
867 image = body['image'] if 'image' in body else body
868 ramdisk_id = image['id']
869 self.addCleanup(self.image_client.delete_image, ramdisk_id)
870 self.assertEqual("queued", image['status'])
871 with open(ramdisk_img_path, 'rb') as image_file:
872 self.image_client.store_image_file(ramdisk_id, image_file)
873 LOG.debug("image:%s", ramdisk_id)
874 # Set the kernel_id, ramdisk_id, container format, disk format for
875 # the split image.
876 params['kernel_id'] = kernel_id
877 params['ramdisk_id'] = ramdisk_id
878 params['container_format'] = 'ami'
879 params['disk_format'] = 'ami'
880
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530881 body = self.image_client.create_image(**params)
882 image = body['image'] if 'image' in body else body
883 self.addCleanup(self.image_client.delete_image, image['id'])
884 self.assertEqual("queued", image['status'])
885 with open(img_path, 'rb') as image_file:
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700886 self.image_client.store_image_file(image['id'], image_file)
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530887 LOG.debug("image:%s", image['id'])
888 return image['id']
Andrea Frittoli247058f2014-07-16 16:09:22 +0100889
Soniya Vyas1b0cddc2021-01-29 17:28:19 +0530890 def log_console_output(self, servers=None, client=None, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530891 """Console log output"""
Matthew Treinish42a3f3a2014-09-04 15:04:53 -0400892 if not CONF.compute_feature_enabled.console_output:
893 LOG.debug('Console output not supported, cannot log')
894 return
Ihar Hrachyshkaa9dca2b2017-04-04 14:17:11 -0700895 client = client or self.servers_client
Andrea Frittoli247058f2014-07-16 16:09:22 +0100896 if not servers:
Ihar Hrachyshkaa9dca2b2017-04-04 14:17:11 -0700897 servers = client.list_servers()
Andrea Frittoli247058f2014-07-16 16:09:22 +0100898 servers = servers['servers']
899 for server in servers:
Attila Fazekas9a5a1122016-11-08 10:24:57 +0100900 try:
Ihar Hrachyshkaa9dca2b2017-04-04 14:17:11 -0700901 console_output = client.get_console_output(
Lukas Piwowarski91ded042020-10-29 15:15:25 +0000902 server['id'], **kwargs)['output']
Attila Fazekas9a5a1122016-11-08 10:24:57 +0100903 LOG.debug('Console output for %s\nbody=\n%s',
904 server['id'], console_output)
905 except lib_exc.NotFound:
Attila Fazekase1360482016-11-10 11:28:08 +0100906 LOG.debug("Server %s disappeared(deleted) while looking "
Attila Fazekas9a5a1122016-11-08 10:24:57 +0100907 "for the console log", server['id'])
Andrea Frittoli247058f2014-07-16 16:09:22 +0100908
Ken'ichi Ohmichi6e201f52014-10-01 04:21:39 +0000909 def _log_net_info(self, exc):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530910 """network debug is called as part of ssh init"""
Andrey Pavlov64723762015-04-29 06:24:58 +0300911 if not isinstance(exc, lib_exc.SSHTimeout):
Ken'ichi Ohmichi6e201f52014-10-01 04:21:39 +0000912 LOG.debug('Network information on a devstack host')
Ken'ichi Ohmichi6e201f52014-10-01 04:21:39 +0000913
Lukas Piwowarski9ad9ca22020-10-29 15:36:30 +0000914 def create_server_snapshot(self, server, name=None, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530915 """Creates server snapshot"""
nithya-ganesan882595e2014-07-29 18:51:07 +0000916 # Glance client
917 _image_client = self.image_client
918 # Compute client
Ghanshyamae76c122015-12-22 13:41:35 +0900919 _images_client = self.compute_images_client
nithya-ganesan882595e2014-07-29 18:51:07 +0000920 if name is None:
Martin Kopec213d0a42023-11-30 10:28:14 +0100921 name = data_utils.rand_name(
922 prefix=CONF.resource_name_prefix,
923 name=self.__class__.__name__ + 'snapshot')
nithya-ganesan882595e2014-07-29 18:51:07 +0000924 LOG.debug("Creating a snapshot image for server: %s", server['name'])
Lukas Piwowarski9ad9ca22020-10-29 15:36:30 +0000925 image = _images_client.create_image(server['id'], name=name, **kwargs)
Benny Kopilov7d2edc22022-06-30 17:22:14 +0300926 # microversion 2.45 and above returns image_id
927 image_id = image.get('image_id') or image.response['location'].split(
928 'images/')[1]
Yaroslav Lobankov2fea4052016-04-19 15:05:57 +0300929 waiters.wait_for_image_status(_image_client, image_id, 'active')
Jordan Pittierf672b7d2016-06-20 18:50:40 +0200930
931 self.addCleanup(_image_client.wait_for_resource_deletion,
932 image_id)
933 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
934 _image_client.delete_image, image_id)
935
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700936 # In glance v2 the additional properties are flattened.
937 snapshot_image = _image_client.show_image(image_id)
938 image_props = snapshot_image
Andrey Pavlovc8bd4b12015-08-17 10:20:17 +0300939
Matt Riedemann2aa19d42016-06-06 17:45:41 -0400940 bdm = image_props.get('block_device_mapping')
Andrey Pavlovc8bd4b12015-08-17 10:20:17 +0300941 if bdm:
942 bdm = json.loads(bdm)
943 if bdm and 'snapshot_id' in bdm[0]:
944 snapshot_id = bdm[0]['snapshot_id']
945 self.addCleanup(
946 self.snapshots_client.wait_for_resource_deletion,
947 snapshot_id)
Jordan Pittier9e227c52016-02-09 14:35:18 +0100948 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
949 self.snapshots_client.delete_snapshot,
950 snapshot_id)
lkuchlan52d7b0d2016-11-07 20:53:19 +0200951 waiters.wait_for_volume_resource_status(self.snapshots_client,
952 snapshot_id,
953 'available')
nithya-ganesan882595e2014-07-29 18:51:07 +0000954 image_name = snapshot_image['name']
955 self.assertEqual(name, image_name)
956 LOG.debug("Created snapshot image %s for server %s",
957 image_name, server['name'])
958 return snapshot_image
959
Milana Levye9a58a12023-02-21 12:55:20 +0000960 def nova_volume_attach(self, server, volume_to_attach,
961 volumes_client=None, servers_client=None,
962 **kwargs):
Soniya Vyasae631132020-08-28 13:37:12 +0530963 """Compute volume attach
964
965 This utility attaches volume from compute and waits for the
966 volume status to be 'in-use' state.
967 """
Milana Levye9a58a12023-02-21 12:55:20 +0000968 if volumes_client is None:
969 volumes_client = self.volumes_client
970 if servers_client is None:
971 servers_client = self.servers_client
972
973 volume = servers_client.attach_volume(
Lukas Piwowarski76819fa2020-10-29 13:46:07 +0000974 server['id'], volumeId=volume_to_attach['id'],
975 **kwargs)['volumeAttachment']
Jordan Pittier7cf64762015-10-14 15:01:12 +0200976 self.assertEqual(volume_to_attach['id'], volume['id'])
Milana Levye9a58a12023-02-21 12:55:20 +0000977 waiters.wait_for_volume_resource_status(volumes_client,
lkuchlan52d7b0d2016-11-07 20:53:19 +0200978 volume['id'], 'in-use')
Lukas Piwowarski76819fa2020-10-29 13:46:07 +0000979 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Milana Levye9a58a12023-02-21 12:55:20 +0000980 self.nova_volume_detach, server, volume,
981 servers_client)
Jordan Pittier7cf64762015-10-14 15:01:12 +0200982 # Return the updated volume after the attachment
Milana Levye9a58a12023-02-21 12:55:20 +0000983 return volumes_client.show_volume(volume['id'])['volume']
Masayuki Igawa1f0ad632014-08-05 13:36:56 +0900984
Milana Levye9a58a12023-02-21 12:55:20 +0000985 def nova_volume_detach(self, server, volume, servers_client=None):
Soniya Vyasae631132020-08-28 13:37:12 +0530986 """Compute volume detach
987
Lee Yarwood5423c532020-12-17 11:24:46 +0000988 This utility detaches the volume from the server and checks whether the
989 volume attachment has been removed from Nova.
Soniya Vyasae631132020-08-28 13:37:12 +0530990 """
Milana Levye9a58a12023-02-21 12:55:20 +0000991 if servers_client is None:
992 servers_client = self.servers_client
993
994 servers_client.detach_volume(server['id'], volume['id'])
Lee Yarwood5423c532020-12-17 11:24:46 +0000995 waiters.wait_for_volume_attachment_remove_from_server(
Milana Levye9a58a12023-02-21 12:55:20 +0000996 servers_client, server['id'], volume['id'])
Jordan Pittier7cf64762015-10-14 15:01:12 +0200997
Steven Hardyda2a8352014-10-02 12:52:20 +0100998 def ping_ip_address(self, ip_address, should_succeed=True,
zhufl0ec74c42017-11-15 14:02:28 +0800999 ping_timeout=None, mtu=None, server=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301000 """ping ip address"""
lanoux5fc14522015-09-21 08:17:35 +00001001 timeout = ping_timeout or CONF.validation.ping_timeout
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +00001002 cmd = ['ping', '-c1', '-w1']
1003
1004 if mtu:
1005 cmd += [
1006 # don't fragment
1007 '-M', 'do',
1008 # ping receives just the size of ICMP payload
1009 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
1010 ]
1011 cmd.append(ip_address)
Aaron Rosena7df13b2014-09-23 09:45:45 -07001012
1013 def ping():
1014 proc = subprocess.Popen(cmd,
1015 stdout=subprocess.PIPE,
1016 stderr=subprocess.PIPE)
1017 proc.communicate()
Shuquan Huang753629e2015-07-20 08:52:29 +00001018
Aaron Rosena7df13b2014-09-23 09:45:45 -07001019 return (proc.returncode == 0) == should_succeed
1020
Jordan Pittier9e227c52016-02-09 14:35:18 +01001021 caller = test_utils.find_test_caller()
Shuquan Huang753629e2015-07-20 08:52:29 +00001022 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
John L. Villalovosa898aec2017-01-13 14:46:46 -08001023 ' expected result is %(should_succeed)s', {
Shuquan Huang753629e2015-07-20 08:52:29 +00001024 'caller': caller, 'ip': ip_address, 'timeout': timeout,
1025 'should_succeed':
1026 'reachable' if should_succeed else 'unreachable'
1027 })
Jordan Pittier35a63752016-08-30 13:09:12 +02001028 result = test_utils.call_until_true(ping, timeout, 1)
Shuquan Huang753629e2015-07-20 08:52:29 +00001029 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
John L. Villalovosa898aec2017-01-13 14:46:46 -08001030 'ping result is %(result)s', {
Shuquan Huang753629e2015-07-20 08:52:29 +00001031 'caller': caller, 'ip': ip_address, 'timeout': timeout,
1032 'result': 'expected' if result else 'unexpected'
1033 })
zhufl0ec74c42017-11-15 14:02:28 +08001034 if server:
Soniya Vyas1b0cddc2021-01-29 17:28:19 +05301035 self.log_console_output([server])
Shuquan Huang753629e2015-07-20 08:52:29 +00001036 return result
Aaron Rosena7df13b2014-09-23 09:45:45 -07001037
Yair Friedae0e73d2014-11-24 11:56:26 +02001038 def check_vm_connectivity(self, ip_address,
1039 username=None,
1040 private_key=None,
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +00001041 should_connect=True,
zhufl0ec74c42017-11-15 14:02:28 +08001042 extra_msg="",
1043 server=None,
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +00001044 mtu=None):
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001045 """Check server connectivity
1046
Yair Friedae0e73d2014-11-24 11:56:26 +02001047 :param ip_address: server to test against
1048 :param username: server's ssh username
1049 :param private_key: server's ssh private key to be used
1050 :param should_connect: True/False indicates positive/negative test
1051 positive - attempt ping and ssh
1052 negative - attempt ping and fail if succeed
zhufl0ec74c42017-11-15 14:02:28 +08001053 :param extra_msg: Message to help with debugging if ``ping_ip_address``
1054 fails
1055 :param server: The server whose console to log for debugging
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +00001056 :param mtu: network MTU to use for connectivity validation
Yair Friedae0e73d2014-11-24 11:56:26 +02001057
1058 :raises: AssertError if the result of the connectivity check does
1059 not match the value of the should_connect param
1060 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301061
zhufl0ec74c42017-11-15 14:02:28 +08001062 LOG.debug('checking network connections to IP %s with user: %s',
1063 ip_address, username)
Yair Friedae0e73d2014-11-24 11:56:26 +02001064 if should_connect:
1065 msg = "Timed out waiting for %s to become reachable" % ip_address
1066 else:
1067 msg = "ip address %s is reachable" % ip_address
zhufl0ec74c42017-11-15 14:02:28 +08001068 if extra_msg:
1069 msg = "%s\n%s" % (extra_msg, msg)
Yair Friedae0e73d2014-11-24 11:56:26 +02001070 self.assertTrue(self.ping_ip_address(ip_address,
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +00001071 should_succeed=should_connect,
zhufl0ec74c42017-11-15 14:02:28 +08001072 mtu=mtu, server=server),
Yair Friedae0e73d2014-11-24 11:56:26 +02001073 msg=msg)
1074 if should_connect:
1075 # no need to check ssh for negative connectivity
zhufl0ec74c42017-11-15 14:02:28 +08001076 try:
1077 self.get_remote_client(ip_address, username, private_key,
1078 server=server)
1079 except Exception:
1080 if not extra_msg:
1081 extra_msg = 'Failed to ssh to %s' % ip_address
1082 LOG.exception(extra_msg)
1083 raise
Yair Friedae0e73d2014-11-24 11:56:26 +02001084
Ghanshyam Mann64281392021-03-24 18:48:38 -05001085 def get_server_port_id_and_ip4(self, server, ip_addr=None, **kwargs):
Yair Friedae0e73d2014-11-24 11:56:26 +02001086
Ghanshyam Mann64281392021-03-24 18:48:38 -05001087 if ip_addr and not kwargs.get('fixed_ips'):
1088 kwargs['fixed_ips'] = 'ip_address=%s' % ip_addr
1089 ports = self.os_admin.ports_client.list_ports(
1090 device_id=server['id'], **kwargs)['ports']
Lukas Piwowarskif759bc12020-11-05 10:51:29 +00001091
Ghanshyam Mann64281392021-03-24 18:48:38 -05001092 # A port can have more than one IP address in some cases.
1093 # If the network is dual-stack (IPv4 + IPv6), this port is associated
1094 # with 2 subnets
1095
1096 def _is_active(port):
1097 # NOTE(vsaienko) With Ironic, instances live on separate hardware
1098 # servers. Neutron does not bind ports for Ironic instances, as a
1099 # result the port remains in the DOWN state. This has been fixed
1100 # with the introduction of the networking-baremetal plugin but
1101 # it's not mandatory (and is not used on all stable branches).
1102 return (port['status'] == 'ACTIVE' or
1103 port.get('binding:vnic_type') == 'baremetal')
1104
1105 port_map = [(p["id"], fxip["ip_address"])
1106 for p in ports
1107 for fxip in p["fixed_ips"]
1108 if (netutils.is_valid_ipv4(fxip["ip_address"]) and
1109 _is_active(p))]
1110 inactive = [p for p in ports if p['status'] != 'ACTIVE']
1111 if inactive:
1112 LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
1113
1114 self.assertNotEmpty(port_map,
1115 "No IPv4 addresses found in: %s" % ports)
1116 self.assertEqual(len(port_map), 1,
1117 "Found multiple IPv4 addresses: %s. "
1118 "Unable to determine which port to target."
1119 % port_map)
1120 return port_map[0]
1121
1122 def create_floating_ip(self, server, external_network_id=None,
1123 port_id=None, client=None, **kwargs):
1124 """Create a floating IP and associates to a resource/port on Neutron"""
1125
1126 if not external_network_id:
1127 external_network_id = CONF.network.public_network_id
1128 if not client:
1129 client = self.floating_ips_client
1130 if not port_id:
1131 port_id, ip4 = self.get_server_port_id_and_ip4(server)
1132 else:
1133 ip4 = None
1134
1135 floatingip_kwargs = {
1136 'floating_network_id': external_network_id,
1137 'port_id': port_id,
1138 'tenant_id': server.get('project_id') or server['tenant_id'],
1139 'fixed_ip_address': ip4,
1140 }
1141 if CONF.network.subnet_id:
1142 floatingip_kwargs['subnet_id'] = CONF.network.subnet_id
1143
1144 floatingip_kwargs.update(kwargs)
1145 result = client.create_floatingip(**floatingip_kwargs)
1146 floating_ip = result['floatingip']
1147
Jordan Pittier9e227c52016-02-09 14:35:18 +01001148 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Ghanshyam Mann64281392021-03-24 18:48:38 -05001149 client.delete_floatingip,
Yair Friedae0e73d2014-11-24 11:56:26 +02001150 floating_ip['id'])
Ghanshyam Mann64281392021-03-24 18:48:38 -05001151 return floating_ip
1152
Dan Smith49c2b3b2023-04-26 15:52:22 -07001153 def get_floating_ip(self, server):
1154 """Attempt to get an existing floating ip or a server
1155
1156 If one exists, return it, else return None
1157 """
1158 port_id, ip4 = self.get_server_port_id_and_ip4(server)
1159 ips = self.floating_ips_client.list_floatingips(
1160 floating_network_id=CONF.network.public_network_id,
1161 port_id=port_id)
1162 try:
1163 return ips['floatingips'][0]['floating_ip_address']
1164 except (KeyError, IndexError):
1165 return None
1166
Ghanshyam Mann64281392021-03-24 18:48:38 -05001167 def associate_floating_ip(self, floating_ip, server):
1168 """Associate floating ip to server
1169
1170 This wrapper utility attaches the floating_ip for
1171 the respective port_id of server
1172 """
1173 port_id, _ = self.get_server_port_id_and_ip4(server)
1174 kwargs = dict(port_id=port_id)
1175 floating_ip = self.floating_ips_client.update_floatingip(
1176 floating_ip['id'], **kwargs)['floatingip']
1177 self.assertEqual(port_id, floating_ip['port_id'])
1178 return floating_ip
1179
1180 def disassociate_floating_ip(self, floating_ip):
1181 """Disassociates floating ip
1182
1183 This wrapper utility disassociates given floating ip.
1184 :param floating_ip: a dict which is a return value of
1185 floating_ips_client.create_floatingip method
1186 """
1187 kwargs = dict(port_id=None)
1188 floating_ip = self.floating_ips_client.update_floatingip(
1189 floating_ip['id'], **kwargs)['floatingip']
1190 self.assertIsNone(floating_ip['port_id'])
Yair Friedae0e73d2014-11-24 11:56:26 +02001191 return floating_ip
1192
Sean Dague20e98612016-01-06 14:33:28 -05001193 def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
Lukas Piwowarski25f7ba22020-10-29 14:01:34 +00001194 private_key=None, server=None, username=None,
Dan Smith30eb8782023-08-02 09:35:01 -07001195 fs='vfat'):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301196 """Creates timestamp
1197
1198 This wrapper utility does ssh, creates timestamp and returns the
1199 created timestamp.
1200 """
Sean Dague20e98612016-01-06 14:33:28 -05001201 ssh_client = self.get_remote_client(ip_address,
Slawek Kaplonski79d8b0f2018-07-30 22:43:41 +02001202 private_key=private_key,
Lukas Piwowarski25f7ba22020-10-29 14:01:34 +00001203 server=server,
1204 username=username)
1205
melanie witt2da632a2023-05-31 03:25:06 +00001206 # Default the directory in which to write the timestamp file to /tmp
1207 # and only use the mount_path as the target directory if we mounted
1208 # dev_name to mount_path.
1209 target_dir = '/tmp'
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001210 if dev_name is not None:
Lukas Piwowarski25f7ba22020-10-29 14:01:34 +00001211 ssh_client.make_fs(dev_name, fs=fs)
Ken'ichi Ohmichi4e5a69e2017-03-01 18:15:29 -08001212 ssh_client.exec_command('sudo mount /dev/%s %s' % (dev_name,
1213 mount_path))
melanie witt2da632a2023-05-31 03:25:06 +00001214 target_dir = mount_path
1215 cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % target_dir
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001216 ssh_client.exec_command(cmd_timestamp)
1217 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
melanie witt2da632a2023-05-31 03:25:06 +00001218 % target_dir)
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001219 if dev_name is not None:
Ken'ichi Ohmichi4e5a69e2017-03-01 18:15:29 -08001220 ssh_client.exec_command('sudo umount %s' % mount_path)
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001221 return timestamp
1222
Sean Dague20e98612016-01-06 14:33:28 -05001223 def get_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
Lukas Piwowarski2c230eb2020-10-30 10:09:18 +00001224 private_key=None, server=None, username=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301225 """Returns timestamp
1226
1227 This wrapper utility does ssh and returns the timestamp.
Lukas Piwowarski2c230eb2020-10-30 10:09:18 +00001228
1229 :param ip_address: The floating IP or fixed IP of the remote server
1230 :param dev_name: Name of the device that stores the timestamp
1231 :param mount_path: Path which should be used as mount point for
1232 dev_name
1233 :param private_key: The SSH private key to use for authentication
1234 :param server: Server dict, used for debugging purposes
1235 :param username: Name of the Linux account on the remote server
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301236 """
1237
Sean Dague20e98612016-01-06 14:33:28 -05001238 ssh_client = self.get_remote_client(ip_address,
Slawek Kaplonski79d8b0f2018-07-30 22:43:41 +02001239 private_key=private_key,
Lukas Piwowarski2c230eb2020-10-30 10:09:18 +00001240 server=server,
1241 username=username)
1242
melanie witt2da632a2023-05-31 03:25:06 +00001243 # Default the directory from which to read the timestamp file to /tmp
1244 # and only use the mount_path as the target directory if we mounted
1245 # dev_name to mount_path.
1246 target_dir = '/tmp'
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001247 if dev_name is not None:
Matt Riedemann076685a2015-09-30 14:38:16 -07001248 ssh_client.mount(dev_name, mount_path)
melanie witt2da632a2023-05-31 03:25:06 +00001249 target_dir = mount_path
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001250 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
melanie witt2da632a2023-05-31 03:25:06 +00001251 % target_dir)
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001252 if dev_name is not None:
Ken'ichi Ohmichi4e5a69e2017-03-01 18:15:29 -08001253 ssh_client.exec_command('sudo umount %s' % mount_path)
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001254 return timestamp
1255
Lukas Piwowarskib0642f92020-10-29 14:51:30 +00001256 def get_server_ip(self, server, **kwargs):
Sean Dague20e98612016-01-06 14:33:28 -05001257 """Get the server fixed or floating IP.
1258
1259 Based on the configuration we're in, return a correct ip
1260 address for validating that a guest is up.
Lukas Piwowarskib0642f92020-10-29 14:51:30 +00001261
1262 If CONF.validation.connect_method is floating, then
1263 a floating ip will be created passing kwargs as additional
1264 argument.
Sean Dague20e98612016-01-06 14:33:28 -05001265 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301266
Alexander Gubanovc8829f82015-11-12 10:35:13 +02001267 if CONF.validation.connect_method == 'floating':
Sean Dague20e98612016-01-06 14:33:28 -05001268 # The tests calling this method don't have a floating IP
zhufl0892cb22016-05-06 14:46:00 +08001269 # and can't make use of the validation resources. So the
Sean Dague20e98612016-01-06 14:33:28 -05001270 # method is creating the floating IP there.
Dan Smith49c2b3b2023-04-26 15:52:22 -07001271 fip = self.get_floating_ip(server)
1272 if fip:
1273 # Already have a floating ip, so use it instead of creating
1274 # another
1275 return fip
1276 else:
1277 return self.create_floating_ip(
1278 server, **kwargs)['floating_ip_address']
Sean Dague20e98612016-01-06 14:33:28 -05001279 elif CONF.validation.connect_method == 'fixed':
Matt Riedemanna7782552016-08-08 16:26:01 -04001280 # Determine the network name to look for based on config or creds
1281 # provider network resources.
1282 if CONF.validation.network_for_ssh:
1283 addresses = server['addresses'][
1284 CONF.validation.network_for_ssh]
1285 else:
zhufl7b4a7202017-09-28 10:29:27 +08001286 network = self.get_tenant_network()
Matt Riedemanna7782552016-08-08 16:26:01 -04001287 addresses = (server['addresses'][network['name']]
1288 if network else [])
Sean Dague20e98612016-01-06 14:33:28 -05001289 for address in addresses:
Federico Ressi2d6bcaa2018-04-11 12:37:36 +02001290 if (address['version'] == CONF.validation.ip_version_for_ssh and # noqa
1291 address['OS-EXT-IPS:type'] == 'fixed'):
Sean Dague20e98612016-01-06 14:33:28 -05001292 return address['addr']
zhufl955f82b2016-07-22 11:14:34 +08001293 raise exceptions.ServerUnreachable(server_id=server['id'])
Alexander Gubanovc8829f82015-11-12 10:35:13 +02001294 else:
Matthew Treinish4217a702016-10-07 17:27:11 -04001295 raise lib_exc.InvalidConfiguration()
Alexander Gubanovc8829f82015-11-12 10:35:13 +02001296
zhufl7bc916d2018-08-22 14:47:39 +08001297 @classmethod
1298 def get_host_for_server(cls, server_id):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301299 """Gets host of server"""
1300
zhufl7bc916d2018-08-22 14:47:39 +08001301 server_details = cls.os_admin.servers_client.show_server(server_id)
1302 return server_details['server']['OS-EXT-SRV-ATTR:host']
1303
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001304 def _get_bdm(self, source_id, source_type, delete_on_termination=False):
1305 bd_map_v2 = [{
1306 'uuid': source_id,
1307 'source_type': source_type,
1308 'destination_type': 'volume',
1309 'boot_index': 0,
1310 'delete_on_termination': delete_on_termination}]
1311 return {'block_device_mapping_v2': bd_map_v2}
1312
1313 def boot_instance_from_resource(self, source_id,
1314 source_type,
1315 keypair=None,
1316 security_group=None,
1317 delete_on_termination=False,
Martin Kopecbee673e2020-11-04 09:40:52 +00001318 name=None, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301319 """Boot instance from resource
1320
1321 This wrapper utility boots instance from resource with block device
1322 mapping with source info passed in arguments
1323 """
1324
Martin Kopecbee673e2020-11-04 09:40:52 +00001325 create_kwargs = dict({'image_id': ''})
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001326 if keypair:
Dan Smith49c2b3b2023-04-26 15:52:22 -07001327 create_kwargs['keypair'] = keypair
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001328 if security_group:
1329 create_kwargs['security_groups'] = [
1330 {'name': security_group['name']}]
1331 create_kwargs.update(self._get_bdm(
1332 source_id,
1333 source_type,
1334 delete_on_termination=delete_on_termination))
1335 if name:
1336 create_kwargs['name'] = name
Martin Kopecbee673e2020-11-04 09:40:52 +00001337 create_kwargs.update(kwargs)
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001338
Martin Kopecbee673e2020-11-04 09:40:52 +00001339 return self.create_server(**create_kwargs)
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001340
Martin Kopec0216b372020-11-04 09:32:05 +00001341 def create_volume_from_image(self, **kwargs):
1342 """Create volume from image.
1343
1344 :param image_id: ID of the image to create volume from,
1345 CONF.compute.image_ref by default
1346 :param name: name of the volume,
1347 '$classname-volume-origin' by default
1348 :param **kwargs: additional parameters
1349 """
1350 image_id = kwargs.pop('image_id', CONF.compute.image_ref)
1351 name = kwargs.pop('name', None)
1352 if not name:
1353 namestart = self.__class__.__name__ + '-volume-origin'
Martin Kopec213d0a42023-11-30 10:28:14 +01001354 name = data_utils.rand_name(
1355 prefix=CONF.resource_name_prefix, name=namestart)
Martin Kopec0216b372020-11-04 09:32:05 +00001356 return self.create_volume(name=name, imageRef=image_id, **kwargs)
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001357
Andrea Frittoli2e733b52014-07-16 14:12:11 +01001358
Arefiev Anton30b04002022-07-27 15:11:55 +03001359class ScenarioTestWithNetwork(ScenarioTest):
1360 """Base class for tests with default network"""
1361
1362 @classmethod
1363 def setup_credentials(cls):
1364 cls.set_network_resources(network=True, subnet=True,
1365 dhcp=True, router=True)
1366 super(ScenarioTestWithNetwork, cls).setup_credentials()
1367
1368
Andrea Frittoli4971fc82014-09-25 10:22:20 +01001369class NetworkScenarioTest(ScenarioTest):
Yair Fried1fc32a12014-08-04 09:11:30 +03001370 """Base class for network scenario tests.
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001371
Yair Fried1fc32a12014-08-04 09:11:30 +03001372 This class provide helpers for network scenario tests, using the neutron
1373 API. Helpers from ancestor which use the nova network API are overridden
1374 with the neutron API.
1375
1376 This Class also enforces using Neutron instead of novanetwork.
1377 Subclassed tests will be skipped if Neutron is not enabled
1378
1379 """
1380
1381 @classmethod
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001382 def skip_checks(cls):
1383 super(NetworkScenarioTest, cls).skip_checks()
Andrea Frittoli2ddc2632014-09-25 11:03:00 +01001384 if not CONF.service_available.neutron:
1385 raise cls.skipException('Neutron not available')
Yair Fried1fc32a12014-08-04 09:11:30 +03001386
Soniya Vyas3bdafd82021-02-22 18:59:27 +05301387 def create_network(self, networks_client=None,
1388 project_id=None,
1389 namestart='network-smoke-',
1390 port_security_enabled=True, **net_dict):
John Warren94d8faf2015-09-15 12:22:24 -04001391 if not networks_client:
1392 networks_client = self.networks_client
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001393 if not project_id:
1394 project_id = networks_client.project_id
Martin Kopec213d0a42023-11-30 10:28:14 +01001395 name = data_utils.rand_name(
1396 prefix=CONF.resource_name_prefix, name=namestart)
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001397 network_kwargs = dict(name=name, project_id=project_id)
Lajos Katonac87a06b2019-01-04 13:21:48 +01001398 if net_dict:
1399 network_kwargs.update(net_dict)
Matt Riedemann039b2fe2016-09-15 16:12:24 -04001400 # Neutron disables port security by default so we have to check the
1401 # config before trying to create the network with port_security_enabled
1402 if CONF.network_feature_enabled.port_security:
1403 network_kwargs['port_security_enabled'] = port_security_enabled
Markus Zoeller156b5da2016-07-11 18:10:31 +02001404 result = networks_client.create_network(**network_kwargs)
Steve Heyman33735f22016-05-24 09:28:08 -05001405 network = result['network']
1406
1407 self.assertEqual(network['name'], name)
Jordan Pittier9e227c52016-02-09 14:35:18 +01001408 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
zhoubin508bf20b32017-02-03 09:39:14 +08001409 networks_client.delete_network,
Steve Heyman33735f22016-05-24 09:28:08 -05001410 network['id'])
Yair Fried1fc32a12014-08-04 09:11:30 +03001411 return network
1412
zhufl5b0a52f2017-10-24 15:48:20 +08001413 def create_subnet(self, network, subnets_client=None,
1414 namestart='subnet-smoke', **kwargs):
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001415 """Create a subnet for the given network
1416
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301417 This utility creates subnet for the given network
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001418 within the cidr block configured for tenant networks.
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301419
1420 :param **kwargs:
1421 See extra parameters below
1422
1423 :Keyword Arguments:
1424
1425 * *ip_version = ip version of the given network,
Soniya Vyas795ef252020-12-10 19:07:23 +05301426 use_default_subnetpool = default subnetpool to
1427 manage IPv6 addresses range.
Yair Fried1fc32a12014-08-04 09:11:30 +03001428 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301429
John Warren3961acd2015-10-02 14:38:53 -04001430 if not subnets_client:
1431 subnets_client = self.subnets_client
Yair Fried1fc32a12014-08-04 09:11:30 +03001432
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001433 def cidr_in_use(cidr, project_id):
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001434 """Check cidr existence
1435
yangjianfeng4ad346e2020-11-22 06:49:19 +00001436 :returns: True if subnet with cidr already exist in tenant or
1437 external False else
Yair Fried1fc32a12014-08-04 09:11:30 +03001438 """
yangjianfeng4ad346e2020-11-22 06:49:19 +00001439 tenant_subnets = self.os_admin.subnets_client.list_subnets(
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001440 project_id=project_id, cidr=cidr)['subnets']
yangjianfeng4ad346e2020-11-22 06:49:19 +00001441 external_nets = self.os_admin.networks_client.list_networks(
1442 **{"router:external": True})['networks']
1443 external_subnets = []
1444 for ext_net in external_nets:
1445 external_subnets.extend(
1446 self.os_admin.subnets_client.list_subnets(
1447 network_id=ext_net['id'], cidr=cidr)['subnets'])
1448 return len(tenant_subnets + external_subnets) != 0
Yair Fried1fc32a12014-08-04 09:11:30 +03001449
Soniya Vyas795ef252020-12-10 19:07:23 +05301450 def _make_create_subnet_request(namestart, network,
1451 ip_version, subnets_client, **kwargs):
Yair Fried1fc32a12014-08-04 09:11:30 +03001452
1453 subnet = dict(
Martin Kopec213d0a42023-11-30 10:28:14 +01001454 name=data_utils.rand_name(
1455 prefix=CONF.resource_name_prefix, name=namestart),
Steve Heyman33735f22016-05-24 09:28:08 -05001456 network_id=network['id'],
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001457 project_id=network['project_id'],
Kirill Shileev14113572014-11-21 16:58:02 +03001458 ip_version=ip_version,
Yair Fried1fc32a12014-08-04 09:11:30 +03001459 **kwargs
1460 )
Soniya Vyas795ef252020-12-10 19:07:23 +05301461
1462 if ip_version == 6:
1463 subnet['ipv6_address_mode'] = 'slaac'
1464 subnet['ipv6_ra_mode'] = 'slaac'
1465
Yair Fried1fc32a12014-08-04 09:11:30 +03001466 try:
Soniya Vyas795ef252020-12-10 19:07:23 +05301467 return subnets_client.create_subnet(**subnet)
Masayuki Igawad9388762015-01-20 14:56:42 +09001468 except lib_exc.Conflict as e:
Soniya Vyas795ef252020-12-10 19:07:23 +05301469 if 'overlaps with another subnet' not in str(e):
Yair Fried1fc32a12014-08-04 09:11:30 +03001470 raise
Soniya Vyas795ef252020-12-10 19:07:23 +05301471
1472 result = None
1473 str_cidr = None
1474
1475 use_default_subnetpool = kwargs.get('use_default_subnetpool', False)
1476 ip_version = kwargs.pop('ip_version', 4)
1477
1478 if not use_default_subnetpool:
1479
1480 if ip_version == 6:
1481 tenant_cidr = netaddr.IPNetwork(
1482 CONF.network.project_network_v6_cidr)
1483 num_bits = CONF.network.project_network_v6_mask_bits
1484 else:
1485 tenant_cidr = netaddr.IPNetwork(
1486 CONF.network.project_network_cidr)
1487 num_bits = CONF.network.project_network_mask_bits
1488
1489 # Repeatedly attempt subnet creation with sequential cidr
1490 # blocks until an unallocated block is found.
1491 for subnet_cidr in tenant_cidr.subnet(num_bits):
1492 str_cidr = str(subnet_cidr)
1493 if cidr_in_use(str_cidr, project_id=network['project_id']):
1494 continue
1495 result = _make_create_subnet_request(
1496 namestart, network, ip_version, subnets_client,
1497 cidr=str_cidr, **kwargs)
1498
1499 if result is not None:
1500 break
1501
1502 else:
1503 result = _make_create_subnet_request(
1504 namestart, network, ip_version, subnets_client,
1505 **kwargs)
Yair Fried1fc32a12014-08-04 09:11:30 +03001506 self.assertIsNotNone(result, 'Unable to allocate tenant network')
Steve Heyman33735f22016-05-24 09:28:08 -05001507
1508 subnet = result['subnet']
Soniya Vyas795ef252020-12-10 19:07:23 +05301509 if str_cidr is not None:
1510 self.assertEqual(subnet['cidr'], str_cidr)
Steve Heyman33735f22016-05-24 09:28:08 -05001511
1512 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1513 subnets_client.delete_subnet, subnet['id'])
1514
Yair Fried1fc32a12014-08-04 09:11:30 +03001515 return subnet
1516
Soniya Vyasc37410f2021-02-24 15:26:27 +05301517 def get_network_by_name(self, network_name):
jeremy.zhang5870ff12017-05-25 11:24:23 +08001518 net = self.os_admin.networks_client.list_networks(
Jordan Pittier64e6b442017-02-20 19:29:02 +01001519 name=network_name)['networks']
Ferenc Horváth268ccce2017-06-08 12:39:02 +02001520 self.assertNotEmpty(net,
Adam Gandelman878a5fd2015-03-30 14:33:36 -07001521 "Unable to get network by name: %s" % network_name)
Steve Heyman33735f22016-05-24 09:28:08 -05001522 return net[0]
David Shrewsbury9bac3662014-08-07 15:07:01 -04001523
Yair Fried45f92952014-06-26 05:19:19 +03001524 def check_floating_ip_status(self, floating_ip, status):
Carl Baldwina754e2d2014-10-23 22:47:41 +00001525 """Verifies floatingip reaches the given status
Yair Fried45f92952014-06-26 05:19:19 +03001526
Steve Heyman33735f22016-05-24 09:28:08 -05001527 :param dict floating_ip: floating IP dict to check status
Yair Fried45f92952014-06-26 05:19:19 +03001528 :param status: target status
1529 :raises: AssertionError if status doesn't match
1530 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301531
Steve Heyman33735f22016-05-24 09:28:08 -05001532 floatingip_id = floating_ip['id']
1533
Carl Baldwina754e2d2014-10-23 22:47:41 +00001534 def refresh():
Martin Kopecf4b5df62020-01-27 09:44:29 +00001535 floating_ip = (self.floating_ips_client.
1536 show_floatingip(floatingip_id)['floatingip'])
1537 if status == floating_ip['status']:
1538 LOG.info("FloatingIP: {fp} is at status: {st}"
1539 .format(fp=floating_ip, st=status))
1540 return status == floating_ip['status']
Carl Baldwina754e2d2014-10-23 22:47:41 +00001541
zhufl4dda94e2017-03-14 16:14:46 +08001542 if not test_utils.call_until_true(refresh,
1543 CONF.network.build_timeout,
1544 CONF.network.build_interval):
1545 floating_ip = self.floating_ips_client.show_floatingip(
1546 floatingip_id)['floatingip']
1547 self.assertEqual(status, floating_ip['status'],
1548 message="FloatingIP: {fp} is at status: {cst}. "
1549 "failed to reach status: {st}"
1550 .format(fp=floating_ip, cst=floating_ip['status'],
1551 st=status))
Yair Fried45f92952014-06-26 05:19:19 +03001552
zhufl420a0192017-09-28 11:04:50 +08001553 def check_tenant_network_connectivity(self, server,
1554 username,
1555 private_key,
1556 should_connect=True,
1557 servers_for_debug=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301558 """Checks tenant network connectivity"""
Sean Dagueed6e5862016-04-04 10:49:13 -04001559 if not CONF.network.project_networks_reachable:
Yair Fried1fc32a12014-08-04 09:11:30 +03001560 msg = 'Tenant networks not configured to be reachable.'
1561 LOG.info(msg)
1562 return
1563 # The target login is assumed to have been configured for
1564 # key-based authentication by cloud-init.
1565 try:
Béla Vancsicsb6dfa082017-03-01 10:44:58 +01001566 for ip_addresses in server['addresses'].values():
Yair Fried1fc32a12014-08-04 09:11:30 +03001567 for ip_address in ip_addresses:
ghanshyam807211c2014-12-18 13:21:22 +09001568 self.check_vm_connectivity(ip_address['addr'],
Yair Friedae0e73d2014-11-24 11:56:26 +02001569 username,
1570 private_key,
1571 should_connect=should_connect)
Yair Fried1fc32a12014-08-04 09:11:30 +03001572 except Exception as e:
1573 LOG.exception('Tenant network connectivity check failed')
Soniya Vyas1b0cddc2021-01-29 17:28:19 +05301574 self.log_console_output(servers_for_debug)
Ken'ichi Ohmichi6e201f52014-10-01 04:21:39 +00001575 self._log_net_info(e)
Yair Fried1fc32a12014-08-04 09:11:30 +03001576 raise
1577
zhufle9877c62017-10-13 09:38:19 +08001578 def check_remote_connectivity(self, source, dest, should_succeed=True,
Claudiu Belu33c3e602014-08-28 16:38:01 +03001579 nic=None, protocol='icmp'):
1580 """check server connectivity via source ssh connection
YAMAMOTO Takashi4c3ebb02017-01-25 16:04:30 +09001581
Claudiu Belu33c3e602014-08-28 16:38:01 +03001582 :param source: RemoteClient: an ssh connection from which to execute
1583 the check
1584 :param dest: an IP to check connectivity against
1585 :param should_succeed: boolean should connection succeed or not
1586 :param nic: specific network interface to test connectivity from
1587 :param protocol: the protocol used to test connectivity with.
1588 :returns: True, if the connection succeeded and it was expected to
1589 succeed. False otherwise.
Yair Fried1fc32a12014-08-04 09:11:30 +03001590 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301591
Claudiu Belu33c3e602014-08-28 16:38:01 +03001592 method_name = '%s_check' % protocol
1593 connectivity_checker = getattr(source, method_name)
1594
1595 def connect_remote():
Yair Fried1fc32a12014-08-04 09:11:30 +03001596 try:
Claudiu Belu33c3e602014-08-28 16:38:01 +03001597 connectivity_checker(dest, nic=nic)
Andrey Pavlov64723762015-04-29 06:24:58 +03001598 except lib_exc.SSHExecCommandFailed:
Claudiu Belu33c3e602014-08-28 16:38:01 +03001599 LOG.warning('Failed to check %(protocol)s connectivity for '
1600 'IP %(dest)s via a ssh connection from: %(src)s.',
1601 dict(protocol=protocol, dest=dest,
1602 src=source.ssh_client.host))
Yair Fried1fc32a12014-08-04 09:11:30 +03001603 return not should_succeed
1604 return should_succeed
1605
Claudiu Belu33c3e602014-08-28 16:38:01 +03001606 result = test_utils.call_until_true(connect_remote,
zhufle9877c62017-10-13 09:38:19 +08001607 CONF.validation.ping_timeout, 1)
Ihar Hrachyshkaf9fda2d2017-11-06 13:16:09 -08001608 if result:
1609 return
1610
YAMAMOTO Takashi4c3ebb02017-01-25 16:04:30 +09001611 source_host = source.ssh_client.host
1612 if should_succeed:
1613 msg = "Timed out waiting for %s to become reachable from %s" \
1614 % (dest, source_host)
1615 else:
1616 msg = "%s is reachable from %s" % (dest, source_host)
Soniya Vyas1b0cddc2021-01-29 17:28:19 +05301617 self.log_console_output()
Ihar Hrachyshkaf9fda2d2017-11-06 13:16:09 -08001618 self.fail(msg)
YAMAMOTO Takashi4c3ebb02017-01-25 16:04:30 +09001619
Soniya Vyas73555df2021-03-04 19:05:42 +05301620 def get_router(self, client=None, project_id=None, **kwargs):
Yair Fried1fc32a12014-08-04 09:11:30 +03001621 """Retrieve a router for the given tenant id.
1622
1623 If a public router has been configured, it will be returned.
1624
1625 If a public router has not been configured, but a public
1626 network has, a tenant router will be created and returned that
1627 routes traffic to the public network.
1628 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301629
Yair Frieddb6c9e92014-08-06 08:53:13 +03001630 if not client:
Ken'ichi Ohmichie35f4722015-12-22 04:57:11 +00001631 client = self.routers_client
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001632 if not project_id:
1633 project_id = client.project_id
Yair Fried1fc32a12014-08-04 09:11:30 +03001634 router_id = CONF.network.public_router_id
1635 network_id = CONF.network.public_network_id
1636 if router_id:
David Kranzca4c7e72015-05-27 11:39:19 -04001637 body = client.show_router(router_id)
Steve Heyman33735f22016-05-24 09:28:08 -05001638 return body['router']
Yair Fried1fc32a12014-08-04 09:11:30 +03001639 elif network_id:
Martin Kopec0090a102020-11-03 13:50:19 +00001640 name = kwargs.pop('name', None)
1641 if not name:
1642 namestart = self.__class__.__name__ + '-router'
Martin Kopec213d0a42023-11-30 10:28:14 +01001643 name = data_utils.rand_name(
1644 prefix=CONF.resource_name_prefix, name=namestart)
Martin Kopec0090a102020-11-03 13:50:19 +00001645
1646 ext_gw_info = kwargs.pop('external_gateway_info', None)
1647 if not ext_gw_info:
1648 ext_gw_info = dict(network_id=network_id)
zhufl3484f992017-10-10 16:18:29 +08001649 router = client.create_router(
Martin Kopec0090a102020-11-03 13:50:19 +00001650 name=name,
1651 admin_state_up=kwargs.get('admin_state_up', True),
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001652 project_id=project_id,
Martin Kopec0090a102020-11-03 13:50:19 +00001653 external_gateway_info=ext_gw_info,
1654 **kwargs)['router']
zhufl3484f992017-10-10 16:18:29 +08001655 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1656 client.delete_router, router['id'])
Yair Fried1fc32a12014-08-04 09:11:30 +03001657 return router
1658 else:
1659 raise Exception("Neither of 'public_router_id' or "
1660 "'public_network_id' has been defined.")
1661
Ghanshyam Mann071d1542021-03-24 19:10:47 -05001662 def setup_network_subnet_with_router(
1663 self, networks_client=None,
1664 routers_client=None, subnets_client=None,
1665 project_id=None, dns_nameservers=None,
1666 port_security_enabled=True, **net_dict):
Yair Fried1fc32a12014-08-04 09:11:30 +03001667 """Create a network with a subnet connected to a router.
1668
David Shrewsbury9bac3662014-08-07 15:07:01 -04001669 The baremetal driver is a special case since all nodes are
1670 on the same shared network.
1671
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001672 :param project_id: id of project to create resources in.
Yair Fried413bf2d2014-11-19 17:07:11 +02001673 :param dns_nameservers: list of dns servers to send to subnet.
Lajos Katonac87a06b2019-01-04 13:21:48 +01001674 :param port_security_enabled: whether or not port_security is enabled
elajkate453fc22019-06-13 15:03:43 +02001675 :param net_dict: a dict containing experimental network information in
Lajos Katonac87a06b2019-01-04 13:21:48 +01001676 a form like this: {'provider:network_type': 'vlan',
1677 'provider:physical_network': 'foo',
1678 'provider:segmentation_id': '42'}
Yair Fried1fc32a12014-08-04 09:11:30 +03001679 :returns: network, subnet, router
1680 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301681
Thiago Paiva66cded22016-08-15 14:55:58 -03001682 if CONF.network.shared_physical_network:
David Shrewsbury9bac3662014-08-07 15:07:01 -04001683 # NOTE(Shrews): This exception is for environments where tenant
1684 # credential isolation is available, but network separation is
1685 # not (the current baremetal case). Likely can be removed when
1686 # test account mgmt is reworked:
1687 # https://blueprints.launchpad.net/tempest/+spec/test-accounts
Adam Gandelman878a5fd2015-03-30 14:33:36 -07001688 if not CONF.compute.fixed_network_name:
1689 m = 'fixed_network_name must be specified in config'
Matthew Treinish4217a702016-10-07 17:27:11 -04001690 raise lib_exc.InvalidConfiguration(m)
Soniya Vyasc37410f2021-02-24 15:26:27 +05301691 network = self.get_network_by_name(
David Shrewsbury9bac3662014-08-07 15:07:01 -04001692 CONF.compute.fixed_network_name)
1693 router = None
1694 subnet = None
1695 else:
Soniya Vyas3bdafd82021-02-22 18:59:27 +05301696 network = self.create_network(
Ken'ichi Ohmichi43e7fcf2016-04-04 11:59:13 -07001697 networks_client=networks_client,
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001698 project_id=project_id,
Lajos Katonac87a06b2019-01-04 13:21:48 +01001699 port_security_enabled=port_security_enabled,
1700 **net_dict)
Soniya Vyas73555df2021-03-04 19:05:42 +05301701 router = self.get_router(client=routers_client,
1702 project_id=project_id)
Ken'ichi Ohmichi43e7fcf2016-04-04 11:59:13 -07001703 subnet_kwargs = dict(network=network,
zhufl5b0a52f2017-10-24 15:48:20 +08001704 subnets_client=subnets_client)
Yair Fried413bf2d2014-11-19 17:07:11 +02001705 # use explicit check because empty list is a valid option
1706 if dns_nameservers is not None:
1707 subnet_kwargs['dns_nameservers'] = dns_nameservers
zhufl5b0a52f2017-10-24 15:48:20 +08001708 subnet = self.create_subnet(**subnet_kwargs)
Steve Heyman33735f22016-05-24 09:28:08 -05001709 if not routers_client:
1710 routers_client = self.routers_client
1711 router_id = router['id']
1712 routers_client.add_router_interface(router_id,
1713 subnet_id=subnet['id'])
1714
1715 # save a cleanup job to remove this association between
1716 # router and subnet
1717 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1718 routers_client.remove_router_interface, router_id,
1719 subnet_id=subnet['id'])
Yair Fried1fc32a12014-08-04 09:11:30 +03001720 return network, subnet, router
1721
1722
Arefiev Anton30b04002022-07-27 15:11:55 +03001723class EncryptionScenarioTest(ScenarioTestWithNetwork):
1724
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001725 """Base class for encryption scenario tests"""
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001726
David Kranz4cc852b2015-03-09 14:57:11 -04001727 @classmethod
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001728 def setup_clients(cls):
1729 super(EncryptionScenarioTest, cls).setup_clients()
ghanshyam6c682ff2018-08-06 09:54:45 +00001730 cls.admin_volume_types_client = cls.os_admin.volume_types_client_latest
ghanshyam3bd0d2b2017-03-23 01:57:28 +00001731 cls.admin_encryption_types_client =\
ghanshyam6c682ff2018-08-06 09:54:45 +00001732 cls.os_admin.encryption_types_client_latest
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001733
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001734 def create_encryption_type(self, client=None, type_id=None, provider=None,
1735 key_size=None, cipher=None,
1736 control_location=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301737 """Creates an encryption type for volume"""
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001738 if not client:
Ken'ichi Ohmichia6ebf622016-08-25 11:52:27 -07001739 client = self.admin_encryption_types_client
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001740 if not type_id:
1741 volume_type = self.create_volume_type()
Masayuki Igawa1f0ad632014-08-05 13:36:56 +09001742 type_id = volume_type['id']
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001743 LOG.debug("Creating an encryption type for volume type: %s", type_id)
Masayuki Igawa1f0ad632014-08-05 13:36:56 +09001744 client.create_encryption_type(
1745 type_id, provider=provider, key_size=key_size, cipher=cipher,
jeremy.zhangb6f67f62018-02-11 09:28:52 +08001746 control_location=control_location)
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001747
lkuchlan3023e752017-06-08 12:53:13 +03001748 def create_encrypted_volume(self, encryption_provider, volume_type,
1749 key_size=256, cipher='aes-xts-plain64',
Ghanshyam Mann51c0f9a2023-07-21 14:09:40 -05001750 control_location='front-end',
1751 wait_until='available'):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301752 """Creates an encrypted volume"""
lkuchlan3023e752017-06-08 12:53:13 +03001753 volume_type = self.create_volume_type(name=volume_type)
1754 self.create_encryption_type(type_id=volume_type['id'],
1755 provider=encryption_provider,
1756 key_size=key_size,
1757 cipher=cipher,
1758 control_location=control_location)
Ghanshyam Mann51c0f9a2023-07-21 14:09:40 -05001759 return self.create_volume(volume_type=volume_type['name'],
1760 wait_until=wait_until)
lkuchlan3023e752017-06-08 12:53:13 +03001761
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001762
Masayuki Igawa0870db52015-09-18 21:08:36 +09001763class ObjectStorageScenarioTest(ScenarioTest):
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001764 """Provide harness to do Object Storage scenario tests.
Chris Dent0d494112014-08-26 13:48:30 +01001765
1766 Subclasses implement the tests that use the methods provided by this
1767 class.
1768 """
1769
Ghanshyam Mann64281392021-03-24 18:48:38 -05001770 credentials = ['primary']
1771
Chris Dent0d494112014-08-26 13:48:30 +01001772 @classmethod
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001773 def skip_checks(cls):
Masayuki Igawa0870db52015-09-18 21:08:36 +09001774 super(ObjectStorageScenarioTest, cls).skip_checks()
Chris Dent0d494112014-08-26 13:48:30 +01001775 if not CONF.service_available.swift:
1776 skip_msg = ("%s skipped as swift is not available" %
1777 cls.__name__)
1778 raise cls.skipException(skip_msg)
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001779
1780 @classmethod
1781 def setup_credentials(cls):
Masayuki Igawa60ea6c52014-10-15 17:32:14 +09001782 cls.set_network_resources()
Masayuki Igawa0870db52015-09-18 21:08:36 +09001783 super(ObjectStorageScenarioTest, cls).setup_credentials()
Matthew Treinish4a596932015-03-06 20:37:01 -05001784 operator_role = CONF.object_storage.operator_role
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +01001785 cls.os_operator = cls.get_client_manager(roles=[operator_role])
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001786
1787 @classmethod
1788 def setup_clients(cls):
Masayuki Igawa0870db52015-09-18 21:08:36 +09001789 super(ObjectStorageScenarioTest, cls).setup_clients()
Chris Dent0d494112014-08-26 13:48:30 +01001790 # Clients for Swift
Matthew Treinish8f268292015-02-24 20:01:36 -05001791 cls.account_client = cls.os_operator.account_client
1792 cls.container_client = cls.os_operator.container_client
1793 cls.object_client = cls.os_operator.object_client
Chris Dent0d494112014-08-26 13:48:30 +01001794
Chris Dentde456a12014-09-10 12:41:15 +01001795 def get_swift_stat(self):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301796 """Get swift status for our user account."""
Chris Dent0d494112014-08-26 13:48:30 +01001797 self.account_client.list_account_containers()
1798 LOG.debug('Swift status information obtained successfully')
1799
Chris Dentde456a12014-09-10 12:41:15 +01001800 def create_container(self, container_name=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301801 """Creates container"""
Chris Dent0d494112014-08-26 13:48:30 +01001802 name = container_name or data_utils.rand_name(
Martin Kopec213d0a42023-11-30 10:28:14 +01001803 prefix=CONF.resource_name_prefix, name='swift-scenario-container')
ghanshyameed40312017-09-15 18:30:04 +03001804 self.container_client.update_container(name)
Chris Dent0d494112014-08-26 13:48:30 +01001805 # look for the container to assure it is created
Chris Dentde456a12014-09-10 12:41:15 +01001806 self.list_and_check_container_objects(name)
Jordan Pittier525ec712016-12-07 17:51:26 +01001807 LOG.debug('Container %s created', name)
Jordan Pittier9e227c52016-02-09 14:35:18 +01001808 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Chris Dent1d4313a2014-10-28 12:16:48 +00001809 self.container_client.delete_container,
1810 name)
Chris Dent0d494112014-08-26 13:48:30 +01001811 return name
1812
Chris Dentde456a12014-09-10 12:41:15 +01001813 def delete_container(self, container_name):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301814 """Deletes container"""
Chris Dent0d494112014-08-26 13:48:30 +01001815 self.container_client.delete_container(container_name)
Jordan Pittier525ec712016-12-07 17:51:26 +01001816 LOG.debug('Container %s deleted', container_name)
Chris Dent0d494112014-08-26 13:48:30 +01001817
Chris Dentde456a12014-09-10 12:41:15 +01001818 def upload_object_to_container(self, container_name, obj_name=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301819 """Uploads object to container"""
Martin Kopec213d0a42023-11-30 10:28:14 +01001820 obj_name = obj_name or data_utils.rand_name(
1821 prefix=CONF.resource_name_prefix, name='swift-scenario-object')
Jordan Pittierb84f2d42016-12-21 19:02:15 +01001822 obj_data = data_utils.random_bytes()
Chris Dent0d494112014-08-26 13:48:30 +01001823 self.object_client.create_object(container_name, obj_name, obj_data)
Jordan Pittier9e227c52016-02-09 14:35:18 +01001824 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Chris Dent1d4313a2014-10-28 12:16:48 +00001825 self.object_client.delete_object,
1826 container_name,
1827 obj_name)
Chris Dent0d494112014-08-26 13:48:30 +01001828 return obj_name, obj_data
1829
Chris Dentde456a12014-09-10 12:41:15 +01001830 def delete_object(self, container_name, filename):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301831 """Deletes object"""
Chris Dent0d494112014-08-26 13:48:30 +01001832 self.object_client.delete_object(container_name, filename)
Chris Dentde456a12014-09-10 12:41:15 +01001833 self.list_and_check_container_objects(container_name,
1834 not_present_obj=[filename])
Chris Dent0d494112014-08-26 13:48:30 +01001835
Chris Dentde456a12014-09-10 12:41:15 +01001836 def list_and_check_container_objects(self, container_name,
1837 present_obj=None,
1838 not_present_obj=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301839 """List and verify objects for a given container
1840
1841 This utility lists objects for a given container
1842 and asserts which are present and
1843 which are not
1844 """
1845
Ghanshyam2a180b82014-06-16 13:54:22 +09001846 if present_obj is None:
1847 present_obj = []
1848 if not_present_obj is None:
1849 not_present_obj = []
ghanshyam871b1a82017-09-14 02:56:16 +03001850 _, object_list = self.container_client.list_container_objects(
Chris Dent0d494112014-08-26 13:48:30 +01001851 container_name)
1852 if present_obj:
1853 for obj in present_obj:
1854 self.assertIn(obj, object_list)
1855 if not_present_obj:
1856 for obj in not_present_obj:
1857 self.assertNotIn(obj, object_list)
1858
Chris Dentde456a12014-09-10 12:41:15 +01001859 def download_and_verify(self, container_name, obj_name, expected_data):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301860 """Asserts the object and expected data to verify if they are same"""
Chris Dent0d494112014-08-26 13:48:30 +01001861 _, obj = self.object_client.get_object(container_name, obj_name)
1862 self.assertEqual(obj, expected_data)