blob: 8a1860998de83055cf7cbd952af95810e605569b [file] [log] [blame]
Andrea Frittolif4510a12017-03-07 19:17:11 +00001# Copyright 2012 OpenStack Foundation
2# 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
Andrea Frittolif4510a12017-03-07 19:17:11 +000017import netaddr
18from oslo_log import log
Andrea Frittolif4510a12017-03-07 19:17:11 +000019from oslo_utils import netutils
Goutham Pacha Ravi37ee6772019-10-18 12:53:22 -070020from oslo_utils import uuidutils
Andrea Frittolif4510a12017-03-07 19:17:11 +000021from tempest.common import image as common_image
Andrea Frittolif4510a12017-03-07 19:17:11 +000022from tempest.common.utils.linux import remote_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000023from tempest import config
24from tempest import exceptions
Ken'ichi Ohmichi02d1f242017-03-12 18:56:27 -070025from tempest.lib.common.utils import data_utils
Andrea Frittolif4510a12017-03-07 19:17:11 +000026from tempest.lib.common.utils import test_utils
27from tempest.lib import exceptions as lib_exc
Roman Popelka290ef292022-02-28 10:41:04 +010028from tempest.scenario import manager
Andrea Frittolif4510a12017-03-07 19:17:11 +000029
30CONF = config.CONF
31
32LOG = log.getLogger(__name__)
33
34
Roman Popelka290ef292022-02-28 10:41:04 +010035class ScenarioTest(manager.ScenarioTest):
Andrea Frittolif4510a12017-03-07 19:17:11 +000036 """Base class for scenario tests. Uses tempest own clients. """
37
38 credentials = ['primary']
39
40 @classmethod
41 def setup_clients(cls):
42 super(ScenarioTest, cls).setup_clients()
43 # Clients (in alphabetical order)
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070044 cls.flavors_client = cls.os_primary.flavors_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000045 cls.compute_floating_ips_client = (
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070046 cls.os_primary.compute_floating_ips_client)
Andrea Frittolif4510a12017-03-07 19:17:11 +000047 if CONF.service_available.glance:
48 # Check if glance v1 is available to determine which client to use.
49 if CONF.image_feature_enabled.api_v1:
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070050 cls.image_client = cls.os_primary.image_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000051 elif CONF.image_feature_enabled.api_v2:
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070052 cls.image_client = cls.os_primary.image_client_v2
Andrea Frittolif4510a12017-03-07 19:17:11 +000053 else:
54 raise lib_exc.InvalidConfiguration(
55 'Either api_v1 or api_v2 must be True in '
56 '[image-feature-enabled].')
57 # Compute image client
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070058 cls.compute_images_client = cls.os_primary.compute_images_client
59 cls.keypairs_client = cls.os_primary.keypairs_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000060 # Nova security groups client
61 cls.compute_security_groups_client = (
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070062 cls.os_primary.compute_security_groups_client)
Andrea Frittolif4510a12017-03-07 19:17:11 +000063 cls.compute_security_group_rules_client = (
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070064 cls.os_primary.compute_security_group_rules_client)
65 cls.servers_client = cls.os_primary.servers_client
66 cls.interface_client = cls.os_primary.interfaces_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000067 # Neutron network client
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070068 cls.networks_client = cls.os_primary.networks_client
69 cls.ports_client = cls.os_primary.ports_client
70 cls.routers_client = cls.os_primary.routers_client
71 cls.subnets_client = cls.os_primary.subnets_client
72 cls.floating_ips_client = cls.os_primary.floating_ips_client
73 cls.security_groups_client = cls.os_primary.security_groups_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000074 cls.security_group_rules_client = (
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070075 cls.os_primary.security_group_rules_client)
Andrea Frittolif4510a12017-03-07 19:17:11 +000076
Andrea Frittolif4510a12017-03-07 19:17:11 +000077 # ## Test functions library
78 #
79 # The create_[resource] functions only return body and discard the
80 # resp part which is not used in scenario tests
81
Andrea Frittolif4510a12017-03-07 19:17:11 +000082 def _create_loginable_secgroup_rule(self, secgroup_id=None):
83 _client = self.compute_security_groups_client
84 _client_rules = self.compute_security_group_rules_client
85 if secgroup_id is None:
86 sgs = _client.list_security_groups()['security_groups']
87 for sg in sgs:
88 if sg['name'] == 'default':
89 secgroup_id = sg['id']
90
91 # These rules are intended to permit inbound ssh and icmp
92 # traffic from all sources, so no group_id is provided.
93 # Setting a group_id would only permit traffic from ports
94 # belonging to the same security group.
95 rulesets = [
96 {
97 # ssh
98 'ip_protocol': 'tcp',
99 'from_port': 22,
100 'to_port': 22,
101 'cidr': '0.0.0.0/0',
102 },
103 {
104 # ping
105 'ip_protocol': 'icmp',
106 'from_port': -1,
107 'to_port': -1,
108 'cidr': '0.0.0.0/0',
109 }
110 ]
111 rules = list()
112 for ruleset in rulesets:
113 sg_rule = _client_rules.create_security_group_rule(
114 parent_group_id=secgroup_id, **ruleset)['security_group_rule']
115 rules.append(sg_rule)
116 return rules
117
118 def _create_security_group(self):
119 # Create security group
120 sg_name = data_utils.rand_name(self.__class__.__name__)
121 sg_desc = sg_name + " description"
122 secgroup = self.compute_security_groups_client.create_security_group(
123 name=sg_name, description=sg_desc)['security_group']
124 self.assertEqual(secgroup['name'], sg_name)
125 self.assertEqual(secgroup['description'], sg_desc)
126 self.addCleanup(
127 test_utils.call_and_ignore_notfound_exc,
128 self.compute_security_groups_client.delete_security_group,
129 secgroup['id'])
130
131 # Add rules to the security group
132 self._create_loginable_secgroup_rule(secgroup['id'])
133
134 return secgroup
135
136 def get_remote_client(self, ip_address, username=None, private_key=None):
137 """Get a SSH client to a remote server
138
139 @param ip_address the server floating or fixed IP address to use
140 for ssh validation
141 @param username name of the Linux account on the remote server
142 @param private_key the SSH private key to use
143 @return a RemoteClient object
144 """
145
146 if username is None:
147 username = CONF.validation.image_ssh_user
148 # Set this with 'keypair' or others to log in with keypair or
149 # username/password.
150 if CONF.validation.auth_method == 'keypair':
151 password = None
152 if private_key is None:
153 private_key = self.keypair['private_key']
154 else:
155 password = CONF.validation.image_ssh_password
156 private_key = None
157 linux_client = remote_client.RemoteClient(ip_address, username,
158 pkey=private_key,
159 password=password)
160 try:
161 linux_client.validate_authentication()
162 except Exception as e:
163 message = ('Initializing SSH connection to %(ip)s failed. '
164 'Error: %(error)s' % {'ip': ip_address,
165 'error': e})
166 caller = test_utils.find_test_caller()
167 if caller:
168 message = '(%s) %s' % (caller, message)
169 LOG.exception(message)
Roman Popelka164898c2022-03-21 09:12:38 +0100170 self.log_console_output()
Andrea Frittolif4510a12017-03-07 19:17:11 +0000171 raise
172
173 return linux_client
174
175 def _image_create(self, name, fmt, path,
176 disk_format=None, properties=None):
177 if properties is None:
178 properties = {}
179 name = data_utils.rand_name('%s-' % name)
180 params = {
181 'name': name,
182 'container_format': fmt,
183 'disk_format': disk_format or fmt,
184 }
185 if CONF.image_feature_enabled.api_v1:
186 params['is_public'] = 'False'
187 params['properties'] = properties
188 params = {'headers': common_image.image_meta_to_headers(**params)}
189 else:
190 params['visibility'] = 'private'
191 # Additional properties are flattened out in the v2 API.
192 params.update(properties)
193 body = self.image_client.create_image(**params)
194 image = body['image'] if 'image' in body else body
195 self.addCleanup(self.image_client.delete_image, image['id'])
196 self.assertEqual("queued", image['status'])
197 with open(path, 'rb') as image_file:
198 if CONF.image_feature_enabled.api_v1:
199 self.image_client.update_image(image['id'], data=image_file)
200 else:
201 self.image_client.store_image_file(image['id'], image_file)
202 return image['id']
203
204 def glance_image_create(self):
Martin Kopec258cc6c2020-04-15 22:55:25 +0000205 img_path = CONF.scenario.img_file
Andrea Frittolif4510a12017-03-07 19:17:11 +0000206 img_container_format = CONF.scenario.img_container_format
207 img_disk_format = CONF.scenario.img_disk_format
208 img_properties = CONF.scenario.img_properties
209 LOG.debug("paths: img: %s, container_format: %s, disk_format: %s, "
Martin Kopec258cc6c2020-04-15 22:55:25 +0000210 "properties: %s",
Andrea Frittolif4510a12017-03-07 19:17:11 +0000211 img_path, img_container_format, img_disk_format,
Martin Kopec258cc6c2020-04-15 22:55:25 +0000212 img_properties)
213 image = self._image_create('scenario-img',
214 img_container_format,
215 img_path,
216 disk_format=img_disk_format,
217 properties=img_properties)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000218 LOG.debug("image:%s", image)
219
220 return image
221
Andrea Frittolif4510a12017-03-07 19:17:11 +0000222 def _log_net_info(self, exc):
223 # network debug is called as part of ssh init
224 if not isinstance(exc, lib_exc.SSHTimeout):
225 LOG.debug('Network information on a devstack host')
226
Andrea Frittolif4510a12017-03-07 19:17:11 +0000227 def check_vm_connectivity(self, ip_address,
228 username=None,
229 private_key=None,
230 should_connect=True,
231 mtu=None):
232 """Check server connectivity
233
234 :param ip_address: server to test against
235 :param username: server's ssh username
236 :param private_key: server's ssh private key to be used
237 :param should_connect: True/False indicates positive/negative test
238 positive - attempt ping and ssh
239 negative - attempt ping and fail if succeed
240 :param mtu: network MTU to use for connectivity validation
241
242 :raises: AssertError if the result of the connectivity check does
243 not match the value of the should_connect param
244 """
245 if should_connect:
246 msg = "Timed out waiting for %s to become reachable" % ip_address
247 else:
248 msg = "ip address %s is reachable" % ip_address
249 self.assertTrue(self.ping_ip_address(ip_address,
250 should_succeed=should_connect,
251 mtu=mtu),
252 msg=msg)
253 if should_connect:
254 # no need to check ssh for negative connectivity
255 self.get_remote_client(ip_address, username, private_key)
256
257 def check_public_network_connectivity(self, ip_address, username,
258 private_key, should_connect=True,
259 msg=None, servers=None, mtu=None):
260 # The target login is assumed to have been configured for
261 # key-based authentication by cloud-init.
262 LOG.debug('checking network connections to IP %s with user: %s',
263 ip_address, username)
264 try:
265 self.check_vm_connectivity(ip_address,
266 username,
267 private_key,
268 should_connect=should_connect,
269 mtu=mtu)
270 except Exception:
271 ex_msg = 'Public network connectivity check failed'
272 if msg:
273 ex_msg += ": " + msg
274 LOG.exception(ex_msg)
Roman Popelka164898c2022-03-21 09:12:38 +0100275 self.log_console_output(servers)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000276 raise
277
278 def create_floating_ip(self, thing, pool_name=None):
279 """Create a floating IP and associates to a server on Nova"""
280
281 if not pool_name:
282 pool_name = CONF.network.floating_network_name
283 floating_ip = (self.compute_floating_ips_client.
284 create_floating_ip(pool=pool_name)['floating_ip'])
285 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
286 self.compute_floating_ips_client.delete_floating_ip,
287 floating_ip['id'])
288 self.compute_floating_ips_client.associate_floating_ip_to_server(
289 floating_ip['ip'], thing['id'])
290 return floating_ip
291
292 def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
293 private_key=None):
294 ssh_client = self.get_remote_client(ip_address,
295 private_key=private_key)
296 if dev_name is not None:
297 ssh_client.make_fs(dev_name)
298 ssh_client.mount(dev_name, mount_path)
299 cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % mount_path
300 ssh_client.exec_command(cmd_timestamp)
301 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
302 % mount_path)
303 if dev_name is not None:
304 ssh_client.umount(mount_path)
305 return timestamp
306
307 def get_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
308 private_key=None):
309 ssh_client = self.get_remote_client(ip_address,
310 private_key=private_key)
311 if dev_name is not None:
312 ssh_client.mount(dev_name, mount_path)
313 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
314 % mount_path)
315 if dev_name is not None:
316 ssh_client.umount(mount_path)
317 return timestamp
318
319 def get_server_ip(self, server):
320 """Get the server fixed or floating IP.
321
322 Based on the configuration we're in, return a correct ip
323 address for validating that a guest is up.
324 """
325 if CONF.validation.connect_method == 'floating':
326 # The tests calling this method don't have a floating IP
327 # and can't make use of the validation resources. So the
328 # method is creating the floating IP there.
329 return self.create_floating_ip(server)['ip']
330 elif CONF.validation.connect_method == 'fixed':
331 # Determine the network name to look for based on config or creds
332 # provider network resources.
333 if CONF.validation.network_for_ssh:
334 addresses = server['addresses'][
335 CONF.validation.network_for_ssh]
336 else:
337 creds_provider = self._get_credentials_provider()
338 net_creds = creds_provider.get_primary_creds()
339 network = getattr(net_creds, 'network', None)
340 addresses = (server['addresses'][network['name']]
341 if network else [])
342 for address in addresses:
343 if (address['version'] == CONF.validation.ip_version_for_ssh
344 and address['OS-EXT-IPS:type'] == 'fixed'):
345 return address['addr']
346 raise exceptions.ServerUnreachable(server_id=server['id'])
347 else:
348 raise lib_exc.InvalidConfiguration()
349
350
351class NetworkScenarioTest(ScenarioTest):
352 """Base class for network scenario tests.
353
354 This class provide helpers for network scenario tests, using the neutron
355 API. Helpers from ancestor which use the nova network API are overridden
356 with the neutron API.
357
358 This Class also enforces using Neutron instead of novanetwork.
359 Subclassed tests will be skipped if Neutron is not enabled
360
361 """
362
363 credentials = ['primary', 'admin']
364
365 @classmethod
366 def skip_checks(cls):
367 super(NetworkScenarioTest, cls).skip_checks()
368 if not CONF.service_available.neutron:
369 raise cls.skipException('Neutron not available')
370
371 def _create_network(self, networks_client=None,
372 tenant_id=None,
373 namestart='network-smoke-',
374 port_security_enabled=True):
375 if not networks_client:
376 networks_client = self.networks_client
377 if not tenant_id:
378 tenant_id = networks_client.tenant_id
379 name = data_utils.rand_name(namestart)
380 network_kwargs = dict(name=name, tenant_id=tenant_id)
381 # Neutron disables port security by default so we have to check the
382 # config before trying to create the network with port_security_enabled
383 if CONF.network_feature_enabled.port_security:
384 network_kwargs['port_security_enabled'] = port_security_enabled
385 result = networks_client.create_network(**network_kwargs)
386 network = result['network']
387
388 self.assertEqual(network['name'], name)
389 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
390 networks_client.delete_network,
391 network['id'])
392 return network
393
394 def _create_subnet(self, network, subnets_client=None,
395 routers_client=None, namestart='subnet-smoke',
396 **kwargs):
397 """Create a subnet for the given network
398
399 within the cidr block configured for tenant networks.
400 """
401 if not subnets_client:
402 subnets_client = self.subnets_client
403 if not routers_client:
404 routers_client = self.routers_client
405
406 def cidr_in_use(cidr, tenant_id):
407 """Check cidr existence
408
409 :returns: True if subnet with cidr already exist in tenant
410 False else
411 """
Vu Cong Tuan99751862017-06-23 19:46:40 +0700412 cidr_in_use = self.os_admin.subnets_client.list_subnets(
Andrea Frittolif4510a12017-03-07 19:17:11 +0000413 tenant_id=tenant_id, cidr=cidr)['subnets']
414 return len(cidr_in_use) != 0
415
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200416 def _make_create_subnet_request(namestart, network,
417 ip_version, subnets_client, **kwargs):
Andrea Frittolif4510a12017-03-07 19:17:11 +0000418
419 subnet = dict(
420 name=data_utils.rand_name(namestart),
421 network_id=network['id'],
422 tenant_id=network['tenant_id'],
Andrea Frittolif4510a12017-03-07 19:17:11 +0000423 ip_version=ip_version,
424 **kwargs
425 )
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200426
427 if ip_version == 6:
428 subnet['ipv6_address_mode'] = 'slaac'
429 subnet['ipv6_ra_mode'] = 'slaac'
430
Andrea Frittolif4510a12017-03-07 19:17:11 +0000431 try:
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200432 return subnets_client.create_subnet(**subnet)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000433 except lib_exc.Conflict as e:
haixin48895812020-09-30 13:50:37 +0800434 if 'overlaps with another subnet' not in str(e):
Andrea Frittolif4510a12017-03-07 19:17:11 +0000435 raise
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200436
437 result = None
438 str_cidr = None
439
440 use_default_subnetpool = kwargs.get('use_default_subnetpool', False)
441
442 ip_version = kwargs.pop('ip_version', 4)
443
444 if not use_default_subnetpool:
445
446 if ip_version == 6:
447 tenant_cidr = netaddr.IPNetwork(
448 CONF.network.project_network_v6_cidr)
449 num_bits = CONF.network.project_network_v6_mask_bits
450 else:
451 tenant_cidr = netaddr.IPNetwork(
452 CONF.network.project_network_cidr)
453 num_bits = CONF.network.project_network_mask_bits
454
455 # Repeatedly attempt subnet creation with sequential cidr
456 # blocks until an unallocated block is found.
457 for subnet_cidr in tenant_cidr.subnet(num_bits):
458 str_cidr = str(subnet_cidr)
459 if cidr_in_use(str_cidr, tenant_id=network['tenant_id']):
460 continue
461
462 result = _make_create_subnet_request(
463 namestart, network, ip_version, subnets_client,
464 cidr=str_cidr, **kwargs)
465 if result is not None:
466 break
467 else:
468 result = _make_create_subnet_request(
469 namestart, network, ip_version, subnets_client,
470 **kwargs)
471
472 self.assertIsNotNone(result)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000473
474 subnet = result['subnet']
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200475 if str_cidr is not None:
476 self.assertEqual(subnet['cidr'], str_cidr)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000477
478 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
479 subnets_client.delete_subnet, subnet['id'])
480
481 return subnet
482
483 def _get_server_port_id_and_ip4(self, server, ip_addr=None):
Rockyd3432662019-07-02 14:58:30 +1000484 if ip_addr:
485 ports = self.os_admin.ports_client.list_ports(
486 device_id=server['id'],
487 fixed_ips='ip_address=%s' % ip_addr)['ports']
488 else:
489 ports = self.os_admin.ports_client.list_ports(
490 device_id=server['id'])['ports']
Andrea Frittolif4510a12017-03-07 19:17:11 +0000491 # A port can have more than one IP address in some cases.
492 # If the network is dual-stack (IPv4 + IPv6), this port is associated
493 # with 2 subnets
Rockyd3432662019-07-02 14:58:30 +1000494
495 def _is_active(port):
496 # NOTE(vsaienko) With Ironic, instances live on separate hardware
497 # servers. Neutron does not bind ports for Ironic instances, as a
498 # result the port remains in the DOWN state. This has been fixed
499 # with the introduction of the networking-baremetal plugin but
500 # it's not mandatory (and is not used on all stable branches).
501 return (port['status'] == 'ACTIVE' or
502 port.get('binding:vnic_type') == 'baremetal')
503
Andrea Frittolif4510a12017-03-07 19:17:11 +0000504 port_map = [(p["id"], fxip["ip_address"])
505 for p in ports
506 for fxip in p["fixed_ips"]
Rockyd3432662019-07-02 14:58:30 +1000507 if (netutils.is_valid_ipv4(fxip["ip_address"]) and
508 _is_active(p))]
Andrea Frittolif4510a12017-03-07 19:17:11 +0000509 inactive = [p for p in ports if p['status'] != 'ACTIVE']
510 if inactive:
511 LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
512
Rockyd3432662019-07-02 14:58:30 +1000513 self.assertNotEmpty(port_map,
Andrea Frittolif4510a12017-03-07 19:17:11 +0000514 "No IPv4 addresses found in: %s" % ports)
515 self.assertEqual(len(port_map), 1,
516 "Found multiple IPv4 addresses: %s. "
517 "Unable to determine which port to target."
518 % port_map)
519 return port_map[0]
520
Goutham Pacha Ravi37ee6772019-10-18 12:53:22 -0700521 def _get_network_by_name_or_id(self, identifier):
522
523 if uuidutils.is_uuid_like(identifier):
524 return self.os_admin.networks_client.show_network(
525 identifier)['network']
526
527 networks = self.os_admin.networks_client.list_networks(
528 name=identifier)['networks']
529 self.assertNotEqual(len(networks), 0,
530 "Unable to get network by name: %s" % identifier)
531 return networks[0]
532
533 def get_networks(self):
534 return self.os_admin.networks_client.list_networks()['networks']
Andrea Frittolif4510a12017-03-07 19:17:11 +0000535
536 def create_floating_ip(self, thing, external_network_id=None,
lkuchlan7636a1f2020-04-30 16:13:13 +0300537 port_id=None, ip_addr=None, client=None):
Andrea Frittolif4510a12017-03-07 19:17:11 +0000538 """Create a floating IP and associates to a resource/port on Neutron"""
539 if not external_network_id:
540 external_network_id = CONF.network.public_network_id
541 if not client:
542 client = self.floating_ips_client
543 if not port_id:
lkuchlan7636a1f2020-04-30 16:13:13 +0300544 port_id, ip4 = self._get_server_port_id_and_ip4(thing,
545 ip_addr=ip_addr)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000546 else:
547 ip4 = None
548 result = client.create_floatingip(
549 floating_network_id=external_network_id,
550 port_id=port_id,
551 tenant_id=thing['tenant_id'],
552 fixed_ip_address=ip4
553 )
554 floating_ip = result['floatingip']
555 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
556 client.delete_floatingip,
557 floating_ip['id'])
558 return floating_ip
559
560 def _associate_floating_ip(self, floating_ip, server):
561 port_id, _ = self._get_server_port_id_and_ip4(server)
562 kwargs = dict(port_id=port_id)
563 floating_ip = self.floating_ips_client.update_floatingip(
564 floating_ip['id'], **kwargs)['floatingip']
565 self.assertEqual(port_id, floating_ip['port_id'])
566 return floating_ip
567
568 def _disassociate_floating_ip(self, floating_ip):
569 """:param floating_ip: floating_ips_client.create_floatingip"""
570 kwargs = dict(port_id=None)
571 floating_ip = self.floating_ips_client.update_floatingip(
572 floating_ip['id'], **kwargs)['floatingip']
573 self.assertIsNone(floating_ip['port_id'])
574 return floating_ip
575
576 def check_floating_ip_status(self, floating_ip, status):
577 """Verifies floatingip reaches the given status
578
579 :param dict floating_ip: floating IP dict to check status
580 :param status: target status
581 :raises: AssertionError if status doesn't match
582 """
583 floatingip_id = floating_ip['id']
584
585 def refresh():
586 result = (self.floating_ips_client.
587 show_floatingip(floatingip_id)['floatingip'])
588 return status == result['status']
589
590 test_utils.call_until_true(refresh,
591 CONF.network.build_timeout,
592 CONF.network.build_interval)
593 floating_ip = self.floating_ips_client.show_floatingip(
594 floatingip_id)['floatingip']
595 self.assertEqual(status, floating_ip['status'],
596 message="FloatingIP: {fp} is at status: {cst}. "
597 "failed to reach status: {st}"
598 .format(fp=floating_ip, cst=floating_ip['status'],
599 st=status))
600 LOG.info("FloatingIP: {fp} is at status: {st}"
601 .format(fp=floating_ip, st=status))
602
603 def _check_tenant_network_connectivity(self, server,
604 username,
605 private_key,
606 should_connect=True,
607 servers_for_debug=None):
608 if not CONF.network.project_networks_reachable:
609 msg = 'Tenant networks not configured to be reachable.'
610 LOG.info(msg)
611 return
612 # The target login is assumed to have been configured for
613 # key-based authentication by cloud-init.
614 try:
615 for ip_addresses in server['addresses'].values():
616 for ip_address in ip_addresses:
617 self.check_vm_connectivity(ip_address['addr'],
618 username,
619 private_key,
620 should_connect=should_connect)
621 except Exception as e:
622 LOG.exception('Tenant network connectivity check failed')
Roman Popelka164898c2022-03-21 09:12:38 +0100623 self.log_console_output(servers_for_debug)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000624 self._log_net_info(e)
625 raise
626
627 def _check_remote_connectivity(self, source, dest, should_succeed=True,
628 nic=None):
629 """assert ping server via source ssh connection
630
631 Note: This is an internal method. Use check_remote_connectivity
632 instead.
633
634 :param source: RemoteClient: an ssh connection from which to ping
635 :param dest: and IP to ping against
636 :param should_succeed: boolean should ping succeed or not
637 :param nic: specific network interface to ping from
638 """
639 def ping_remote():
640 try:
641 source.ping_host(dest, nic=nic)
642 except lib_exc.SSHExecCommandFailed:
643 LOG.warning('Failed to ping IP: %s via a ssh connection '
644 'from: %s.', dest, source.ssh_client.host)
645 return not should_succeed
646 return should_succeed
647
648 return test_utils.call_until_true(ping_remote,
649 CONF.validation.ping_timeout,
650 1)
651
652 def check_remote_connectivity(self, source, dest, should_succeed=True,
653 nic=None):
654 """assert ping server via source ssh connection
655
656 :param source: RemoteClient: an ssh connection from which to ping
657 :param dest: and IP to ping against
658 :param should_succeed: boolean should ping succeed or not
659 :param nic: specific network interface to ping from
660 """
661 result = self._check_remote_connectivity(source, dest, should_succeed,
662 nic)
663 source_host = source.ssh_client.host
664 if should_succeed:
zhongjun39e9c582017-06-21 15:17:11 +0800665 msg = ("Timed out waiting for %s to become reachable from %s"
666 % (dest, source_host))
Andrea Frittolif4510a12017-03-07 19:17:11 +0000667 else:
668 msg = "%s is reachable from %s" % (dest, source_host)
669 self.assertTrue(result, msg)
670
671 def _create_security_group(self, security_group_rules_client=None,
672 tenant_id=None,
673 namestart='secgroup-smoke',
674 security_groups_client=None):
675 if security_group_rules_client is None:
676 security_group_rules_client = self.security_group_rules_client
677 if security_groups_client is None:
678 security_groups_client = self.security_groups_client
679 if tenant_id is None:
680 tenant_id = security_groups_client.tenant_id
681 secgroup = self._create_empty_security_group(
682 namestart=namestart, client=security_groups_client,
683 tenant_id=tenant_id)
684
685 # Add rules to the security group
686 rules = self._create_loginable_secgroup_rule(
687 security_group_rules_client=security_group_rules_client,
688 secgroup=secgroup,
689 security_groups_client=security_groups_client)
690 for rule in rules:
691 self.assertEqual(tenant_id, rule['tenant_id'])
692 self.assertEqual(secgroup['id'], rule['security_group_id'])
693 return secgroup
694
695 def _create_empty_security_group(self, client=None, tenant_id=None,
696 namestart='secgroup-smoke'):
697 """Create a security group without rules.
698
699 Default rules will be created:
700 - IPv4 egress to any
701 - IPv6 egress to any
702
703 :param tenant_id: secgroup will be created in this tenant
704 :returns: the created security group
705 """
706 if client is None:
707 client = self.security_groups_client
708 if not tenant_id:
709 tenant_id = client.tenant_id
710 sg_name = data_utils.rand_name(namestart)
711 sg_desc = sg_name + " description"
712 sg_dict = dict(name=sg_name,
713 description=sg_desc)
714 sg_dict['tenant_id'] = tenant_id
715 result = client.create_security_group(**sg_dict)
716
717 secgroup = result['security_group']
718 self.assertEqual(secgroup['name'], sg_name)
719 self.assertEqual(tenant_id, secgroup['tenant_id'])
720 self.assertEqual(secgroup['description'], sg_desc)
721
722 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
723 client.delete_security_group, secgroup['id'])
724 return secgroup
725
726 def _default_security_group(self, client=None, tenant_id=None):
727 """Get default secgroup for given tenant_id.
728
729 :returns: default secgroup for given tenant
730 """
731 if client is None:
732 client = self.security_groups_client
733 if not tenant_id:
734 tenant_id = client.tenant_id
735 sgs = [
736 sg for sg in list(client.list_security_groups().values())[0]
737 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
738 ]
739 msg = "No default security group for tenant %s." % (tenant_id)
740 self.assertGreater(len(sgs), 0, msg)
741 return sgs[0]
742
743 def _create_security_group_rule(self, secgroup=None,
744 sec_group_rules_client=None,
745 tenant_id=None,
746 security_groups_client=None, **kwargs):
747 """Create a rule from a dictionary of rule parameters.
748
749 Create a rule in a secgroup. if secgroup not defined will search for
750 default secgroup in tenant_id.
751
752 :param secgroup: the security group.
753 :param tenant_id: if secgroup not passed -- the tenant in which to
754 search for default secgroup
755 :param kwargs: a dictionary containing rule parameters:
756 for example, to allow incoming ssh:
757 rule = {
758 direction: 'ingress'
759 protocol:'tcp',
760 port_range_min: 22,
761 port_range_max: 22
762 }
763 """
764 if sec_group_rules_client is None:
765 sec_group_rules_client = self.security_group_rules_client
766 if security_groups_client is None:
767 security_groups_client = self.security_groups_client
768 if not tenant_id:
769 tenant_id = security_groups_client.tenant_id
770 if secgroup is None:
771 secgroup = self._default_security_group(
772 client=security_groups_client, tenant_id=tenant_id)
773
774 ruleset = dict(security_group_id=secgroup['id'],
775 tenant_id=secgroup['tenant_id'])
776 ruleset.update(kwargs)
777
778 sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
779 sg_rule = sg_rule['security_group_rule']
780
781 self.assertEqual(secgroup['tenant_id'], sg_rule['tenant_id'])
782 self.assertEqual(secgroup['id'], sg_rule['security_group_id'])
783
784 return sg_rule
785
786 def _create_loginable_secgroup_rule(self, security_group_rules_client=None,
787 secgroup=None,
788 security_groups_client=None):
789 """Create loginable security group rule
790
791 This function will create:
792 1. egress and ingress tcp port 22 allow rule in order to allow ssh
793 access for ipv4.
794 2. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6.
795 3. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4.
796 """
797
798 if security_group_rules_client is None:
799 security_group_rules_client = self.security_group_rules_client
800 if security_groups_client is None:
801 security_groups_client = self.security_groups_client
802 rules = []
803 rulesets = [
804 dict(
805 # ssh
806 protocol='tcp',
807 port_range_min=22,
808 port_range_max=22,
809 ),
810 dict(
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200811 # ipv6-ssh
812 protocol='tcp',
813 port_range_min=22,
814 port_range_max=22,
815 ethertype='IPv6',
816 ),
817 dict(
Andrea Frittolif4510a12017-03-07 19:17:11 +0000818 # ping
819 protocol='icmp',
820 ),
821 dict(
822 # ipv6-icmp for ping6
823 protocol='icmp',
824 ethertype='IPv6',
825 )
826 ]
827 sec_group_rules_client = security_group_rules_client
828 for ruleset in rulesets:
829 for r_direction in ['ingress', 'egress']:
830 ruleset['direction'] = r_direction
831 try:
832 sg_rule = self._create_security_group_rule(
833 sec_group_rules_client=sec_group_rules_client,
834 secgroup=secgroup,
835 security_groups_client=security_groups_client,
836 **ruleset)
837 except lib_exc.Conflict as ex:
838 # if rule already exist - skip rule and continue
839 msg = 'Security group rule already exists'
840 if msg not in ex._error_string:
841 raise ex
842 else:
843 self.assertEqual(r_direction, sg_rule['direction'])
844 rules.append(sg_rule)
845
846 return rules
847
848 def _get_router(self, client=None, tenant_id=None):
849 """Retrieve a router for the given tenant id.
850
851 If a public router has been configured, it will be returned.
852
853 If a public router has not been configured, but a public
854 network has, a tenant router will be created and returned that
855 routes traffic to the public network.
856 """
857 if not client:
858 client = self.routers_client
859 if not tenant_id:
860 tenant_id = client.tenant_id
861 router_id = CONF.network.public_router_id
862 network_id = CONF.network.public_network_id
863 if router_id:
864 body = client.show_router(router_id)
865 return body['router']
866 elif network_id:
867 router = self._create_router(client, tenant_id)
868 kwargs = {'external_gateway_info': dict(network_id=network_id)}
869 router = client.update_router(router['id'], **kwargs)['router']
870 return router
871 else:
872 raise Exception("Neither of 'public_router_id' or "
873 "'public_network_id' has been defined.")
874
875 def _create_router(self, client=None, tenant_id=None,
876 namestart='router-smoke'):
877 if not client:
878 client = self.routers_client
879 if not tenant_id:
880 tenant_id = client.tenant_id
881 name = data_utils.rand_name(namestart)
882 result = client.create_router(name=name,
883 admin_state_up=True,
884 tenant_id=tenant_id)
885 router = result['router']
886 self.assertEqual(router['name'], name)
887 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
888 client.delete_router,
889 router['id'])
890 return router
891
892 def _update_router_admin_state(self, router, admin_state_up):
893 kwargs = dict(admin_state_up=admin_state_up)
894 router = self.routers_client.update_router(
895 router['id'], **kwargs)['router']
896 self.assertEqual(admin_state_up, router['admin_state_up'])
897
898 def create_networks(self, networks_client=None,
899 routers_client=None, subnets_client=None,
900 tenant_id=None, dns_nameservers=None,
901 port_security_enabled=True):
902 """Create a network with a subnet connected to a router.
903
904 The baremetal driver is a special case since all nodes are
905 on the same shared network.
906
907 :param tenant_id: id of tenant to create resources in.
908 :param dns_nameservers: list of dns servers to send to subnet.
909 :returns: network, subnet, router
910 """
911 if CONF.network.shared_physical_network:
912 # NOTE(Shrews): This exception is for environments where tenant
913 # credential isolation is available, but network separation is
914 # not (the current baremetal case). Likely can be removed when
915 # test account mgmt is reworked:
916 # https://blueprints.launchpad.net/tempest/+spec/test-accounts
917 if not CONF.compute.fixed_network_name:
918 m = 'fixed_network_name must be specified in config'
919 raise lib_exc.InvalidConfiguration(m)
Goutham Pacha Ravi37ee6772019-10-18 12:53:22 -0700920 network = self._get_network_by_name_or_id(
Andrea Frittolif4510a12017-03-07 19:17:11 +0000921 CONF.compute.fixed_network_name)
922 router = None
923 subnet = None
924 else:
925 network = self._create_network(
926 networks_client=networks_client,
927 tenant_id=tenant_id,
928 port_security_enabled=port_security_enabled)
929 router = self._get_router(client=routers_client,
930 tenant_id=tenant_id)
931 subnet_kwargs = dict(network=network,
932 subnets_client=subnets_client,
933 routers_client=routers_client)
934 # use explicit check because empty list is a valid option
935 if dns_nameservers is not None:
936 subnet_kwargs['dns_nameservers'] = dns_nameservers
937 subnet = self._create_subnet(**subnet_kwargs)
938 if not routers_client:
939 routers_client = self.routers_client
940 router_id = router['id']
941 routers_client.add_router_interface(router_id,
942 subnet_id=subnet['id'])
943
944 # save a cleanup job to remove this association between
945 # router and subnet
946 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
947 routers_client.remove_router_interface, router_id,
948 subnet_id=subnet['id'])
949 return network, subnet, router