blob: feb2cf1f4bca9c890bf2b8eea14ad9fbe8242866 [file] [log] [blame]
ZhiQiang Fan39f97222013-09-20 04:49:44 +08001# Copyright 2012 OpenStack Foundation
Sean Dague6dbc6da2013-05-08 17:49:46 -04002# Copyright 2013 IBM Corp.
3# All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
Dan Smith49c2b3b2023-04-26 15:52:22 -070017import copy
Martin Kopec02af6a42020-03-03 12:39:12 +000018import os
melanie witta0b161b2023-12-01 23:41:44 +000019import re
20import shutil
Sean Dague6dbc6da2013-05-08 17:49:46 -040021import subprocess
melanie witta0b161b2023-12-01 23:41:44 +000022import tarfile
23import tempfile
Sean Dague6dbc6da2013-05-08 17:49:46 -040024
Sean Dague6dbc6da2013-05-08 17:49:46 -040025import netaddr
Soniya Vyas795ef252020-12-10 19:07:23 +053026
Doug Hellmann583ce2c2015-03-11 14:55:46 +000027from oslo_log import log
Andrey Pavlovc8bd4b12015-08-17 10:20:17 +030028from oslo_serialization import jsonutils as json
Yatin Kumbhareee4924c2016-06-09 15:12:06 +053029from oslo_utils import netutils
Sean Dague6dbc6da2013-05-08 17:49:46 -040030
lanoux5fc14522015-09-21 08:17:35 +000031from tempest.common import compute
Masayuki Igawa4ded9f02014-02-17 15:05:59 +090032from tempest.common.utils.linux import remote_client
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +000033from tempest.common.utils import net_utils
Ken'ichi Ohmichi0eb153c2015-07-13 02:18:25 +000034from tempest.common import waiters
Matthew Treinish6c072292014-01-29 19:15:52 +000035from tempest import config
Giulio Fidente92f77192013-08-26 17:13:28 +020036from tempest import exceptions
Ghanshyam Mann09c4eb92019-06-04 13:07:12 +000037from tempest.lib.common import api_version_utils
Ken'ichi Ohmichibe4fb502017-03-10 10:04:48 -080038from tempest.lib.common.utils import data_utils
Jordan Pittier9e227c52016-02-09 14:35:18 +010039from tempest.lib.common.utils import test_utils
Andrea Frittoli (andreaf)db9672e2016-02-23 14:07:24 -050040from tempest.lib import exceptions as lib_exc
Sean Dague6dbc6da2013-05-08 17:49:46 -040041import tempest.test
Sean Dague6dbc6da2013-05-08 17:49:46 -040042
Matthew Treinish6c072292014-01-29 19:15:52 +000043CONF = config.CONF
Sean Dague6dbc6da2013-05-08 17:49:46 -040044
Attila Fazekasfb7552a2013-08-27 13:02:26 +020045LOG = log.getLogger(__name__)
46
Ghanshyam Mann09c4eb92019-06-04 13:07:12 +000047LATEST_MICROVERSION = 'latest'
48
Sean Dague6dbc6da2013-05-08 17:49:46 -040049
Andrea Frittoli2e733b52014-07-16 14:12:11 +010050class ScenarioTest(tempest.test.BaseTestCase):
Andrea Frittoli486ede72014-09-25 11:50:05 +010051 """Base class for scenario tests. Uses tempest own clients. """
Andrea Frittoli2e733b52014-07-16 14:12:11 +010052
Ghanshyam Mann64281392021-03-24 18:48:38 -050053 credentials = ['primary', 'admin']
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +000054
Ghanshyam Mann09c4eb92019-06-04 13:07:12 +000055 compute_min_microversion = None
56 compute_max_microversion = LATEST_MICROVERSION
57 volume_min_microversion = None
58 volume_max_microversion = LATEST_MICROVERSION
59 placement_min_microversion = None
60 placement_max_microversion = LATEST_MICROVERSION
61
62 @classmethod
63 def skip_checks(cls):
64 super(ScenarioTest, cls).skip_checks()
65 api_version_utils.check_skip_with_microversion(
66 cls.compute_min_microversion, cls.compute_max_microversion,
67 CONF.compute.min_microversion, CONF.compute.max_microversion)
68 api_version_utils.check_skip_with_microversion(
69 cls.volume_min_microversion, cls.volume_max_microversion,
70 CONF.volume.min_microversion, CONF.volume.max_microversion)
71 api_version_utils.check_skip_with_microversion(
72 cls.placement_min_microversion, cls.placement_max_microversion,
73 CONF.placement.min_microversion, CONF.placement.max_microversion)
74
75 @classmethod
76 def resource_setup(cls):
77 super(ScenarioTest, cls).resource_setup()
78 cls.compute_request_microversion = (
79 api_version_utils.select_request_microversion(
80 cls.compute_min_microversion,
81 CONF.compute.min_microversion))
82 cls.volume_request_microversion = (
83 api_version_utils.select_request_microversion(
84 cls.volume_min_microversion,
85 CONF.volume.min_microversion))
86 cls.placement_request_microversion = (
87 api_version_utils.select_request_microversion(
88 cls.placement_min_microversion,
89 CONF.placement.min_microversion))
90
Ghanshyam Mann18b45d72021-12-07 12:37:29 -060091 cls.setup_api_microversion_fixture(
92 compute_microversion=cls.compute_request_microversion,
93 volume_microversion=cls.volume_request_microversion,
94 placement_microversion=cls.placement_request_microversion)
Ghanshyam Mann09c4eb92019-06-04 13:07:12 +000095
Dan Smith49c2b3b2023-04-26 15:52:22 -070096 @classmethod
97 def setup_credentials(cls):
98 # Setting network=True, subnet=True creates a default network
99 cls.set_network_resources(
100 network=True,
101 subnet=True,
102 router=True,
103 dhcp=True)
104 super(ScenarioTest, cls).setup_credentials()
105
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530106 def setup_compute_client(cls):
Ghanshyam Mann1072f502021-03-24 19:15:22 -0500107 """Compute client"""
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530108 cls.compute_images_client = cls.os_primary.compute_images_client
109 cls.keypairs_client = cls.os_primary.keypairs_client
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530110 cls.servers_client = cls.os_primary.servers_client
111 cls.interface_client = cls.os_primary.interfaces_client
Ghanshyam Mann1072f502021-03-24 19:15:22 -0500112 cls.flavors_client = cls.os_primary.flavors_client
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530113
114 def setup_network_client(cls):
115 """Neutron network client"""
116 cls.networks_client = cls.os_primary.networks_client
117 cls.ports_client = cls.os_primary.ports_client
118 cls.routers_client = cls.os_primary.routers_client
119 cls.subnets_client = cls.os_primary.subnets_client
120 cls.floating_ips_client = cls.os_primary.floating_ips_client
121 cls.security_groups_client = cls.os_primary.security_groups_client
122 cls.security_group_rules_client = (
123 cls.os_primary.security_group_rules_client)
124
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +0000125 @classmethod
126 def setup_clients(cls):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530127 """This setup the service clients for the tests"""
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +0000128 super(ScenarioTest, cls).setup_clients()
Jordan Pittier1d2e40f2016-01-05 18:49:14 +0100129 if CONF.service_available.glance:
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700130 if CONF.image_feature_enabled.api_v2:
jeremy.zhang0343be52017-05-25 21:29:57 +0800131 cls.image_client = cls.os_primary.image_client_v2
Matt Riedemann2aa19d42016-06-06 17:45:41 -0400132 else:
Matthew Treinish4217a702016-10-07 17:27:11 -0400133 raise lib_exc.InvalidConfiguration(
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700134 'api_v2 must be True in [image-feature-enabled].')
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530135
136 cls.setup_compute_client(cls)
137 cls.setup_network_client(cls)
Andrea Frittolia6b30152017-08-04 10:46:10 +0100138 if CONF.service_available.cinder:
139 cls.volumes_client = cls.os_primary.volumes_client_latest
140 cls.snapshots_client = cls.os_primary.snapshots_client_latest
lkuchlane20e6a82018-05-08 11:28:46 +0300141 cls.backups_client = cls.os_primary.backups_client_latest
Ivan Kolodyazhnybcfc32e2015-08-06 13:31:36 +0300142
Jordan Pittierf672b7d2016-06-20 18:50:40 +0200143 # ## Test functions library
Jordan Pittierf672b7d2016-06-20 18:50:40 +0200144 # The create_[resource] functions only return body and discard the
145 # resp part which is not used in scenario tests
Andrea Frittoli247058f2014-07-16 16:09:22 +0100146
zhufl1e446b52017-10-16 16:54:57 +0800147 def create_port(self, network_id, client=None, **kwargs):
Martin Kopec9c874412020-12-17 20:43:26 +0000148 """Creates port for the respective network_id
149
150 :param network_id: the id of the network
151 :param client: the client to use, defaults to self.ports_client
152 :param kwargs: additional arguments such as:
153 - namestart - a string to generate a name for the port from
154 - default is self.__class__.__name__
155 - 'binding:vnic_type' - defaults to CONF.network.port_vnic_type
156 - 'binding:profile' - defaults to CONF.network.port_profile
157 """
Eliad Cohenbec2d4d2022-09-14 17:52:59 +0000158
Lenny Verkhovsky136376f2016-06-29 14:33:34 +0300159 if not client:
160 client = self.ports_client
Martin Kopec9c874412020-12-17 20:43:26 +0000161 name = data_utils.rand_name(
Martin Kopec213d0a42023-11-30 10:28:14 +0100162 prefix=CONF.resource_name_prefix,
163 name=kwargs.pop('namestart', self.__class__.__name__))
Edan David408a97b2018-01-15 03:52:15 -0500164 if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
165 kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
166 if CONF.network.port_profile and 'binding:profile' not in kwargs:
167 kwargs['binding:profile'] = CONF.network.port_profile
Lenny Verkhovsky136376f2016-06-29 14:33:34 +0300168 result = client.create_port(
169 name=name,
170 network_id=network_id,
171 **kwargs)
Soniya Vyas0123f522020-09-24 17:43:26 +0530172 self.assertIsNotNone(result, 'Unable to allocate port')
Eliad Cohenbec2d4d2022-09-14 17:52:59 +0000173 port_id = result['port']['id']
Lenny Verkhovsky136376f2016-06-29 14:33:34 +0300174 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Eliad Cohenbec2d4d2022-09-14 17:52:59 +0000175 client.delete_port, port_id)
176 port = waiters.wait_for_port_status(
177 client=client, port_id=port_id, status="DOWN")
178 return port["port"]
Lenny Verkhovsky136376f2016-06-29 14:33:34 +0300179
Martin Kopec30b4d532020-10-16 12:02:43 +0000180 def create_keypair(self, client=None, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530181 """Creates keypair
182
183 Keypair is a public key of OpenSSH key pair used for accessing
184 and create servers
185 Keypair can also be created by a private key for the same purpose
186 Here, the keys are randomly generated[public/private]
187 """
Yair Frieddb6c9e92014-08-06 08:53:13 +0300188 if not client:
189 client = self.keypairs_client
Martin Kopec30b4d532020-10-16 12:02:43 +0000190 if not kwargs.get('name'):
Martin Kopec213d0a42023-11-30 10:28:14 +0100191 kwargs['name'] = data_utils.rand_name(
192 prefix=CONF.resource_name_prefix,
193 name=self.__class__.__name__)
Andrea Frittoli247058f2014-07-16 16:09:22 +0100194 # We don't need to create a keypair by pubkey in scenario
Martin Kopec30b4d532020-10-16 12:02:43 +0000195 body = client.create_keypair(**kwargs)
196 self.addCleanup(client.delete_keypair, kwargs['name'])
ghanshyamdee01f22015-08-17 11:41:47 +0900197 return body['keypair']
Andrea Frittoli247058f2014-07-16 16:09:22 +0100198
Anusha Ramineni9aaef8b2016-01-19 10:56:40 +0530199 def create_server(self, name=None, image_id=None, flavor=None,
Dan Smith65744692023-05-04 09:06:41 -0700200 validatable=None, wait_until='ACTIVE',
Jordan Pittierf672b7d2016-06-20 18:50:40 +0200201 clients=None, **kwargs):
lanoux5fc14522015-09-21 08:17:35 +0000202 """Wrapper utility that returns a test server.
Andrea Frittoli247058f2014-07-16 16:09:22 +0100203
lanoux5fc14522015-09-21 08:17:35 +0000204 This wrapper utility calls the common create test server and
205 returns a test server. The purpose of this wrapper is to minimize
206 the impact on the code of the tests already using this
207 function.
Noam Angel6e309952019-01-27 05:52:40 +0000208
209 :param **kwargs:
210 See extra parameters below
211
212 :Keyword Arguments:
213 * *vnic_type* (``string``) --
214 used when launching instances with pre-configured ports.
215 Examples:
216 normal: a traditional virtual port that is either attached
217 to a linux bridge or an openvswitch bridge on a
218 compute node.
219 direct: an SR-IOV port that is directly attached to a VM
220 macvtap: an SR-IOV port that is attached to a VM via a macvtap
221 device.
Tom Stappaerts27fd5cb2020-11-26 12:07:47 +0100222 direct-physical: an SR-IOV port that is directly attached to a
223 VM using physical instead of virtual
224 functions.
225 baremetal: a baremetal port directly attached to a baremetal
226 node.
227 virtio-forwarder: an SR-IOV port that is indirectly attached
228 to a VM using a low-latency vhost-user
229 forwarding process.
Noam Angel6e309952019-01-27 05:52:40 +0000230 Defaults to ``CONF.network.port_vnic_type``.
231 * *port_profile* (``dict``) --
232 This attribute is a dictionary that can be used (with admin
233 credentials) to supply information influencing the binding of
234 the port.
235 example: port_profile = "capabilities:[switchdev]"
236 Defaults to ``CONF.network.port_profile``.
Martin Kopec9c874412020-12-17 20:43:26 +0000237 * *create_port_body* (``dict``) --
238 This attribute is a dictionary of additional arguments to be
239 passed to create_port method.
Andrea Frittoli247058f2014-07-16 16:09:22 +0100240 """
Andrea Frittoli247058f2014-07-16 16:09:22 +0100241
lanoux5fc14522015-09-21 08:17:35 +0000242 # NOTE(jlanoux): As a first step, ssh checks in the scenario
243 # tests need to be run regardless of the run_validation and
244 # validatable parameters and thus until the ssh validation job
245 # becomes voting in CI. The test resources management and IP
246 # association are taken care of in the scenario tests.
247 # Therefore, the validatable parameter is set to false in all
248 # those tests. In this way create_server just return a standard
249 # server and the scenario tests always perform ssh checks.
250
251 # Needed for the cross_tenant_traffic test:
252 if clients is None:
jeremy.zhang0343be52017-05-25 21:29:57 +0800253 clients = self.os_primary
lanoux5fc14522015-09-21 08:17:35 +0000254
zhufl24208c22016-10-25 15:23:48 +0800255 if name is None:
Martin Kopec213d0a42023-11-30 10:28:14 +0100256 name = data_utils.rand_name(
257 prefix=CONF.resource_name_prefix,
258 name=self.__class__.__name__ + "-server")
zhufl24208c22016-10-25 15:23:48 +0800259
Noam Angel6e309952019-01-27 05:52:40 +0000260 vnic_type = kwargs.pop('vnic_type', CONF.network.port_vnic_type)
261 profile = kwargs.pop('port_profile', CONF.network.port_profile)
lanoux5fc14522015-09-21 08:17:35 +0000262
Lenny Verkhovskyfe3a03f2018-02-28 10:19:37 +0000263 # If vnic_type or profile are configured create port for
lanoux5fc14522015-09-21 08:17:35 +0000264 # every network
Lenny Verkhovskyfe3a03f2018-02-28 10:19:37 +0000265 if vnic_type or profile:
lanoux5fc14522015-09-21 08:17:35 +0000266 ports = []
Martin Kopec9c874412020-12-17 20:43:26 +0000267 create_port_body = kwargs.pop('create_port_body', {})
Lenny Verkhovsky69363502016-07-17 16:33:33 +0300268
Lenny Verkhovskyfe3a03f2018-02-28 10:19:37 +0000269 if vnic_type:
270 create_port_body['binding:vnic_type'] = vnic_type
271
272 if profile:
273 create_port_body['binding:profile'] = profile
274
lanoux5fc14522015-09-21 08:17:35 +0000275 if kwargs:
276 # Convert security group names to security group ids
277 # to pass to create_port
278 if 'security_groups' in kwargs:
Thiago Paiva66cded22016-08-15 14:55:58 -0300279 security_groups = \
John Warrenf9606e92015-12-10 12:12:42 -0500280 clients.security_groups_client.list_security_groups(
lanoux5fc14522015-09-21 08:17:35 +0000281 ).get('security_groups')
282 sec_dict = dict([(s['name'], s['id'])
afazekas40fcb9b2019-03-08 11:25:11 +0100283 for s in security_groups])
lanoux5fc14522015-09-21 08:17:35 +0000284
285 sec_groups_names = [s['name'] for s in kwargs.pop(
286 'security_groups')]
287 security_groups_ids = [sec_dict[s]
288 for s in sec_groups_names]
289
290 if security_groups_ids:
291 create_port_body[
292 'security_groups'] = security_groups_ids
Lenny Verkhovsky69363502016-07-17 16:33:33 +0300293 networks = kwargs.pop('networks', [])
294 else:
295 networks = []
lanoux5fc14522015-09-21 08:17:35 +0000296
297 # If there are no networks passed to us we look up
Lenny Verkhovsky136376f2016-06-29 14:33:34 +0300298 # for the project's private networks and create a port.
299 # The same behaviour as we would expect when passing
300 # the call to the clients with no networks
lanoux5fc14522015-09-21 08:17:35 +0000301 if not networks:
302 networks = clients.networks_client.list_networks(
Lenny Verkhovsky136376f2016-06-29 14:33:34 +0300303 **{'router:external': False, 'fields': 'id'})['networks']
304
305 # It's net['uuid'] if networks come from kwargs
306 # and net['id'] if they come from
307 # clients.networks_client.list_networks
lanoux5fc14522015-09-21 08:17:35 +0000308 for net in networks:
Lenny Verkhovsky97f7cea2016-08-15 13:29:48 +0000309 net_id = net.get('uuid', net.get('id'))
Lenny Verkhovsky69363502016-07-17 16:33:33 +0300310 if 'port' not in net:
zhufl1e446b52017-10-16 16:54:57 +0800311 port = self.create_port(network_id=net_id,
312 client=clients.ports_client,
313 **create_port_body)
Lenny Verkhovsky69363502016-07-17 16:33:33 +0300314 ports.append({'port': port['id']})
315 else:
316 ports.append({'port': net['port']})
lanoux5fc14522015-09-21 08:17:35 +0000317 if ports:
318 kwargs['networks'] = ports
319 self.ports = ports
320
321 tenant_network = self.get_tenant_network()
322
Marc Koderer979e4942016-12-08 10:07:59 +0100323 if CONF.compute.compute_volume_common_az:
324 kwargs.setdefault('availability_zone',
325 CONF.compute.compute_volume_common_az)
326
Dan Smith65744692023-05-04 09:06:41 -0700327 kwargs['validatable'] = bool(validatable)
Dan Smith49c2b3b2023-04-26 15:52:22 -0700328 keypair = kwargs.pop('keypair', None)
Dan Smith65744692023-05-04 09:06:41 -0700329 if wait_until == 'SSHABLE' and (
330 kwargs.get('validation_resources') is None):
Dan Smith49c2b3b2023-04-26 15:52:22 -0700331 # NOTE(danms): We should do this whether valdiation is enabled or
332 # not to consistently provide the resources to the
333 # create_test_server() function. If validation is disabled, then
334 # get_test_validation_resources() is basically a no-op for
335 # performance.
336 validation_resources = self.get_test_validation_resources(
337 self.os_primary)
338 if keypair:
339 validation_resources = copy.deepcopy(validation_resources)
340 validation_resources.update(
341 keypair=keypair)
Dan Smith65744692023-05-04 09:06:41 -0700342 kwargs.update({
343 'validatable': (validatable if validatable is not None
344 else True),
345 'validation_resources': validation_resources})
Dan Smith49c2b3b2023-04-26 15:52:22 -0700346 if keypair:
347 kwargs.update({'key_name': keypair['name']})
348
Ferenc Horváthbce1fcf2017-06-07 11:19:51 +0200349 body, _ = compute.create_test_server(
lanoux5fc14522015-09-21 08:17:35 +0000350 clients,
351 tenant_network=tenant_network,
352 wait_until=wait_until,
Anusha Ramineni9aaef8b2016-01-19 10:56:40 +0530353 name=name, flavor=flavor,
354 image_id=image_id, **kwargs)
lanoux5fc14522015-09-21 08:17:35 +0000355
Jordan Pittierf672b7d2016-06-20 18:50:40 +0200356 self.addCleanup(waiters.wait_for_server_termination,
357 clients.servers_client, body['id'])
358 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
359 clients.servers_client.delete_server, body['id'])
lanoux5fc14522015-09-21 08:17:35 +0000360 server = clients.servers_client.show_server(body['id'])['server']
Andrea Frittoli247058f2014-07-16 16:09:22 +0100361 return server
362
Markus Zoeller3d2a21c2015-02-27 12:04:22 +0100363 def create_volume(self, size=None, name=None, snapshot_id=None,
Benny Kopilov7beb2d02022-04-12 20:33:53 +0300364 imageRef=None, volume_type=None, wait_until='available',
Milana Levye9a58a12023-02-21 12:55:20 +0000365 client=None, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530366 """Creates volume
367
368 This wrapper utility creates volume and waits for volume to be
Benny Kopilov7beb2d02022-04-12 20:33:53 +0300369 in 'available' state by default. If wait_until is None, means no wait.
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530370 This method returns the volume's full representation by GET request.
371 """
Milana Levye9a58a12023-02-21 12:55:20 +0000372 if client is None:
373 client = self.volumes_client
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530374
Ken'ichi Ohmichiadb905e2016-08-26 15:16:23 -0700375 if size is None:
376 size = CONF.volume.volume_size
Nuno Santosb746d992016-11-17 15:41:55 -0500377 if imageRef:
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700378 image = self.image_client.show_image(imageRef)
zhufl66275c22018-03-28 15:32:14 +0800379 min_disk = image.get('min_disk')
Nuno Santosb746d992016-11-17 15:41:55 -0500380 size = max(size, min_disk)
Andrea Frittoli247058f2014-07-16 16:09:22 +0100381 if name is None:
Martin Kopec213d0a42023-11-30 10:28:14 +0100382 name = data_utils.rand_name(
383 prefix=CONF.resource_name_prefix,
384 name=self.__class__.__name__ + "-volume")
Martin Kopecd3ad5e92020-10-16 14:45:09 +0000385 kwargs.update({'name': name,
386 'snapshot_id': snapshot_id,
387 'imageRef': imageRef,
388 'volume_type': volume_type,
389 'size': size})
Marc Koderer979e4942016-12-08 10:07:59 +0100390
391 if CONF.compute.compute_volume_common_az:
392 kwargs.setdefault('availability_zone',
393 CONF.compute.compute_volume_common_az)
394
Milana Levye9a58a12023-02-21 12:55:20 +0000395 volume = client.create_volume(**kwargs)['volume']
Matt Riedemanne85c2702014-09-10 11:50:13 -0700396
Milana Levye9a58a12023-02-21 12:55:20 +0000397 self.addCleanup(client.wait_for_resource_deletion,
Jordan Pittier5e1741c2016-03-02 18:25:51 +0100398 volume['id'])
Jordan Pittier9e227c52016-02-09 14:35:18 +0100399 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Milana Levye9a58a12023-02-21 12:55:20 +0000400 client.delete_volume, volume['id'])
lkuchlan5cbc00a2017-03-26 11:49:54 +0300401 self.assertEqual(name, volume['name'])
Benny Kopilov7beb2d02022-04-12 20:33:53 +0300402 if wait_until:
Milana Levye9a58a12023-02-21 12:55:20 +0000403 waiters.wait_for_volume_resource_status(client,
Benny Kopilov7beb2d02022-04-12 20:33:53 +0300404 volume['id'], wait_until)
405 # The volume retrieved on creation has a non-up-to-date status.
406 # Retrieval after it becomes active ensures correct details.
Milana Levye9a58a12023-02-21 12:55:20 +0000407 volume = client.show_volume(volume['id'])['volume']
408
Andrea Frittoli247058f2014-07-16 16:09:22 +0100409 return volume
410
lkuchlane20e6a82018-05-08 11:28:46 +0300411 def create_backup(self, volume_id, name=None, description=None,
412 force=False, snapshot_id=None, incremental=False,
Martin Kopec4a140052020-10-16 16:26:55 +0000413 container=None, **kwargs):
414 """Creates a backup of the given volume_id or snapshot_id
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530415
Martin Kopec4a140052020-10-16 16:26:55 +0000416 This wrapper utility creates a backup and waits until it is in
417 'available' state.
418
419 :param volume_id: UUID of the volume to back up
420 :param name: backup name, '$classname-backup' by default
421 :param description: Description of the backup, None by default
422 :param force: boolean whether to backup even if the volume is attached
423 False by default
424 :param snapshot_id: UUID of the source snapshot to back up
425 None by default
426 :param incremental: boolean, False by default
427 :param container: a container name, None by default
428 :param **kwargs: additional parameters per the documentation:
429 https://docs.openstack.org/api-ref/block-storage/v3/
430 #create-a-backup
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530431 """
lkuchlane20e6a82018-05-08 11:28:46 +0300432
433 name = name or data_utils.rand_name(
Martin Kopec213d0a42023-11-30 10:28:14 +0100434 prefix=CONF.resource_name_prefix,
435 name=self.__class__.__name__ + "-backup")
Martin Kopec4a140052020-10-16 16:26:55 +0000436 args = {'name': name,
437 'description': description,
438 'force': force,
439 'snapshot_id': snapshot_id,
440 'incremental': incremental,
441 'container': container}
442 args.update(kwargs)
lkuchlane20e6a82018-05-08 11:28:46 +0300443 backup = self.backups_client.create_backup(volume_id=volume_id,
Yosi Ben Shimonbce267e2024-02-06 15:00:25 +0200444 **args)['backup']
lkuchlane20e6a82018-05-08 11:28:46 +0300445 self.addCleanup(self.backups_client.delete_backup, backup['id'])
446 waiters.wait_for_volume_resource_status(self.backups_client,
447 backup['id'], 'available')
448 return backup
449
Martin Kopec4a140052020-10-16 16:26:55 +0000450 def restore_backup(self, backup_id, **kwargs):
451 """Restores a backup given by the backup_id
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530452
Martin Kopec4a140052020-10-16 16:26:55 +0000453 This wrapper utility restores a backup and waits until it is in
454 'available' state.
455
456 :param backup_id: UUID of a backup to restore
457 :param **kwargs: additional parameters per the documentation:
458 https://docs.openstack.org/api-ref/block-storage/v3/
459 #restore-a-backup
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530460 """
461
Martin Kopec4a140052020-10-16 16:26:55 +0000462 body = self.backups_client.restore_backup(backup_id, **kwargs)
463 restore = body['restore']
Sofia Enriquez404b55c2022-05-26 19:33:47 +0000464
465 using_pre_existing_volume = kwargs.get('volume_id', False)
466 if not using_pre_existing_volume:
467 self.addCleanup(self.volumes_client.delete_volume,
468 restore['volume_id'])
469
lkuchlane20e6a82018-05-08 11:28:46 +0300470 waiters.wait_for_volume_resource_status(self.backups_client,
471 backup_id, 'available')
472 waiters.wait_for_volume_resource_status(self.volumes_client,
473 restore['volume_id'],
474 'available')
475 self.assertEqual(backup_id, restore['backup_id'])
476 return restore
477
Martin Kopecbe8ba2c2020-12-17 21:33:32 +0000478 def rebuild_server(self, server_id, image=None, preserve_ephemeral=False,
479 wait=True, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530480 if image is None:
481 image = CONF.compute.image_ref
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530482 LOG.debug("Rebuilding server (id: %s, image: %s, preserve eph: %s)",
483 server_id, image, preserve_ephemeral)
484 self.servers_client.rebuild_server(
485 server_id=server_id,
486 image_ref=image,
487 preserve_ephemeral=preserve_ephemeral,
Martin Kopecbe8ba2c2020-12-17 21:33:32 +0000488 **kwargs)
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530489 if wait:
490 waiters.wait_for_server_status(self.servers_client,
491 server_id, 'ACTIVE')
492
lkuchlan73ed1f32017-07-06 16:22:12 +0300493 def create_volume_snapshot(self, volume_id, name=None, description=None,
Martin Kopeca17cca42020-10-17 16:57:51 +0000494 metadata=None, force=False, **kwargs):
495 """Creates volume's snapshot
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530496
Martin Kopeca17cca42020-10-17 16:57:51 +0000497 This wrapper utility creates volume snapshot and waits for it until
498 it is in 'available' state.
499
500 :param volume_id: UUID of a volume to create snapshot of
501 :param name: name of the snapshot, '$classname-snapshot' by default
502 :param description: description of the snapshot
503 :param metadata: metadata key and value pairs for the snapshot
504 :param force: whether snapshot even when the volume is attached
505 :param **kwargs: additional parameters per the doc
506 https://docs.openstack.org/api-ref/block-storage/v3/
507 #create-a-snapshot
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530508 """
509
lkuchlan73ed1f32017-07-06 16:22:12 +0300510 name = name or data_utils.rand_name(
Martin Kopec213d0a42023-11-30 10:28:14 +0100511 prefix=CONF.resource_name_prefix,
512 name=self.__class__.__name__ + '-snapshot')
lkuchlan73ed1f32017-07-06 16:22:12 +0300513 snapshot = self.snapshots_client.create_snapshot(
514 volume_id=volume_id,
515 force=force,
Martin Kopec20c87c72020-10-17 11:42:29 +0000516 name=name,
lkuchlan73ed1f32017-07-06 16:22:12 +0300517 description=description,
Martin Kopeca17cca42020-10-17 16:57:51 +0000518 metadata=metadata,
519 **kwargs)['snapshot']
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530520
lkuchlan73ed1f32017-07-06 16:22:12 +0300521 self.addCleanup(self.snapshots_client.wait_for_resource_deletion,
522 snapshot['id'])
Benny Kopilovd4d49b02021-08-10 18:54:01 +0300523 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
524 self.snapshots_client.delete_snapshot, snapshot['id'])
lkuchlan73ed1f32017-07-06 16:22:12 +0300525 waiters.wait_for_volume_resource_status(self.snapshots_client,
526 snapshot['id'], 'available')
Benny Kopilov11b28002017-12-19 12:46:19 +0200527 snapshot = self.snapshots_client.show_snapshot(
528 snapshot['id'])['snapshot']
lkuchlan73ed1f32017-07-06 16:22:12 +0300529 return snapshot
530
Soniya Vyasfd4dcf92021-02-17 18:12:43 +0530531 def cleanup_volume_type(self, volume_type):
Lee Yarwoodbe64e1a2019-04-09 14:02:12 +0100532 """Clean up a given volume type.
533
534 Ensuring all volumes associated to a type are first removed before
535 attempting to remove the type itself. This includes any image volume
536 cache volumes stored in a separate tenant to the original volumes
537 created from the type.
538 """
539 admin_volume_type_client = self.os_admin.volume_types_client_latest
540 admin_volumes_client = self.os_admin.volumes_client_latest
541 volumes = admin_volumes_client.list_volumes(
542 detail=True, params={'all_tenants': 1})['volumes']
543 type_name = volume_type['name']
544 for volume in [v for v in volumes if v['volume_type'] == type_name]:
545 test_utils.call_and_ignore_notfound_exc(
546 admin_volumes_client.delete_volume, volume['id'])
547 admin_volumes_client.wait_for_resource_deletion(volume['id'])
548 admin_volume_type_client.delete_volume_type(volume_type['id'])
549
Martin Kopec8e673a42020-10-18 17:33:02 +0000550 def create_volume_type(self, client=None, name=None, backend_name=None,
551 **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530552 """Creates volume type
553
554 In a multiple-storage back-end configuration,
555 each back end has a name (volume_backend_name).
556 The name of the back end is declared as an extra-specification
557 of a volume type (such as, volume_backend_name=LVM).
558 When a volume is created, the scheduler chooses an
559 appropriate back end to handle the request, according
560 to the volume type specified by the user.
561 The scheduler uses volume types to explicitly create volumes on
562 specific back ends.
563
564 Before using volume type, a volume type has to be declared
565 to Block Storage. In addition to that, an extra-specification
566 has to be created to link the volume type to a back end name.
567 """
568
scottda61f68ac2016-06-07 12:07:55 -0600569 if not client:
ghanshyam6c682ff2018-08-06 09:54:45 +0000570 client = self.os_admin.volume_types_client_latest
Matt Riedemann514495b2019-05-04 17:34:12 +0000571 if not name:
572 class_name = self.__class__.__name__
Martin Kopec213d0a42023-11-30 10:28:14 +0100573 name = data_utils.rand_name(
574 prefix=CONF.resource_name_prefix,
575 name=class_name + '-volume-type')
576 randomized_name = data_utils.rand_name(
577 prefix=CONF.resource_name_prefix, name='scenario-type-' + name)
scottda61f68ac2016-06-07 12:07:55 -0600578
579 LOG.debug("Creating a volume type: %s on backend %s",
580 randomized_name, backend_name)
Martin Kopec8e673a42020-10-18 17:33:02 +0000581 extra_specs = kwargs.pop("extra_specs", {})
scottda61f68ac2016-06-07 12:07:55 -0600582 if backend_name:
Martin Kopec8e673a42020-10-18 17:33:02 +0000583 extra_specs.update({"volume_backend_name": backend_name})
scottda61f68ac2016-06-07 12:07:55 -0600584
Martin Kopec8e673a42020-10-18 17:33:02 +0000585 volume_type_resp = client.create_volume_type(
586 name=randomized_name, extra_specs=extra_specs, **kwargs)
587 volume_type = volume_type_resp['volume_type']
588
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530589 self.assertIn('id', volume_type)
Soniya Vyasfd4dcf92021-02-17 18:12:43 +0530590 self.addCleanup(self.cleanup_volume_type, volume_type)
scottda61f68ac2016-06-07 12:07:55 -0600591 return volume_type
592
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500593 def create_security_group(self, security_group_rules_client=None,
594 project_id=None,
595 namestart='secgroup-smoke',
596 security_groups_client=None):
597 if security_group_rules_client is None:
598 security_group_rules_client = self.security_group_rules_client
599 if security_groups_client is None:
600 security_groups_client = self.security_groups_client
601 if project_id is None:
602 project_id = security_groups_client.project_id
603 secgroup = self.create_empty_security_group(
604 namestart=namestart, client=security_groups_client,
605 project_id=project_id)
Andrea Frittoli247058f2014-07-16 16:09:22 +0100606
607 # Add rules to the security group
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500608 rules = self.create_loginable_secgroup_rule(
609 security_group_rules_client=security_group_rules_client,
610 secgroup=secgroup,
611 security_groups_client=security_groups_client)
612 for rule in rules:
613 self.assertEqual(project_id, rule['project_id'])
614 self.assertEqual(secgroup['id'], rule['security_group_id'])
Andrea Frittoli247058f2014-07-16 16:09:22 +0100615 return secgroup
616
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500617 def create_empty_security_group(self, client=None, project_id=None,
618 namestart='secgroup-smoke'):
619 """Create a security group without rules.
620
621 Default rules will be created:
622 - IPv4 egress to any
623 - IPv6 egress to any
624 :param project_id: secgroup will be created in this project
625 :returns: the created security group
626 """
627
628 if client is None:
629 client = self.security_groups_client
630 if not project_id:
631 project_id = client.project_id
Martin Kopec213d0a42023-11-30 10:28:14 +0100632 sg_name = data_utils.rand_name(
633 prefix=CONF.resource_name_prefix, name=namestart)
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500634 sg_desc = sg_name + " description"
635 sg_dict = dict(name=sg_name,
636 description=sg_desc)
637 sg_dict['project_id'] = project_id
638 result = client.create_security_group(**sg_dict)
639
640 secgroup = result['security_group']
641 self.assertEqual(secgroup['name'], sg_name)
642 self.assertEqual(project_id, secgroup['project_id'])
643 self.assertEqual(secgroup['description'], sg_desc)
644
645 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
646 client.delete_security_group, secgroup['id'])
647 return secgroup
648
649 def create_security_group_rule(self, secgroup=None,
650 sec_group_rules_client=None,
651 project_id=None,
652 security_groups_client=None, **kwargs):
653 """Create a rule from a dictionary of rule parameters.
654
655 Create a rule in a secgroup. if secgroup not defined will search for
656 default secgroup in project_id.
657 :param secgroup: the security group.
658 :param project_id: if secgroup not passed -- the tenant in which to
659 search for default secgroup
660 :param kwargs: a dictionary containing rule parameters:
661 for example, to allow incoming ssh:
662 rule = {
663 direction: 'ingress'
664 protocol:'tcp',
665 port_range_min: 22,
666 port_range_max: 22
667 }
668 """
669
670 if sec_group_rules_client is None:
671 sec_group_rules_client = self.security_group_rules_client
672 if security_groups_client is None:
673 security_groups_client = self.security_groups_client
674 if not project_id:
675 project_id = security_groups_client.project_id
676 if secgroup is None:
677 # Get default secgroup for project_id
678 default_secgroups = security_groups_client.list_security_groups(
679 name='default', project_id=project_id)['security_groups']
680 msg = "No default security group for project %s." % (project_id)
681 self.assertNotEmpty(default_secgroups, msg)
682 secgroup = default_secgroups[0]
683
684 ruleset = dict(security_group_id=secgroup['id'],
685 project_id=secgroup['project_id'])
686 ruleset.update(kwargs)
687
688 sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
689 sg_rule = sg_rule['security_group_rule']
690
691 self.assertEqual(secgroup['tenant_id'], sg_rule['tenant_id'])
692 self.assertEqual(secgroup['id'], sg_rule['security_group_id'])
693
694 return sg_rule
695
696 def create_loginable_secgroup_rule(self, security_group_rules_client=None,
697 secgroup=None,
Roman Popelka3b0ccb02022-03-24 10:25:19 +0100698 security_groups_client=None,
699 rulesets=None):
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500700 """Create loginable security group rule by neutron clients by default.
701
702 This function will create:
703 1. egress and ingress tcp port 22 allow rule in order to allow ssh
704 access for ipv4.
705 2. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6.
706 3. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4.
707 """
708
709 if security_group_rules_client is None:
710 security_group_rules_client = self.security_group_rules_client
711 if security_groups_client is None:
712 security_groups_client = self.security_groups_client
Roman Popelka3b0ccb02022-03-24 10:25:19 +0100713 if rulesets is None:
714 rulesets = [
715 dict(
716 # ssh
717 protocol='tcp',
718 port_range_min=22,
719 port_range_max=22,
720 ),
721 dict(
722 # ping
723 protocol='icmp',
724 ),
725 dict(
726 # ipv6-icmp for ping6
727 protocol='icmp',
728 ethertype='IPv6',
729 )
730 ]
731
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500732 rules = []
Soniya Vyasbbc9dd32021-03-23 16:06:29 -0500733 sec_group_rules_client = security_group_rules_client
734 for ruleset in rulesets:
735 for r_direction in ['ingress', 'egress']:
736 ruleset['direction'] = r_direction
737 try:
738 sg_rule = self.create_security_group_rule(
739 sec_group_rules_client=sec_group_rules_client,
740 secgroup=secgroup,
741 security_groups_client=security_groups_client,
742 **ruleset)
743 except lib_exc.Conflict as ex:
744 # if rule already exist - skip rule and continue
745 msg = 'Security group rule already exists'
746 if msg not in ex._error_string:
747 raise ex
748 else:
749 self.assertEqual(r_direction, sg_rule['direction'])
750 rules.append(sg_rule)
751
752 return rules
753
jskundad7445982023-10-18 13:49:02 +0200754 def create_and_add_security_group_to_server(self, server):
755 """Create a security group and add it to the server.
756
757 :param server: The server to add the security group to.
758 :return: The security group was added to the server.
759 """
760
761 secgroup = self.create_security_group()
762 self.servers_client.add_security_group(server['id'],
763 name=secgroup['name'])
764 self.addCleanup(self.servers_client.remove_security_group,
765 server['id'], name=secgroup['name'])
766
767 def wait_for_secgroup_add():
768 body = (self.servers_client.show_server(server['id'])
769 ['server'])
770 return {'name': secgroup['name']} in body['security_groups']
771
772 if not test_utils.call_until_true(wait_for_secgroup_add,
773 CONF.compute.build_timeout,
774 CONF.compute.build_interval):
775 msg = ('Timed out waiting for adding security group %s to server '
776 '%s' % (secgroup['id'], server['id']))
777 raise lib_exc.TimeoutException(msg)
778
zhuflf52c7592017-05-25 13:55:24 +0800779 def get_remote_client(self, ip_address, username=None, private_key=None,
780 server=None):
JordanP3fe2dc32014-11-17 13:06:01 +0100781 """Get a SSH client to a remote server
782
Sergey Vilgelmeac094a2018-11-21 18:27:51 -0600783 :param ip_address: the server floating or fixed IP address to use
784 for ssh validation
785 :param username: name of the Linux account on the remote server
786 :param private_key: the SSH private key to use
787 :param server: server dict, used for debugging purposes
788 :return: a RemoteClient object
JordanP3fe2dc32014-11-17 13:06:01 +0100789 """
Adam Gandelmanc78c7572014-08-28 18:38:55 -0700790
Andrea Frittoli247058f2014-07-16 16:09:22 +0100791 if username is None:
lanoux283273b2015-12-04 03:01:54 -0800792 username = CONF.validation.image_ssh_user
wantwatering896300c2015-03-27 15:17:42 +0800793 # Set this with 'keypair' or others to log in with keypair or
794 # username/password.
lanoux5fc14522015-09-21 08:17:35 +0000795 if CONF.validation.auth_method == 'keypair':
wantwatering896300c2015-03-27 15:17:42 +0800796 password = None
797 if private_key is None:
798 private_key = self.keypair['private_key']
799 else:
lanoux283273b2015-12-04 03:01:54 -0800800 password = CONF.validation.image_ssh_password
wantwatering896300c2015-03-27 15:17:42 +0800801 private_key = None
zhuflf52c7592017-05-25 13:55:24 +0800802 linux_client = remote_client.RemoteClient(
803 ip_address, username, pkey=private_key, password=password,
804 server=server, servers_client=self.servers_client)
Pavlo Shchelokovskyyd42faeb2023-08-09 13:51:48 +0300805 try:
806 linux_client.validate_authentication()
807 except Exception as e:
808 message = ('Initializing SSH connection to %(ip)s failed. '
809 'Error: %(error)s' % {'ip': ip_address,
810 'error': e})
811 caller = test_utils.find_test_caller()
812 if caller:
813 message = '(%s) %s' % (caller, message)
814 LOG.exception(message)
815 servers = (server,) if server else None
816 self.log_console_output(servers=servers)
817 raise
818
Andrea Frittoli247058f2014-07-16 16:09:22 +0100819 return linux_client
820
Lukas Piwowarskib50eabe2020-11-05 15:15:38 +0000821 def image_create(self, name='scenario-img', **kwargs):
Martin Kopec02af6a42020-03-03 12:39:12 +0000822 img_path = CONF.scenario.img_file
823 if not os.path.exists(img_path):
Martin Kopec008950e2020-09-29 08:12:39 +0000824 lib_exc.InvalidConfiguration(
Martin Kopec02af6a42020-03-03 12:39:12 +0000825 'Starting Tempest 25.0.0 release, CONF.scenario.img_file need '
826 'a full path for the image. CONF.scenario.img_dir was '
827 'deprecated and will be removed in the next release. Till '
Martin Kopec008950e2020-09-29 08:12:39 +0000828 'Tempest 25.0.0, old behavior was maintained and kept working '
Martin Kopec02af6a42020-03-03 12:39:12 +0000829 'but starting Tempest 26.0.0, you need to specify the full '
830 'path in CONF.scenario.img_file config option.')
Alessandro Pilottib7c1daa2014-08-16 14:24:13 +0300831 img_container_format = CONF.scenario.img_container_format
832 img_disk_format = CONF.scenario.img_disk_format
Evgeny Antyshev7ba0d5f2015-04-28 13:18:07 +0000833 img_properties = CONF.scenario.img_properties
PranaliD2aa523c2016-06-07 03:54:34 -0400834 LOG.debug("paths: img: %s, container_format: %s, disk_format: %s, "
Martin Kopec02af6a42020-03-03 12:39:12 +0000835 "properties: %s",
Jordan Pittier525ec712016-12-07 17:51:26 +0100836 img_path, img_container_format, img_disk_format,
Martin Kopec02af6a42020-03-03 12:39:12 +0000837 img_properties)
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530838 if img_properties is None:
839 img_properties = {}
Martin Kopec213d0a42023-11-30 10:28:14 +0100840 name = data_utils.rand_name(
841 prefix=CONF.resource_name_prefix, name='%s-' % name)
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530842 params = {
843 'name': name,
844 'container_format': img_container_format,
845 'disk_format': img_disk_format or img_container_format,
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700846 'visibility': 'private'
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530847 }
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700848 # Additional properties are flattened out in the v2 API.
849 if img_properties:
850 params.update(img_properties)
Lukas Piwowarskib50eabe2020-11-05 15:15:38 +0000851 params.update(kwargs)
melanie witta0b161b2023-12-01 23:41:44 +0000852
853 # This code is basically copying the devstack code that extracts and
854 # uploads split kernel/ramdisk images.
855 if tarfile.is_tarfile(img_path):
856 extract_dir = os.path.join(tempfile.gettempdir(), 'images', name)
857 self.addCleanup(shutil.rmtree, extract_dir)
858 os.makedirs(extract_dir)
859 with tarfile.open(img_path) as tar:
860 tar.extractall(extract_dir, filter='data')
861 filenames = os.listdir(extract_dir)
862 for fname in filenames:
863 if re.search(r'(.*-vmlinuz.*|aki-.*/image$)', fname):
864 kernel_img_path = os.path.join(extract_dir, fname)
865 elif re.search(r'(.*-initrd.*|ari-.*/image$)', fname):
866 ramdisk_img_path = os.path.join(extract_dir, fname)
Takashi Kajinami397f49c2024-01-25 20:32:34 +0900867 elif re.search(r'(.*\\.img$|ami-.*/image$)', fname):
melanie witta0b161b2023-12-01 23:41:44 +0000868 img_path = os.path.join(extract_dir, fname)
869 # Create the kernel image.
870 kparams = {
871 'name': name + '-kernel',
872 'container_format': 'aki',
873 'disk_format': 'aki',
874 'visibility': 'private'
875 }
876 body = self.image_client.create_image(**kparams)
877 image = body['image'] if 'image' in body else body
878 kernel_id = image['id']
879 self.addCleanup(self.image_client.delete_image, kernel_id)
880 self.assertEqual("queued", image['status'])
881 with open(kernel_img_path, 'rb') as image_file:
882 self.image_client.store_image_file(kernel_id, image_file)
883 LOG.debug("image:%s", kernel_id)
884 # Create the ramdisk image.
885 rparams = {
886 'name': name + '-ramdisk',
887 'container_format': 'ari',
888 'disk_format': 'ari',
889 'visibility': 'private'
890 }
891 body = self.image_client.create_image(**rparams)
892 image = body['image'] if 'image' in body else body
893 ramdisk_id = image['id']
894 self.addCleanup(self.image_client.delete_image, ramdisk_id)
895 self.assertEqual("queued", image['status'])
896 with open(ramdisk_img_path, 'rb') as image_file:
897 self.image_client.store_image_file(ramdisk_id, image_file)
898 LOG.debug("image:%s", ramdisk_id)
899 # Set the kernel_id, ramdisk_id, container format, disk format for
900 # the split image.
901 params['kernel_id'] = kernel_id
902 params['ramdisk_id'] = ramdisk_id
903 params['container_format'] = 'ami'
904 params['disk_format'] = 'ami'
905
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530906 body = self.image_client.create_image(**params)
907 image = body['image'] if 'image' in body else body
908 self.addCleanup(self.image_client.delete_image, image['id'])
909 self.assertEqual("queued", image['status'])
910 with open(img_path, 'rb') as image_file:
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700911 self.image_client.store_image_file(image['id'], image_file)
Soniya Vyasbe8d5102020-08-17 17:23:30 +0530912 LOG.debug("image:%s", image['id'])
913 return image['id']
Andrea Frittoli247058f2014-07-16 16:09:22 +0100914
Soniya Vyas1b0cddc2021-01-29 17:28:19 +0530915 def log_console_output(self, servers=None, client=None, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530916 """Console log output"""
Matthew Treinish42a3f3a2014-09-04 15:04:53 -0400917 if not CONF.compute_feature_enabled.console_output:
918 LOG.debug('Console output not supported, cannot log')
919 return
Ihar Hrachyshkaa9dca2b2017-04-04 14:17:11 -0700920 client = client or self.servers_client
Andrea Frittoli247058f2014-07-16 16:09:22 +0100921 if not servers:
Ihar Hrachyshkaa9dca2b2017-04-04 14:17:11 -0700922 servers = client.list_servers()
Andrea Frittoli247058f2014-07-16 16:09:22 +0100923 servers = servers['servers']
924 for server in servers:
Attila Fazekas9a5a1122016-11-08 10:24:57 +0100925 try:
Ihar Hrachyshkaa9dca2b2017-04-04 14:17:11 -0700926 console_output = client.get_console_output(
Lukas Piwowarski91ded042020-10-29 15:15:25 +0000927 server['id'], **kwargs)['output']
Attila Fazekas9a5a1122016-11-08 10:24:57 +0100928 LOG.debug('Console output for %s\nbody=\n%s',
929 server['id'], console_output)
930 except lib_exc.NotFound:
Attila Fazekase1360482016-11-10 11:28:08 +0100931 LOG.debug("Server %s disappeared(deleted) while looking "
Attila Fazekas9a5a1122016-11-08 10:24:57 +0100932 "for the console log", server['id'])
Andrea Frittoli247058f2014-07-16 16:09:22 +0100933
Ken'ichi Ohmichi6e201f52014-10-01 04:21:39 +0000934 def _log_net_info(self, exc):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530935 """network debug is called as part of ssh init"""
Andrey Pavlov64723762015-04-29 06:24:58 +0300936 if not isinstance(exc, lib_exc.SSHTimeout):
Ken'ichi Ohmichi6e201f52014-10-01 04:21:39 +0000937 LOG.debug('Network information on a devstack host')
Ken'ichi Ohmichi6e201f52014-10-01 04:21:39 +0000938
melanie witt370bea42023-08-22 01:43:31 +0000939 def get_snapshot_id(self, bdms):
940 if isinstance(bdms, str):
941 bdms = json.loads(bdms)
942 snapshot_id = None
943 for bdm in bdms:
944 # Look for the block device mapping that actually has a
945 # snapshot. If the server has ephemeral or swap disk, their
946 # block device mappings will be present with snapshot_id = None
947 if 'snapshot_id' in bdm and bdm['snapshot_id'] is not None:
948 snapshot_id = bdm['snapshot_id']
949 break
950 return snapshot_id
951
Lukas Piwowarski9ad9ca22020-10-29 15:36:30 +0000952 def create_server_snapshot(self, server, name=None, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +0530953 """Creates server snapshot"""
nithya-ganesan882595e2014-07-29 18:51:07 +0000954 # Glance client
955 _image_client = self.image_client
956 # Compute client
Ghanshyamae76c122015-12-22 13:41:35 +0900957 _images_client = self.compute_images_client
nithya-ganesan882595e2014-07-29 18:51:07 +0000958 if name is None:
Martin Kopec213d0a42023-11-30 10:28:14 +0100959 name = data_utils.rand_name(
960 prefix=CONF.resource_name_prefix,
961 name=self.__class__.__name__ + 'snapshot')
nithya-ganesan882595e2014-07-29 18:51:07 +0000962 LOG.debug("Creating a snapshot image for server: %s", server['name'])
Lukas Piwowarski9ad9ca22020-10-29 15:36:30 +0000963 image = _images_client.create_image(server['id'], name=name, **kwargs)
Benny Kopilov7d2edc22022-06-30 17:22:14 +0300964 # microversion 2.45 and above returns image_id
965 image_id = image.get('image_id') or image.response['location'].split(
966 'images/')[1]
Yaroslav Lobankov2fea4052016-04-19 15:05:57 +0300967 waiters.wait_for_image_status(_image_client, image_id, 'active')
Jordan Pittierf672b7d2016-06-20 18:50:40 +0200968
969 self.addCleanup(_image_client.wait_for_resource_deletion,
970 image_id)
971 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
972 _image_client.delete_image, image_id)
973
Ghanshyam Mann3562cd02023-08-05 17:22:03 -0700974 # In glance v2 the additional properties are flattened.
975 snapshot_image = _image_client.show_image(image_id)
976 image_props = snapshot_image
Andrey Pavlovc8bd4b12015-08-17 10:20:17 +0300977
melanie witt370bea42023-08-22 01:43:31 +0000978 bdms = image_props.get('block_device_mapping')
979 if bdms:
980 snapshot_id = self.get_snapshot_id(bdms)
981 self.assertIsNotNone(snapshot_id)
982 self.addCleanup(
983 self.snapshots_client.wait_for_resource_deletion,
984 snapshot_id)
985 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
986 self.snapshots_client.delete_snapshot,
987 snapshot_id)
988 waiters.wait_for_volume_resource_status(
989 self.snapshots_client, snapshot_id, 'available')
990
nithya-ganesan882595e2014-07-29 18:51:07 +0000991 image_name = snapshot_image['name']
992 self.assertEqual(name, image_name)
993 LOG.debug("Created snapshot image %s for server %s",
994 image_name, server['name'])
995 return snapshot_image
996
Milana Levye9a58a12023-02-21 12:55:20 +0000997 def nova_volume_attach(self, server, volume_to_attach,
998 volumes_client=None, servers_client=None,
999 **kwargs):
Soniya Vyasae631132020-08-28 13:37:12 +05301000 """Compute volume attach
1001
1002 This utility attaches volume from compute and waits for the
1003 volume status to be 'in-use' state.
1004 """
Milana Levye9a58a12023-02-21 12:55:20 +00001005 if volumes_client is None:
1006 volumes_client = self.volumes_client
1007 if servers_client is None:
1008 servers_client = self.servers_client
1009
1010 volume = servers_client.attach_volume(
Lukas Piwowarski76819fa2020-10-29 13:46:07 +00001011 server['id'], volumeId=volume_to_attach['id'],
1012 **kwargs)['volumeAttachment']
Jordan Pittier7cf64762015-10-14 15:01:12 +02001013 self.assertEqual(volume_to_attach['id'], volume['id'])
Milana Levye9a58a12023-02-21 12:55:20 +00001014 waiters.wait_for_volume_resource_status(volumes_client,
lkuchlan52d7b0d2016-11-07 20:53:19 +02001015 volume['id'], 'in-use')
Lukas Piwowarski76819fa2020-10-29 13:46:07 +00001016 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Milana Levye9a58a12023-02-21 12:55:20 +00001017 self.nova_volume_detach, server, volume,
1018 servers_client)
Jordan Pittier7cf64762015-10-14 15:01:12 +02001019 # Return the updated volume after the attachment
Milana Levye9a58a12023-02-21 12:55:20 +00001020 return volumes_client.show_volume(volume['id'])['volume']
Masayuki Igawa1f0ad632014-08-05 13:36:56 +09001021
Milana Levye9a58a12023-02-21 12:55:20 +00001022 def nova_volume_detach(self, server, volume, servers_client=None):
Soniya Vyasae631132020-08-28 13:37:12 +05301023 """Compute volume detach
1024
Lee Yarwood5423c532020-12-17 11:24:46 +00001025 This utility detaches the volume from the server and checks whether the
1026 volume attachment has been removed from Nova.
Soniya Vyasae631132020-08-28 13:37:12 +05301027 """
Milana Levye9a58a12023-02-21 12:55:20 +00001028 if servers_client is None:
1029 servers_client = self.servers_client
1030
1031 servers_client.detach_volume(server['id'], volume['id'])
Lee Yarwood5423c532020-12-17 11:24:46 +00001032 waiters.wait_for_volume_attachment_remove_from_server(
Milana Levye9a58a12023-02-21 12:55:20 +00001033 servers_client, server['id'], volume['id'])
Jordan Pittier7cf64762015-10-14 15:01:12 +02001034
Steven Hardyda2a8352014-10-02 12:52:20 +01001035 def ping_ip_address(self, ip_address, should_succeed=True,
zhufl0ec74c42017-11-15 14:02:28 +08001036 ping_timeout=None, mtu=None, server=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301037 """ping ip address"""
lanoux5fc14522015-09-21 08:17:35 +00001038 timeout = ping_timeout or CONF.validation.ping_timeout
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +00001039 cmd = ['ping', '-c1', '-w1']
1040
1041 if mtu:
1042 cmd += [
1043 # don't fragment
1044 '-M', 'do',
1045 # ping receives just the size of ICMP payload
1046 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
1047 ]
1048 cmd.append(ip_address)
Aaron Rosena7df13b2014-09-23 09:45:45 -07001049
1050 def ping():
1051 proc = subprocess.Popen(cmd,
1052 stdout=subprocess.PIPE,
1053 stderr=subprocess.PIPE)
1054 proc.communicate()
Shuquan Huang753629e2015-07-20 08:52:29 +00001055
Aaron Rosena7df13b2014-09-23 09:45:45 -07001056 return (proc.returncode == 0) == should_succeed
1057
Jordan Pittier9e227c52016-02-09 14:35:18 +01001058 caller = test_utils.find_test_caller()
Shuquan Huang753629e2015-07-20 08:52:29 +00001059 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
John L. Villalovosa898aec2017-01-13 14:46:46 -08001060 ' expected result is %(should_succeed)s', {
Shuquan Huang753629e2015-07-20 08:52:29 +00001061 'caller': caller, 'ip': ip_address, 'timeout': timeout,
1062 'should_succeed':
1063 'reachable' if should_succeed else 'unreachable'
1064 })
Jordan Pittier35a63752016-08-30 13:09:12 +02001065 result = test_utils.call_until_true(ping, timeout, 1)
Shuquan Huang753629e2015-07-20 08:52:29 +00001066 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
John L. Villalovosa898aec2017-01-13 14:46:46 -08001067 'ping result is %(result)s', {
Shuquan Huang753629e2015-07-20 08:52:29 +00001068 'caller': caller, 'ip': ip_address, 'timeout': timeout,
1069 'result': 'expected' if result else 'unexpected'
1070 })
zhufl0ec74c42017-11-15 14:02:28 +08001071 if server:
Soniya Vyas1b0cddc2021-01-29 17:28:19 +05301072 self.log_console_output([server])
Shuquan Huang753629e2015-07-20 08:52:29 +00001073 return result
Aaron Rosena7df13b2014-09-23 09:45:45 -07001074
Yair Friedae0e73d2014-11-24 11:56:26 +02001075 def check_vm_connectivity(self, ip_address,
1076 username=None,
1077 private_key=None,
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +00001078 should_connect=True,
zhufl0ec74c42017-11-15 14:02:28 +08001079 extra_msg="",
1080 server=None,
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +00001081 mtu=None):
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001082 """Check server connectivity
1083
Yair Friedae0e73d2014-11-24 11:56:26 +02001084 :param ip_address: server to test against
1085 :param username: server's ssh username
1086 :param private_key: server's ssh private key to be used
1087 :param should_connect: True/False indicates positive/negative test
1088 positive - attempt ping and ssh
1089 negative - attempt ping and fail if succeed
zhufl0ec74c42017-11-15 14:02:28 +08001090 :param extra_msg: Message to help with debugging if ``ping_ip_address``
1091 fails
1092 :param server: The server whose console to log for debugging
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +00001093 :param mtu: network MTU to use for connectivity validation
Yair Friedae0e73d2014-11-24 11:56:26 +02001094
1095 :raises: AssertError if the result of the connectivity check does
1096 not match the value of the should_connect param
1097 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301098
zhufl0ec74c42017-11-15 14:02:28 +08001099 LOG.debug('checking network connections to IP %s with user: %s',
1100 ip_address, username)
Yair Friedae0e73d2014-11-24 11:56:26 +02001101 if should_connect:
1102 msg = "Timed out waiting for %s to become reachable" % ip_address
1103 else:
1104 msg = "ip address %s is reachable" % ip_address
zhufl0ec74c42017-11-15 14:02:28 +08001105 if extra_msg:
1106 msg = "%s\n%s" % (extra_msg, msg)
Yair Friedae0e73d2014-11-24 11:56:26 +02001107 self.assertTrue(self.ping_ip_address(ip_address,
Ihar Hrachyshkaf9227c02016-09-15 11:16:47 +00001108 should_succeed=should_connect,
zhufl0ec74c42017-11-15 14:02:28 +08001109 mtu=mtu, server=server),
Yair Friedae0e73d2014-11-24 11:56:26 +02001110 msg=msg)
1111 if should_connect:
1112 # no need to check ssh for negative connectivity
zhufl0ec74c42017-11-15 14:02:28 +08001113 try:
1114 self.get_remote_client(ip_address, username, private_key,
1115 server=server)
1116 except Exception:
1117 if not extra_msg:
1118 extra_msg = 'Failed to ssh to %s' % ip_address
1119 LOG.exception(extra_msg)
1120 raise
Yair Friedae0e73d2014-11-24 11:56:26 +02001121
Ghanshyam Mann64281392021-03-24 18:48:38 -05001122 def get_server_port_id_and_ip4(self, server, ip_addr=None, **kwargs):
Yair Friedae0e73d2014-11-24 11:56:26 +02001123
Ghanshyam Mann64281392021-03-24 18:48:38 -05001124 if ip_addr and not kwargs.get('fixed_ips'):
1125 kwargs['fixed_ips'] = 'ip_address=%s' % ip_addr
Lukas Piwowarskif759bc12020-11-05 10:51:29 +00001126
Ghanshyam Mann64281392021-03-24 18:48:38 -05001127 # A port can have more than one IP address in some cases.
1128 # If the network is dual-stack (IPv4 + IPv6), this port is associated
1129 # with 2 subnets
1130
1131 def _is_active(port):
1132 # NOTE(vsaienko) With Ironic, instances live on separate hardware
1133 # servers. Neutron does not bind ports for Ironic instances, as a
1134 # result the port remains in the DOWN state. This has been fixed
1135 # with the introduction of the networking-baremetal plugin but
1136 # it's not mandatory (and is not used on all stable branches).
1137 return (port['status'] == 'ACTIVE' or
1138 port.get('binding:vnic_type') == 'baremetal')
1139
Brian Haleyd6437c92024-09-06 16:09:26 -04001140 # Wait for all compute ports to be ACTIVE.
1141 # This will raise a TimeoutException if that does not happen.
1142 client = self.os_admin.ports_client
1143 try:
1144 ports = waiters.wait_for_server_ports_active(
1145 client=client, server_id=server['id'],
1146 is_active=_is_active, **kwargs)
1147 except lib_exc.TimeoutException:
1148 LOG.error("Server ports failed transitioning to ACTIVE for "
1149 "server: %s", server)
1150 raise
1151
Ghanshyam Mann64281392021-03-24 18:48:38 -05001152 port_map = [(p["id"], fxip["ip_address"])
1153 for p in ports
1154 for fxip in p["fixed_ips"]
1155 if (netutils.is_valid_ipv4(fxip["ip_address"]) and
1156 _is_active(p))]
1157 inactive = [p for p in ports if p['status'] != 'ACTIVE']
1158 if inactive:
Brian Haleyd6437c92024-09-06 16:09:26 -04001159 # This should just be Ironic ports, see _is_active() above
1160 LOG.debug("Instance has ports that are not ACTIVE: %s", inactive)
Ghanshyam Mann64281392021-03-24 18:48:38 -05001161
1162 self.assertNotEmpty(port_map,
1163 "No IPv4 addresses found in: %s" % ports)
1164 self.assertEqual(len(port_map), 1,
1165 "Found multiple IPv4 addresses: %s. "
1166 "Unable to determine which port to target."
1167 % port_map)
1168 return port_map[0]
1169
1170 def create_floating_ip(self, server, external_network_id=None,
1171 port_id=None, client=None, **kwargs):
1172 """Create a floating IP and associates to a resource/port on Neutron"""
1173
1174 if not external_network_id:
1175 external_network_id = CONF.network.public_network_id
1176 if not client:
1177 client = self.floating_ips_client
1178 if not port_id:
1179 port_id, ip4 = self.get_server_port_id_and_ip4(server)
1180 else:
1181 ip4 = None
1182
1183 floatingip_kwargs = {
1184 'floating_network_id': external_network_id,
1185 'port_id': port_id,
1186 'tenant_id': server.get('project_id') or server['tenant_id'],
1187 'fixed_ip_address': ip4,
1188 }
1189 if CONF.network.subnet_id:
1190 floatingip_kwargs['subnet_id'] = CONF.network.subnet_id
1191
1192 floatingip_kwargs.update(kwargs)
1193 result = client.create_floatingip(**floatingip_kwargs)
1194 floating_ip = result['floatingip']
1195
Jordan Pittier9e227c52016-02-09 14:35:18 +01001196 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Ghanshyam Mann64281392021-03-24 18:48:38 -05001197 client.delete_floatingip,
Yair Friedae0e73d2014-11-24 11:56:26 +02001198 floating_ip['id'])
Ghanshyam Mann64281392021-03-24 18:48:38 -05001199 return floating_ip
1200
Dan Smith49c2b3b2023-04-26 15:52:22 -07001201 def get_floating_ip(self, server):
1202 """Attempt to get an existing floating ip or a server
1203
1204 If one exists, return it, else return None
1205 """
1206 port_id, ip4 = self.get_server_port_id_and_ip4(server)
1207 ips = self.floating_ips_client.list_floatingips(
1208 floating_network_id=CONF.network.public_network_id,
1209 port_id=port_id)
1210 try:
1211 return ips['floatingips'][0]['floating_ip_address']
1212 except (KeyError, IndexError):
1213 return None
1214
lkuchlane1145a32024-12-09 22:40:59 +02001215 def associate_floating_ip(self, floating_ip, server, ip_addr=None,
1216 **kwargs):
Ghanshyam Mann64281392021-03-24 18:48:38 -05001217 """Associate floating ip to server
1218
1219 This wrapper utility attaches the floating_ip for
1220 the respective port_id of server
1221 """
lkuchlane1145a32024-12-09 22:40:59 +02001222 port_id, _ = self.get_server_port_id_and_ip4(server, ip_addr=ip_addr)
1223 kwargs.update({"port_id": port_id})
Ghanshyam Mann64281392021-03-24 18:48:38 -05001224 floating_ip = self.floating_ips_client.update_floatingip(
1225 floating_ip['id'], **kwargs)['floatingip']
1226 self.assertEqual(port_id, floating_ip['port_id'])
1227 return floating_ip
1228
1229 def disassociate_floating_ip(self, floating_ip):
1230 """Disassociates floating ip
1231
1232 This wrapper utility disassociates given floating ip.
1233 :param floating_ip: a dict which is a return value of
1234 floating_ips_client.create_floatingip method
1235 """
1236 kwargs = dict(port_id=None)
1237 floating_ip = self.floating_ips_client.update_floatingip(
1238 floating_ip['id'], **kwargs)['floatingip']
1239 self.assertIsNone(floating_ip['port_id'])
Yair Friedae0e73d2014-11-24 11:56:26 +02001240 return floating_ip
1241
jskundad7445982023-10-18 13:49:02 +02001242 def create_file(self, ip_address, path, private_key=None, server=None,
1243 username=None):
1244 """Create a file on a remote server"""
1245 ssh_client = self.get_remote_client(ip_address,
1246 private_key=private_key,
1247 server=server,
1248 username=username)
1249 ssh_client.exec_command('sudo mkdir -p %s' % path)
1250
Sean Dague20e98612016-01-06 14:33:28 -05001251 def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
Lukas Piwowarski25f7ba22020-10-29 14:01:34 +00001252 private_key=None, server=None, username=None,
Dan Smith30eb8782023-08-02 09:35:01 -07001253 fs='vfat'):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301254 """Creates timestamp
1255
1256 This wrapper utility does ssh, creates timestamp and returns the
1257 created timestamp.
1258 """
Sean Dague20e98612016-01-06 14:33:28 -05001259 ssh_client = self.get_remote_client(ip_address,
Slawek Kaplonski79d8b0f2018-07-30 22:43:41 +02001260 private_key=private_key,
Lukas Piwowarski25f7ba22020-10-29 14:01:34 +00001261 server=server,
1262 username=username)
1263
melanie witt2da632a2023-05-31 03:25:06 +00001264 # Default the directory in which to write the timestamp file to /tmp
1265 # and only use the mount_path as the target directory if we mounted
1266 # dev_name to mount_path.
Martin Kopec9086dca2024-03-20 13:18:57 +01001267 target_dir = CONF.scenario.target_dir
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001268 if dev_name is not None:
Takashi Kajinami8280f432024-05-20 18:44:53 +09001269 mount_path = os.path.join(mount_path, dev_name)
Lukas Piwowarski25f7ba22020-10-29 14:01:34 +00001270 ssh_client.make_fs(dev_name, fs=fs)
Takashi Kajinami8280f432024-05-20 18:44:53 +09001271 ssh_client.mkdir(mount_path)
1272 ssh_client.mount(dev_name, mount_path)
melanie witt2da632a2023-05-31 03:25:06 +00001273 target_dir = mount_path
Takashi Kajinami8280f432024-05-20 18:44:53 +09001274
melanie witt2da632a2023-05-31 03:25:06 +00001275 cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % target_dir
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001276 ssh_client.exec_command(cmd_timestamp)
1277 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
melanie witt2da632a2023-05-31 03:25:06 +00001278 % target_dir)
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001279 if dev_name is not None:
Takashi Kajinami8280f432024-05-20 18:44:53 +09001280 ssh_client.umount(mount_path)
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001281 return timestamp
1282
Sean Dague20e98612016-01-06 14:33:28 -05001283 def get_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
Lukas Piwowarski2c230eb2020-10-30 10:09:18 +00001284 private_key=None, server=None, username=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301285 """Returns timestamp
1286
1287 This wrapper utility does ssh and returns the timestamp.
Lukas Piwowarski2c230eb2020-10-30 10:09:18 +00001288
1289 :param ip_address: The floating IP or fixed IP of the remote server
1290 :param dev_name: Name of the device that stores the timestamp
1291 :param mount_path: Path which should be used as mount point for
1292 dev_name
1293 :param private_key: The SSH private key to use for authentication
1294 :param server: Server dict, used for debugging purposes
1295 :param username: Name of the Linux account on the remote server
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301296 """
1297
Sean Dague20e98612016-01-06 14:33:28 -05001298 ssh_client = self.get_remote_client(ip_address,
Slawek Kaplonski79d8b0f2018-07-30 22:43:41 +02001299 private_key=private_key,
Lukas Piwowarski2c230eb2020-10-30 10:09:18 +00001300 server=server,
1301 username=username)
1302
melanie witt2da632a2023-05-31 03:25:06 +00001303 # Default the directory from which to read the timestamp file to /tmp
1304 # and only use the mount_path as the target directory if we mounted
1305 # dev_name to mount_path.
Martin Kopec9086dca2024-03-20 13:18:57 +01001306 target_dir = CONF.scenario.target_dir
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001307 if dev_name is not None:
Takashi Kajinami8280f432024-05-20 18:44:53 +09001308 mount_path = os.path.join(mount_path, dev_name)
1309 ssh_client.mkdir(mount_path)
Matt Riedemann076685a2015-09-30 14:38:16 -07001310 ssh_client.mount(dev_name, mount_path)
melanie witt2da632a2023-05-31 03:25:06 +00001311 target_dir = mount_path
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001312 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
melanie witt2da632a2023-05-31 03:25:06 +00001313 % target_dir)
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001314 if dev_name is not None:
Takashi Kajinami8280f432024-05-20 18:44:53 +09001315 ssh_client.umount(mount_path)
Alexander Gubanovabd154c2015-09-23 23:24:06 +03001316 return timestamp
1317
Lukas Piwowarskib0642f92020-10-29 14:51:30 +00001318 def get_server_ip(self, server, **kwargs):
Sean Dague20e98612016-01-06 14:33:28 -05001319 """Get the server fixed or floating IP.
1320
1321 Based on the configuration we're in, return a correct ip
1322 address for validating that a guest is up.
Lukas Piwowarskib0642f92020-10-29 14:51:30 +00001323
1324 If CONF.validation.connect_method is floating, then
1325 a floating ip will be created passing kwargs as additional
1326 argument.
Sean Dague20e98612016-01-06 14:33:28 -05001327 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301328
Alexander Gubanovc8829f82015-11-12 10:35:13 +02001329 if CONF.validation.connect_method == 'floating':
Sean Dague20e98612016-01-06 14:33:28 -05001330 # The tests calling this method don't have a floating IP
zhufl0892cb22016-05-06 14:46:00 +08001331 # and can't make use of the validation resources. So the
Sean Dague20e98612016-01-06 14:33:28 -05001332 # method is creating the floating IP there.
Dan Smith49c2b3b2023-04-26 15:52:22 -07001333 fip = self.get_floating_ip(server)
1334 if fip:
1335 # Already have a floating ip, so use it instead of creating
1336 # another
1337 return fip
1338 else:
1339 return self.create_floating_ip(
1340 server, **kwargs)['floating_ip_address']
Sean Dague20e98612016-01-06 14:33:28 -05001341 elif CONF.validation.connect_method == 'fixed':
Matt Riedemanna7782552016-08-08 16:26:01 -04001342 # Determine the network name to look for based on config or creds
1343 # provider network resources.
1344 if CONF.validation.network_for_ssh:
1345 addresses = server['addresses'][
1346 CONF.validation.network_for_ssh]
1347 else:
zhufl7b4a7202017-09-28 10:29:27 +08001348 network = self.get_tenant_network()
Matt Riedemanna7782552016-08-08 16:26:01 -04001349 addresses = (server['addresses'][network['name']]
1350 if network else [])
Sean Dague20e98612016-01-06 14:33:28 -05001351 for address in addresses:
Federico Ressi2d6bcaa2018-04-11 12:37:36 +02001352 if (address['version'] == CONF.validation.ip_version_for_ssh and # noqa
1353 address['OS-EXT-IPS:type'] == 'fixed'):
Sean Dague20e98612016-01-06 14:33:28 -05001354 return address['addr']
zhufl955f82b2016-07-22 11:14:34 +08001355 raise exceptions.ServerUnreachable(server_id=server['id'])
Alexander Gubanovc8829f82015-11-12 10:35:13 +02001356 else:
Matthew Treinish4217a702016-10-07 17:27:11 -04001357 raise lib_exc.InvalidConfiguration()
Alexander Gubanovc8829f82015-11-12 10:35:13 +02001358
zhufl7bc916d2018-08-22 14:47:39 +08001359 @classmethod
1360 def get_host_for_server(cls, server_id):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301361 """Gets host of server"""
1362
zhufl7bc916d2018-08-22 14:47:39 +08001363 server_details = cls.os_admin.servers_client.show_server(server_id)
1364 return server_details['server']['OS-EXT-SRV-ATTR:host']
1365
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001366 def _get_bdm(self, source_id, source_type, delete_on_termination=False):
1367 bd_map_v2 = [{
1368 'uuid': source_id,
1369 'source_type': source_type,
1370 'destination_type': 'volume',
1371 'boot_index': 0,
1372 'delete_on_termination': delete_on_termination}]
1373 return {'block_device_mapping_v2': bd_map_v2}
1374
1375 def boot_instance_from_resource(self, source_id,
1376 source_type,
1377 keypair=None,
1378 security_group=None,
1379 delete_on_termination=False,
Martin Kopecbee673e2020-11-04 09:40:52 +00001380 name=None, **kwargs):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301381 """Boot instance from resource
1382
1383 This wrapper utility boots instance from resource with block device
1384 mapping with source info passed in arguments
1385 """
1386
Martin Kopecbee673e2020-11-04 09:40:52 +00001387 create_kwargs = dict({'image_id': ''})
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001388 if keypair:
Dan Smith49c2b3b2023-04-26 15:52:22 -07001389 create_kwargs['keypair'] = keypair
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001390 if security_group:
1391 create_kwargs['security_groups'] = [
1392 {'name': security_group['name']}]
1393 create_kwargs.update(self._get_bdm(
1394 source_id,
1395 source_type,
1396 delete_on_termination=delete_on_termination))
1397 if name:
1398 create_kwargs['name'] = name
Martin Kopecbee673e2020-11-04 09:40:52 +00001399 create_kwargs.update(kwargs)
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001400
Martin Kopecbee673e2020-11-04 09:40:52 +00001401 return self.create_server(**create_kwargs)
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001402
Martin Kopec0216b372020-11-04 09:32:05 +00001403 def create_volume_from_image(self, **kwargs):
1404 """Create volume from image.
1405
1406 :param image_id: ID of the image to create volume from,
1407 CONF.compute.image_ref by default
1408 :param name: name of the volume,
1409 '$classname-volume-origin' by default
1410 :param **kwargs: additional parameters
1411 """
1412 image_id = kwargs.pop('image_id', CONF.compute.image_ref)
1413 name = kwargs.pop('name', None)
1414 if not name:
1415 namestart = self.__class__.__name__ + '-volume-origin'
Martin Kopec213d0a42023-11-30 10:28:14 +01001416 name = data_utils.rand_name(
1417 prefix=CONF.resource_name_prefix, name=namestart)
Martin Kopec0216b372020-11-04 09:32:05 +00001418 return self.create_volume(name=name, imageRef=image_id, **kwargs)
Rajat Dhasmanaa8bfa172020-01-14 17:34:44 +00001419
Andrea Frittoli2e733b52014-07-16 14:12:11 +01001420
Arefiev Antonc45249d2022-07-27 15:11:55 +03001421class ScenarioTestWithNetwork(ScenarioTest):
1422 """Base class for tests with default network"""
1423
1424 @classmethod
1425 def setup_credentials(cls):
1426 cls.set_network_resources(network=True, subnet=True,
1427 dhcp=True, router=True)
1428 super(ScenarioTestWithNetwork, cls).setup_credentials()
1429
1430
Andrea Frittoli4971fc82014-09-25 10:22:20 +01001431class NetworkScenarioTest(ScenarioTest):
Yair Fried1fc32a12014-08-04 09:11:30 +03001432 """Base class for network scenario tests.
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001433
Yair Fried1fc32a12014-08-04 09:11:30 +03001434 This class provide helpers for network scenario tests, using the neutron
1435 API. Helpers from ancestor which use the nova network API are overridden
1436 with the neutron API.
1437
1438 This Class also enforces using Neutron instead of novanetwork.
1439 Subclassed tests will be skipped if Neutron is not enabled
1440
1441 """
1442
1443 @classmethod
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001444 def skip_checks(cls):
1445 super(NetworkScenarioTest, cls).skip_checks()
Andrea Frittoli2ddc2632014-09-25 11:03:00 +01001446 if not CONF.service_available.neutron:
1447 raise cls.skipException('Neutron not available')
Yair Fried1fc32a12014-08-04 09:11:30 +03001448
Soniya Vyas3bdafd82021-02-22 18:59:27 +05301449 def create_network(self, networks_client=None,
1450 project_id=None,
1451 namestart='network-smoke-',
1452 port_security_enabled=True, **net_dict):
John Warren94d8faf2015-09-15 12:22:24 -04001453 if not networks_client:
1454 networks_client = self.networks_client
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001455 if not project_id:
1456 project_id = networks_client.project_id
Martin Kopec213d0a42023-11-30 10:28:14 +01001457 name = data_utils.rand_name(
1458 prefix=CONF.resource_name_prefix, name=namestart)
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001459 network_kwargs = dict(name=name, project_id=project_id)
Lajos Katonac87a06b2019-01-04 13:21:48 +01001460 if net_dict:
1461 network_kwargs.update(net_dict)
Matt Riedemann039b2fe2016-09-15 16:12:24 -04001462 # Neutron disables port security by default so we have to check the
1463 # config before trying to create the network with port_security_enabled
1464 if CONF.network_feature_enabled.port_security:
1465 network_kwargs['port_security_enabled'] = port_security_enabled
Markus Zoeller156b5da2016-07-11 18:10:31 +02001466 result = networks_client.create_network(**network_kwargs)
Steve Heyman33735f22016-05-24 09:28:08 -05001467 network = result['network']
1468
1469 self.assertEqual(network['name'], name)
Jordan Pittier9e227c52016-02-09 14:35:18 +01001470 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
zhoubin508bf20b32017-02-03 09:39:14 +08001471 networks_client.delete_network,
Steve Heyman33735f22016-05-24 09:28:08 -05001472 network['id'])
Yair Fried1fc32a12014-08-04 09:11:30 +03001473 return network
1474
zhufl5b0a52f2017-10-24 15:48:20 +08001475 def create_subnet(self, network, subnets_client=None,
1476 namestart='subnet-smoke', **kwargs):
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001477 """Create a subnet for the given network
1478
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301479 This utility creates subnet for the given network
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001480 within the cidr block configured for tenant networks.
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301481
1482 :param **kwargs:
1483 See extra parameters below
1484
1485 :Keyword Arguments:
1486
1487 * *ip_version = ip version of the given network,
Soniya Vyas795ef252020-12-10 19:07:23 +05301488 use_default_subnetpool = default subnetpool to
1489 manage IPv6 addresses range.
Yair Fried1fc32a12014-08-04 09:11:30 +03001490 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301491
John Warren3961acd2015-10-02 14:38:53 -04001492 if not subnets_client:
1493 subnets_client = self.subnets_client
Yair Fried1fc32a12014-08-04 09:11:30 +03001494
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001495 def cidr_in_use(cidr, project_id):
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001496 """Check cidr existence
1497
yangjianfeng4ad346e2020-11-22 06:49:19 +00001498 :returns: True if subnet with cidr already exist in tenant or
1499 external False else
Yair Fried1fc32a12014-08-04 09:11:30 +03001500 """
yangjianfeng4ad346e2020-11-22 06:49:19 +00001501 tenant_subnets = self.os_admin.subnets_client.list_subnets(
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001502 project_id=project_id, cidr=cidr)['subnets']
yangjianfeng4ad346e2020-11-22 06:49:19 +00001503 external_nets = self.os_admin.networks_client.list_networks(
1504 **{"router:external": True})['networks']
1505 external_subnets = []
1506 for ext_net in external_nets:
1507 external_subnets.extend(
1508 self.os_admin.subnets_client.list_subnets(
1509 network_id=ext_net['id'], cidr=cidr)['subnets'])
1510 return len(tenant_subnets + external_subnets) != 0
Yair Fried1fc32a12014-08-04 09:11:30 +03001511
Soniya Vyas795ef252020-12-10 19:07:23 +05301512 def _make_create_subnet_request(namestart, network,
1513 ip_version, subnets_client, **kwargs):
Yair Fried1fc32a12014-08-04 09:11:30 +03001514
1515 subnet = dict(
Martin Kopec213d0a42023-11-30 10:28:14 +01001516 name=data_utils.rand_name(
1517 prefix=CONF.resource_name_prefix, name=namestart),
Steve Heyman33735f22016-05-24 09:28:08 -05001518 network_id=network['id'],
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001519 project_id=network['project_id'],
Kirill Shileev14113572014-11-21 16:58:02 +03001520 ip_version=ip_version,
Yair Fried1fc32a12014-08-04 09:11:30 +03001521 **kwargs
1522 )
Soniya Vyas795ef252020-12-10 19:07:23 +05301523
1524 if ip_version == 6:
1525 subnet['ipv6_address_mode'] = 'slaac'
1526 subnet['ipv6_ra_mode'] = 'slaac'
1527
Yair Fried1fc32a12014-08-04 09:11:30 +03001528 try:
Soniya Vyas795ef252020-12-10 19:07:23 +05301529 return subnets_client.create_subnet(**subnet)
Masayuki Igawad9388762015-01-20 14:56:42 +09001530 except lib_exc.Conflict as e:
Soniya Vyas795ef252020-12-10 19:07:23 +05301531 if 'overlaps with another subnet' not in str(e):
Yair Fried1fc32a12014-08-04 09:11:30 +03001532 raise
Soniya Vyas795ef252020-12-10 19:07:23 +05301533
1534 result = None
1535 str_cidr = None
1536
1537 use_default_subnetpool = kwargs.get('use_default_subnetpool', False)
1538 ip_version = kwargs.pop('ip_version', 4)
1539
1540 if not use_default_subnetpool:
1541
1542 if ip_version == 6:
1543 tenant_cidr = netaddr.IPNetwork(
1544 CONF.network.project_network_v6_cidr)
1545 num_bits = CONF.network.project_network_v6_mask_bits
1546 else:
1547 tenant_cidr = netaddr.IPNetwork(
1548 CONF.network.project_network_cidr)
1549 num_bits = CONF.network.project_network_mask_bits
1550
1551 # Repeatedly attempt subnet creation with sequential cidr
1552 # blocks until an unallocated block is found.
1553 for subnet_cidr in tenant_cidr.subnet(num_bits):
1554 str_cidr = str(subnet_cidr)
1555 if cidr_in_use(str_cidr, project_id=network['project_id']):
1556 continue
1557 result = _make_create_subnet_request(
1558 namestart, network, ip_version, subnets_client,
1559 cidr=str_cidr, **kwargs)
1560
1561 if result is not None:
1562 break
1563
1564 else:
1565 result = _make_create_subnet_request(
1566 namestart, network, ip_version, subnets_client,
1567 **kwargs)
Yair Fried1fc32a12014-08-04 09:11:30 +03001568 self.assertIsNotNone(result, 'Unable to allocate tenant network')
Steve Heyman33735f22016-05-24 09:28:08 -05001569
1570 subnet = result['subnet']
Soniya Vyas795ef252020-12-10 19:07:23 +05301571 if str_cidr is not None:
1572 self.assertEqual(subnet['cidr'], str_cidr)
Steve Heyman33735f22016-05-24 09:28:08 -05001573
1574 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1575 subnets_client.delete_subnet, subnet['id'])
1576
Yair Fried1fc32a12014-08-04 09:11:30 +03001577 return subnet
1578
Soniya Vyasc37410f2021-02-24 15:26:27 +05301579 def get_network_by_name(self, network_name):
jeremy.zhang5870ff12017-05-25 11:24:23 +08001580 net = self.os_admin.networks_client.list_networks(
Jordan Pittier64e6b442017-02-20 19:29:02 +01001581 name=network_name)['networks']
Ferenc Horváth268ccce2017-06-08 12:39:02 +02001582 self.assertNotEmpty(net,
Adam Gandelman878a5fd2015-03-30 14:33:36 -07001583 "Unable to get network by name: %s" % network_name)
Steve Heyman33735f22016-05-24 09:28:08 -05001584 return net[0]
David Shrewsbury9bac3662014-08-07 15:07:01 -04001585
Yair Fried45f92952014-06-26 05:19:19 +03001586 def check_floating_ip_status(self, floating_ip, status):
Carl Baldwina754e2d2014-10-23 22:47:41 +00001587 """Verifies floatingip reaches the given status
Yair Fried45f92952014-06-26 05:19:19 +03001588
Steve Heyman33735f22016-05-24 09:28:08 -05001589 :param dict floating_ip: floating IP dict to check status
Yair Fried45f92952014-06-26 05:19:19 +03001590 :param status: target status
1591 :raises: AssertionError if status doesn't match
1592 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301593
Steve Heyman33735f22016-05-24 09:28:08 -05001594 floatingip_id = floating_ip['id']
1595
Carl Baldwina754e2d2014-10-23 22:47:41 +00001596 def refresh():
Martin Kopecf4b5df62020-01-27 09:44:29 +00001597 floating_ip = (self.floating_ips_client.
1598 show_floatingip(floatingip_id)['floatingip'])
1599 if status == floating_ip['status']:
Takashi Kajinami397f49c2024-01-25 20:32:34 +09001600 LOG.info("FloatingIP: %s is at status: %s",
1601 floating_ip, status)
Martin Kopecf4b5df62020-01-27 09:44:29 +00001602 return status == floating_ip['status']
Carl Baldwina754e2d2014-10-23 22:47:41 +00001603
zhufl4dda94e2017-03-14 16:14:46 +08001604 if not test_utils.call_until_true(refresh,
1605 CONF.network.build_timeout,
1606 CONF.network.build_interval):
1607 floating_ip = self.floating_ips_client.show_floatingip(
1608 floatingip_id)['floatingip']
1609 self.assertEqual(status, floating_ip['status'],
1610 message="FloatingIP: {fp} is at status: {cst}. "
1611 "failed to reach status: {st}"
1612 .format(fp=floating_ip, cst=floating_ip['status'],
1613 st=status))
Yair Fried45f92952014-06-26 05:19:19 +03001614
zhufl420a0192017-09-28 11:04:50 +08001615 def check_tenant_network_connectivity(self, server,
1616 username,
1617 private_key,
1618 should_connect=True,
1619 servers_for_debug=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301620 """Checks tenant network connectivity"""
Sean Dagueed6e5862016-04-04 10:49:13 -04001621 if not CONF.network.project_networks_reachable:
Yair Fried1fc32a12014-08-04 09:11:30 +03001622 msg = 'Tenant networks not configured to be reachable.'
1623 LOG.info(msg)
1624 return
1625 # The target login is assumed to have been configured for
1626 # key-based authentication by cloud-init.
1627 try:
Béla Vancsicsb6dfa082017-03-01 10:44:58 +01001628 for ip_addresses in server['addresses'].values():
Yair Fried1fc32a12014-08-04 09:11:30 +03001629 for ip_address in ip_addresses:
ghanshyam807211c2014-12-18 13:21:22 +09001630 self.check_vm_connectivity(ip_address['addr'],
Yair Friedae0e73d2014-11-24 11:56:26 +02001631 username,
1632 private_key,
1633 should_connect=should_connect)
Yair Fried1fc32a12014-08-04 09:11:30 +03001634 except Exception as e:
1635 LOG.exception('Tenant network connectivity check failed')
Soniya Vyas1b0cddc2021-01-29 17:28:19 +05301636 self.log_console_output(servers_for_debug)
Ken'ichi Ohmichi6e201f52014-10-01 04:21:39 +00001637 self._log_net_info(e)
Yair Fried1fc32a12014-08-04 09:11:30 +03001638 raise
1639
zhufle9877c62017-10-13 09:38:19 +08001640 def check_remote_connectivity(self, source, dest, should_succeed=True,
Claudiu Belu33c3e602014-08-28 16:38:01 +03001641 nic=None, protocol='icmp'):
1642 """check server connectivity via source ssh connection
YAMAMOTO Takashi4c3ebb02017-01-25 16:04:30 +09001643
Claudiu Belu33c3e602014-08-28 16:38:01 +03001644 :param source: RemoteClient: an ssh connection from which to execute
1645 the check
1646 :param dest: an IP to check connectivity against
1647 :param should_succeed: boolean should connection succeed or not
1648 :param nic: specific network interface to test connectivity from
1649 :param protocol: the protocol used to test connectivity with.
1650 :returns: True, if the connection succeeded and it was expected to
1651 succeed. False otherwise.
Yair Fried1fc32a12014-08-04 09:11:30 +03001652 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301653
Claudiu Belu33c3e602014-08-28 16:38:01 +03001654 method_name = '%s_check' % protocol
1655 connectivity_checker = getattr(source, method_name)
1656
1657 def connect_remote():
Yair Fried1fc32a12014-08-04 09:11:30 +03001658 try:
Claudiu Belu33c3e602014-08-28 16:38:01 +03001659 connectivity_checker(dest, nic=nic)
Andrey Pavlov64723762015-04-29 06:24:58 +03001660 except lib_exc.SSHExecCommandFailed:
Claudiu Belu33c3e602014-08-28 16:38:01 +03001661 LOG.warning('Failed to check %(protocol)s connectivity for '
1662 'IP %(dest)s via a ssh connection from: %(src)s.',
1663 dict(protocol=protocol, dest=dest,
1664 src=source.ssh_client.host))
Yair Fried1fc32a12014-08-04 09:11:30 +03001665 return not should_succeed
1666 return should_succeed
1667
Claudiu Belu33c3e602014-08-28 16:38:01 +03001668 result = test_utils.call_until_true(connect_remote,
zhufle9877c62017-10-13 09:38:19 +08001669 CONF.validation.ping_timeout, 1)
Ihar Hrachyshkaf9fda2d2017-11-06 13:16:09 -08001670 if result:
1671 return
1672
YAMAMOTO Takashi4c3ebb02017-01-25 16:04:30 +09001673 source_host = source.ssh_client.host
1674 if should_succeed:
1675 msg = "Timed out waiting for %s to become reachable from %s" \
1676 % (dest, source_host)
1677 else:
1678 msg = "%s is reachable from %s" % (dest, source_host)
Soniya Vyas1b0cddc2021-01-29 17:28:19 +05301679 self.log_console_output()
Ihar Hrachyshkaf9fda2d2017-11-06 13:16:09 -08001680 self.fail(msg)
YAMAMOTO Takashi4c3ebb02017-01-25 16:04:30 +09001681
Soniya Vyas73555df2021-03-04 19:05:42 +05301682 def get_router(self, client=None, project_id=None, **kwargs):
Yair Fried1fc32a12014-08-04 09:11:30 +03001683 """Retrieve a router for the given tenant id.
1684
1685 If a public router has been configured, it will be returned.
1686
1687 If a public router has not been configured, but a public
1688 network has, a tenant router will be created and returned that
1689 routes traffic to the public network.
1690 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301691
Yair Frieddb6c9e92014-08-06 08:53:13 +03001692 if not client:
Ken'ichi Ohmichie35f4722015-12-22 04:57:11 +00001693 client = self.routers_client
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001694 if not project_id:
1695 project_id = client.project_id
Yair Fried1fc32a12014-08-04 09:11:30 +03001696 router_id = CONF.network.public_router_id
1697 network_id = CONF.network.public_network_id
1698 if router_id:
David Kranzca4c7e72015-05-27 11:39:19 -04001699 body = client.show_router(router_id)
Steve Heyman33735f22016-05-24 09:28:08 -05001700 return body['router']
Yair Fried1fc32a12014-08-04 09:11:30 +03001701 elif network_id:
Martin Kopec0090a102020-11-03 13:50:19 +00001702 name = kwargs.pop('name', None)
1703 if not name:
1704 namestart = self.__class__.__name__ + '-router'
Martin Kopec213d0a42023-11-30 10:28:14 +01001705 name = data_utils.rand_name(
1706 prefix=CONF.resource_name_prefix, name=namestart)
Martin Kopec0090a102020-11-03 13:50:19 +00001707
1708 ext_gw_info = kwargs.pop('external_gateway_info', None)
1709 if not ext_gw_info:
1710 ext_gw_info = dict(network_id=network_id)
zhufl3484f992017-10-10 16:18:29 +08001711 router = client.create_router(
Martin Kopec0090a102020-11-03 13:50:19 +00001712 name=name,
1713 admin_state_up=kwargs.get('admin_state_up', True),
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001714 project_id=project_id,
Martin Kopec0090a102020-11-03 13:50:19 +00001715 external_gateway_info=ext_gw_info,
1716 **kwargs)['router']
zhufl3484f992017-10-10 16:18:29 +08001717 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1718 client.delete_router, router['id'])
Yair Fried1fc32a12014-08-04 09:11:30 +03001719 return router
1720 else:
1721 raise Exception("Neither of 'public_router_id' or "
1722 "'public_network_id' has been defined.")
1723
Ghanshyam Mann071d1542021-03-24 19:10:47 -05001724 def setup_network_subnet_with_router(
1725 self, networks_client=None,
1726 routers_client=None, subnets_client=None,
1727 project_id=None, dns_nameservers=None,
1728 port_security_enabled=True, **net_dict):
Yair Fried1fc32a12014-08-04 09:11:30 +03001729 """Create a network with a subnet connected to a router.
1730
David Shrewsbury9bac3662014-08-07 15:07:01 -04001731 The baremetal driver is a special case since all nodes are
1732 on the same shared network.
1733
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001734 :param project_id: id of project to create resources in.
Yair Fried413bf2d2014-11-19 17:07:11 +02001735 :param dns_nameservers: list of dns servers to send to subnet.
Lajos Katonac87a06b2019-01-04 13:21:48 +01001736 :param port_security_enabled: whether or not port_security is enabled
elajkate453fc22019-06-13 15:03:43 +02001737 :param net_dict: a dict containing experimental network information in
Lajos Katonac87a06b2019-01-04 13:21:48 +01001738 a form like this: {'provider:network_type': 'vlan',
1739 'provider:physical_network': 'foo',
1740 'provider:segmentation_id': '42'}
Yair Fried1fc32a12014-08-04 09:11:30 +03001741 :returns: network, subnet, router
1742 """
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301743
Thiago Paiva66cded22016-08-15 14:55:58 -03001744 if CONF.network.shared_physical_network:
David Shrewsbury9bac3662014-08-07 15:07:01 -04001745 # NOTE(Shrews): This exception is for environments where tenant
1746 # credential isolation is available, but network separation is
1747 # not (the current baremetal case). Likely can be removed when
1748 # test account mgmt is reworked:
1749 # https://blueprints.launchpad.net/tempest/+spec/test-accounts
Adam Gandelman878a5fd2015-03-30 14:33:36 -07001750 if not CONF.compute.fixed_network_name:
1751 m = 'fixed_network_name must be specified in config'
Matthew Treinish4217a702016-10-07 17:27:11 -04001752 raise lib_exc.InvalidConfiguration(m)
Soniya Vyasc37410f2021-02-24 15:26:27 +05301753 network = self.get_network_by_name(
David Shrewsbury9bac3662014-08-07 15:07:01 -04001754 CONF.compute.fixed_network_name)
1755 router = None
1756 subnet = None
1757 else:
Soniya Vyas3bdafd82021-02-22 18:59:27 +05301758 network = self.create_network(
Ken'ichi Ohmichi43e7fcf2016-04-04 11:59:13 -07001759 networks_client=networks_client,
Rodolfo Alonso Hernandezc1449d42020-02-15 13:24:28 +00001760 project_id=project_id,
Lajos Katonac87a06b2019-01-04 13:21:48 +01001761 port_security_enabled=port_security_enabled,
1762 **net_dict)
Soniya Vyas73555df2021-03-04 19:05:42 +05301763 router = self.get_router(client=routers_client,
1764 project_id=project_id)
Ken'ichi Ohmichi43e7fcf2016-04-04 11:59:13 -07001765 subnet_kwargs = dict(network=network,
zhufl5b0a52f2017-10-24 15:48:20 +08001766 subnets_client=subnets_client)
Yair Fried413bf2d2014-11-19 17:07:11 +02001767 # use explicit check because empty list is a valid option
1768 if dns_nameservers is not None:
1769 subnet_kwargs['dns_nameservers'] = dns_nameservers
zhufl5b0a52f2017-10-24 15:48:20 +08001770 subnet = self.create_subnet(**subnet_kwargs)
Steve Heyman33735f22016-05-24 09:28:08 -05001771 if not routers_client:
1772 routers_client = self.routers_client
1773 router_id = router['id']
1774 routers_client.add_router_interface(router_id,
1775 subnet_id=subnet['id'])
1776
1777 # save a cleanup job to remove this association between
1778 # router and subnet
1779 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1780 routers_client.remove_router_interface, router_id,
1781 subnet_id=subnet['id'])
Yair Fried1fc32a12014-08-04 09:11:30 +03001782 return network, subnet, router
1783
1784
Arefiev Antonc45249d2022-07-27 15:11:55 +03001785class EncryptionScenarioTest(ScenarioTestWithNetwork):
1786
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001787 """Base class for encryption scenario tests"""
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001788
David Kranz4cc852b2015-03-09 14:57:11 -04001789 @classmethod
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001790 def setup_clients(cls):
1791 super(EncryptionScenarioTest, cls).setup_clients()
ghanshyam6c682ff2018-08-06 09:54:45 +00001792 cls.admin_volume_types_client = cls.os_admin.volume_types_client_latest
ghanshyam3bd0d2b2017-03-23 01:57:28 +00001793 cls.admin_encryption_types_client =\
ghanshyam6c682ff2018-08-06 09:54:45 +00001794 cls.os_admin.encryption_types_client_latest
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001795
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001796 def create_encryption_type(self, client=None, type_id=None, provider=None,
1797 key_size=None, cipher=None,
1798 control_location=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301799 """Creates an encryption type for volume"""
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001800 if not client:
Ken'ichi Ohmichia6ebf622016-08-25 11:52:27 -07001801 client = self.admin_encryption_types_client
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001802 if not type_id:
1803 volume_type = self.create_volume_type()
Masayuki Igawa1f0ad632014-08-05 13:36:56 +09001804 type_id = volume_type['id']
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001805 LOG.debug("Creating an encryption type for volume type: %s", type_id)
Masayuki Igawa1f0ad632014-08-05 13:36:56 +09001806 client.create_encryption_type(
1807 type_id, provider=provider, key_size=key_size, cipher=cipher,
jeremy.zhangb6f67f62018-02-11 09:28:52 +08001808 control_location=control_location)
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001809
lkuchlan3023e752017-06-08 12:53:13 +03001810 def create_encrypted_volume(self, encryption_provider, volume_type,
1811 key_size=256, cipher='aes-xts-plain64',
Ghanshyam Mann51c0f9a2023-07-21 14:09:40 -05001812 control_location='front-end',
1813 wait_until='available'):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301814 """Creates an encrypted volume"""
lkuchlan3023e752017-06-08 12:53:13 +03001815 volume_type = self.create_volume_type(name=volume_type)
1816 self.create_encryption_type(type_id=volume_type['id'],
1817 provider=encryption_provider,
1818 key_size=key_size,
1819 cipher=cipher,
1820 control_location=control_location)
Ghanshyam Mann51c0f9a2023-07-21 14:09:40 -05001821 return self.create_volume(volume_type=volume_type['name'],
1822 wait_until=wait_until)
lkuchlan3023e752017-06-08 12:53:13 +03001823
Kaitlin Farr366a51f2014-04-21 12:43:54 -04001824
Masayuki Igawa0870db52015-09-18 21:08:36 +09001825class ObjectStorageScenarioTest(ScenarioTest):
Ken'ichi Ohmichic4e4f1c2015-11-17 08:16:12 +00001826 """Provide harness to do Object Storage scenario tests.
Chris Dent0d494112014-08-26 13:48:30 +01001827
1828 Subclasses implement the tests that use the methods provided by this
1829 class.
1830 """
1831
Ghanshyam Mann64281392021-03-24 18:48:38 -05001832 credentials = ['primary']
1833
Chris Dent0d494112014-08-26 13:48:30 +01001834 @classmethod
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001835 def skip_checks(cls):
Masayuki Igawa0870db52015-09-18 21:08:36 +09001836 super(ObjectStorageScenarioTest, cls).skip_checks()
Chris Dent0d494112014-08-26 13:48:30 +01001837 if not CONF.service_available.swift:
1838 skip_msg = ("%s skipped as swift is not available" %
1839 cls.__name__)
1840 raise cls.skipException(skip_msg)
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001841
1842 @classmethod
1843 def setup_credentials(cls):
Masayuki Igawa60ea6c52014-10-15 17:32:14 +09001844 cls.set_network_resources()
Masayuki Igawa0870db52015-09-18 21:08:36 +09001845 super(ObjectStorageScenarioTest, cls).setup_credentials()
Matthew Treinish4a596932015-03-06 20:37:01 -05001846 operator_role = CONF.object_storage.operator_role
Andrea Frittoli (andreaf)737fac92015-05-12 16:14:35 +01001847 cls.os_operator = cls.get_client_manager(roles=[operator_role])
Emily Hugenbruch5e2d2a22015-02-25 21:35:45 +00001848
1849 @classmethod
1850 def setup_clients(cls):
Masayuki Igawa0870db52015-09-18 21:08:36 +09001851 super(ObjectStorageScenarioTest, cls).setup_clients()
Chris Dent0d494112014-08-26 13:48:30 +01001852 # Clients for Swift
Matthew Treinish8f268292015-02-24 20:01:36 -05001853 cls.account_client = cls.os_operator.account_client
1854 cls.container_client = cls.os_operator.container_client
1855 cls.object_client = cls.os_operator.object_client
Chris Dent0d494112014-08-26 13:48:30 +01001856
Chris Dentde456a12014-09-10 12:41:15 +01001857 def get_swift_stat(self):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301858 """Get swift status for our user account."""
Chris Dent0d494112014-08-26 13:48:30 +01001859 self.account_client.list_account_containers()
1860 LOG.debug('Swift status information obtained successfully')
1861
Chris Dentde456a12014-09-10 12:41:15 +01001862 def create_container(self, container_name=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301863 """Creates container"""
Chris Dent0d494112014-08-26 13:48:30 +01001864 name = container_name or data_utils.rand_name(
Martin Kopec213d0a42023-11-30 10:28:14 +01001865 prefix=CONF.resource_name_prefix, name='swift-scenario-container')
ghanshyameed40312017-09-15 18:30:04 +03001866 self.container_client.update_container(name)
Chris Dent0d494112014-08-26 13:48:30 +01001867 # look for the container to assure it is created
Chris Dentde456a12014-09-10 12:41:15 +01001868 self.list_and_check_container_objects(name)
Jordan Pittier525ec712016-12-07 17:51:26 +01001869 LOG.debug('Container %s created', name)
Jordan Pittier9e227c52016-02-09 14:35:18 +01001870 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Chris Dent1d4313a2014-10-28 12:16:48 +00001871 self.container_client.delete_container,
1872 name)
Chris Dent0d494112014-08-26 13:48:30 +01001873 return name
1874
Chris Dentde456a12014-09-10 12:41:15 +01001875 def delete_container(self, container_name):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301876 """Deletes container"""
Chris Dent0d494112014-08-26 13:48:30 +01001877 self.container_client.delete_container(container_name)
Jordan Pittier525ec712016-12-07 17:51:26 +01001878 LOG.debug('Container %s deleted', container_name)
Chris Dent0d494112014-08-26 13:48:30 +01001879
Chris Dentde456a12014-09-10 12:41:15 +01001880 def upload_object_to_container(self, container_name, obj_name=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301881 """Uploads object to container"""
Martin Kopec213d0a42023-11-30 10:28:14 +01001882 obj_name = obj_name or data_utils.rand_name(
1883 prefix=CONF.resource_name_prefix, name='swift-scenario-object')
Jordan Pittierb84f2d42016-12-21 19:02:15 +01001884 obj_data = data_utils.random_bytes()
Chris Dent0d494112014-08-26 13:48:30 +01001885 self.object_client.create_object(container_name, obj_name, obj_data)
Jordan Pittier9e227c52016-02-09 14:35:18 +01001886 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Chris Dent1d4313a2014-10-28 12:16:48 +00001887 self.object_client.delete_object,
1888 container_name,
1889 obj_name)
Chris Dent0d494112014-08-26 13:48:30 +01001890 return obj_name, obj_data
1891
Chris Dentde456a12014-09-10 12:41:15 +01001892 def delete_object(self, container_name, filename):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301893 """Deletes object"""
Chris Dent0d494112014-08-26 13:48:30 +01001894 self.object_client.delete_object(container_name, filename)
Chris Dentde456a12014-09-10 12:41:15 +01001895 self.list_and_check_container_objects(container_name,
1896 not_present_obj=[filename])
Chris Dent0d494112014-08-26 13:48:30 +01001897
Chris Dentde456a12014-09-10 12:41:15 +01001898 def list_and_check_container_objects(self, container_name,
1899 present_obj=None,
1900 not_present_obj=None):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301901 """List and verify objects for a given container
1902
1903 This utility lists objects for a given container
1904 and asserts which are present and
1905 which are not
1906 """
1907
Ghanshyam2a180b82014-06-16 13:54:22 +09001908 if present_obj is None:
1909 present_obj = []
1910 if not_present_obj is None:
1911 not_present_obj = []
ghanshyam871b1a82017-09-14 02:56:16 +03001912 _, object_list = self.container_client.list_container_objects(
Chris Dent0d494112014-08-26 13:48:30 +01001913 container_name)
1914 if present_obj:
1915 for obj in present_obj:
1916 self.assertIn(obj, object_list)
1917 if not_present_obj:
1918 for obj in not_present_obj:
1919 self.assertNotIn(obj, object_list)
1920
Chris Dentde456a12014-09-10 12:41:15 +01001921 def download_and_verify(self, container_name, obj_name, expected_data):
Soniya Vyas0c84f3e2020-07-15 15:20:59 +05301922 """Asserts the object and expected data to verify if they are same"""
Chris Dent0d494112014-08-26 13:48:30 +01001923 _, obj = self.object_client.get_object(container_name, obj_name)
1924 self.assertEqual(obj, expected_data)