blob: 21ba60d6964b7ebbd9c6bcbfbd91259891adbd86 [file] [log] [blame]
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +02001# 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
Roman Bubyr2957f312022-09-14 18:33:23 +030017import os
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020018import subprocess
19
20import netaddr
Roman Bubyr2957f312022-09-14 18:33:23 +030021
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020022from oslo_log import log
23from oslo_utils import netutils
24
25from tempest.common import compute
26from tempest.common import utils
27from tempest.common.utils.linux import remote_client
28from tempest.common.utils import net_utils
29from tempest.common import waiters
30from tempest import config
31from tempest.lib.common.utils import data_utils
32from tempest.lib.common.utils import test_utils
33from tempest.lib import exceptions as lib_exc
34import tempest.test
35
Roman Bubyr2957f312022-09-14 18:33:23 +030036
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020037CONF = config.CONF
38
39LOG = log.getLogger(__name__)
40
Roman Bubyr2957f312022-09-14 18:33:23 +030041NET_A = 'A'
42NET_A_BIS = 'A-Bis'
43NET_B = 'B'
44NET_C = 'C'
45
46if "SUBNETPOOL_PREFIX_V4" in os.environ:
47 subnet_base = netaddr.IPNetwork(os.environ['SUBNETPOOL_PREFIX_V4'])
48 if subnet_base.prefixlen > 21:
49 raise Exception("if SUBNETPOOL_PREFIX_V4 is set, it needs to offer "
50 "space for at least 8 /24 subnets")
51else:
52 subnet_base = netaddr.IPNetwork("10.100.0.0/16")
53
54
55def assign_24(idx):
56 # how many addresses in a /24:
57 range_size = 2 ** (32 - 24)
58 return netaddr.cidr_merge(
59 subnet_base[range_size * idx:range_size * (idx + 1)])[0]
60
61
62S1A = assign_24(1)
63S2A = assign_24(2)
64S1B = assign_24(4)
65S2B = assign_24(6)
66S1C = assign_24(6)
67NET_A_S1 = str(S1A)
68NET_A_S2 = str(S2A)
69NET_B_S1 = str(S1B)
70NET_B_S2 = str(S2B)
71NET_C_S1 = str(S1C)
72IP_A_S1_1 = str(S1A[10])
73IP_B_S1_1 = str(S1B[20])
74IP_C_S1_1 = str(S1C[30])
75IP_A_S1_2 = str(S1A[30])
76IP_B_S1_2 = str(S1B[40])
77IP_A_S1_3 = str(S1A[50])
78IP_B_S1_3 = str(S1B[60])
79IP_A_S2_1 = str(S2A[50])
80IP_B_S2_1 = str(S2B[60])
81IP_A_BIS_S1_1 = IP_A_S1_1
82IP_A_BIS_S1_2 = IP_A_S1_2
83IP_A_BIS_S1_3 = IP_A_S1_3
84IP_A_BIS_S2_1 = IP_A_S2_1
85
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020086
87class ScenarioTest(tempest.test.BaseTestCase):
88 """Base class for scenario tests. Uses tempest own clients. """
89
90 credentials = ['primary']
91
92 @classmethod
93 def setup_clients(cls):
94 super(ScenarioTest, cls).setup_clients()
95 # Clients (in alphabetical order)
96 cls.keypairs_client = cls.os_primary.keypairs_client
97 cls.servers_client = cls.os_primary.servers_client
98 # Neutron network client
99 cls.networks_client = cls.os_primary.networks_client
100 cls.ports_client = cls.os_primary.ports_client
101 cls.routers_client = cls.os_primary.routers_client
102 cls.subnets_client = cls.os_primary.subnets_client
103 cls.floating_ips_client = cls.os_primary.floating_ips_client
104 cls.security_groups_client = cls.os_primary.security_groups_client
105 cls.security_group_rules_client = (
106 cls.os_primary.security_group_rules_client)
107
108 # ## Test functions library
109 #
110 # The create_[resource] functions only return body and discard the
111 # resp part which is not used in scenario tests
112
113 def _create_port(self, network_id, client=None, namestart='port-quotatest',
114 **kwargs):
115 if not client:
116 client = self.ports_client
117 name = data_utils.rand_name(namestart)
118 result = client.create_port(
119 name=name,
120 network_id=network_id,
121 **kwargs)
122 self.assertIsNotNone(result, 'Unable to allocate port')
123 port = result['port']
124 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
125 client.delete_port, port['id'])
126 return port
127
128 def create_keypair(self, client=None):
129 if not client:
130 client = self.keypairs_client
131 name = data_utils.rand_name(self.__class__.__name__)
132 # We don't need to create a keypair by pubkey in scenario
133 body = client.create_keypair(name=name)
134 self.addCleanup(client.delete_keypair, name)
135 return body['keypair']
136
137 def create_server(self, name=None, image_id=None, flavor=None,
138 validatable=False, wait_until='ACTIVE',
139 clients=None, **kwargs):
140 """Wrapper utility that returns a test server.
141
142 This wrapper utility calls the common create test server and
143 returns a test server. The purpose of this wrapper is to minimize
144 the impact on the code of the tests already using this
145 function.
146 """
147
148 # NOTE(jlanoux): As a first step, ssh checks in the scenario
149 # tests need to be run regardless of the run_validation and
150 # validatable parameters and thus until the ssh validation job
151 # becomes voting in CI. The test resources management and IP
152 # association are taken care of in the scenario tests.
153 # Therefore, the validatable parameter is set to false in all
154 # those tests. In this way create_server just return a standard
155 # server and the scenario tests always perform ssh checks.
156
157 # Needed for the cross_tenant_traffic test:
158 if clients is None:
159 clients = self.os_primary
160
161 if name is None:
162 name = data_utils.rand_name(self.__class__.__name__ + "-server")
163
164 vnic_type = CONF.network.port_vnic_type
165
166 # If vnic_type is configured create port for
167 # every network
168 if vnic_type:
169 ports = []
170
171 create_port_body = {'binding:vnic_type': vnic_type,
172 'namestart': 'port-smoke'}
173 if kwargs:
174 # Convert security group names to security group ids
175 # to pass to create_port
176 if 'security_groups' in kwargs:
177 security_groups = \
178 clients.security_groups_client.list_security_groups(
179 ).get('security_groups')
180 sec_dict = dict([(s['name'], s['id'])
181 for s in security_groups])
182
183 sec_groups_names = [s['name'] for s in kwargs.pop(
184 'security_groups')]
185 security_groups_ids = [sec_dict[s]
186 for s in sec_groups_names]
187
188 if security_groups_ids:
189 create_port_body[
190 'security_groups'] = security_groups_ids
191 networks = kwargs.pop('networks', [])
192 else:
193 networks = []
194
195 # If there are no networks passed to us we look up
196 # for the project's private networks and create a port.
197 # The same behaviour as we would expect when passing
198 # the call to the clients with no networks
199 if not networks:
200 networks = clients.networks_client.list_networks(
201 **{'router:external': False, 'fields': 'id'})['networks']
202
203 # It's net['uuid'] if networks come from kwargs
204 # and net['id'] if they come from
205 # clients.networks_client.list_networks
206 for net in networks:
207 net_id = net.get('uuid', net.get('id'))
208 if 'port' not in net:
209 port = self._create_port(network_id=net_id,
210 client=clients.ports_client,
211 **create_port_body)
212 ports.append({'port': port['id']})
213 else:
214 ports.append({'port': net['port']})
215 if ports:
216 kwargs['networks'] = ports
217 self.ports = ports
218
219 tenant_network = self.get_tenant_network()
220
221 body, servers = compute.create_test_server(
222 clients,
223 tenant_network=tenant_network,
224 wait_until=wait_until,
225 name=name, flavor=flavor,
226 image_id=image_id, **kwargs)
227
228 self.addCleanup(waiters.wait_for_server_termination,
229 clients.servers_client, body['id'])
230 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
231 clients.servers_client.delete_server, body['id'])
232 server = clients.servers_client.show_server(body['id'])['server']
233 return server
234
235 def get_remote_client(self, ip_address, username=None, private_key=None):
236 """Get a SSH client to a remote server
237
238 @param ip_address the server floating or fixed IP address to use
239 for ssh validation
240 @param username name of the Linux account on the remote server
241 @param private_key the SSH private key to use
242 @return a RemoteClient object
243 """
244
245 if username is None:
246 username = CONF.validation.image_ssh_user
247 # Set this with 'keypair' or others to log in with keypair or
248 # username/password.
249 if CONF.validation.auth_method == 'keypair':
250 password = None
251 if private_key is None:
252 private_key = self.keypair['private_key']
253 else:
254 password = CONF.validation.image_ssh_password
255 private_key = None
256 linux_client = remote_client.RemoteClient(ip_address, username,
257 pkey=private_key,
258 password=password)
259 try:
260 linux_client.validate_authentication()
261 except Exception as e:
262 message = ('Initializing SSH connection to %(ip)s failed. '
263 'Error: %(error)s' % {'ip': ip_address,
264 'error': e})
265 caller = test_utils.find_test_caller()
266 if caller:
267 message = '(%s) %s' % (caller, message)
268 LOG.exception(message)
269 self._log_console_output()
270 raise
271
272 return linux_client
273
274 def _log_console_output(self, servers=None):
275 if not CONF.compute_feature_enabled.console_output:
276 LOG.debug('Console output not supported, cannot log')
277 return
278 if not servers:
279 servers = self.servers_client.list_servers()
280 servers = servers['servers']
281 for server in servers:
282 try:
283 console_output = self.servers_client.get_console_output(
284 server['id'])['output']
285 LOG.debug('Console output for %s\nbody=\n%s',
286 server['id'], console_output)
287 except lib_exc.NotFound:
288 LOG.debug("Server %s disappeared(deleted) while looking "
289 "for the console log", server['id'])
290
291 def _log_net_info(self, exc):
292 # network debug is called as part of ssh init
293 if not isinstance(exc, lib_exc.SSHTimeout):
294 LOG.debug('Network information on a devstack host')
295
296 def ping_ip_address(self, ip_address, should_succeed=True,
297 ping_timeout=None, mtu=None):
298 timeout = ping_timeout or CONF.validation.ping_timeout
299 cmd = ['ping', '-c1', '-w1']
300
301 if mtu:
302 cmd += [
303 # don't fragment
304 '-M', 'do',
305 # ping receives just the size of ICMP payload
306 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
307 ]
308 cmd.append(ip_address)
309
310 def ping():
311 proc = subprocess.Popen(cmd,
312 stdout=subprocess.PIPE,
313 stderr=subprocess.PIPE)
314 proc.communicate()
315
316 return (proc.returncode == 0) == should_succeed
317
318 caller = test_utils.find_test_caller()
319 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
320 ' expected result is %(should_succeed)s', {
321 'caller': caller, 'ip': ip_address, 'timeout': timeout,
322 'should_succeed':
323 'reachable' if should_succeed else 'unreachable'
324 })
325 result = test_utils.call_until_true(ping, timeout, 1)
326 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
327 'ping result is %(result)s', {
328 'caller': caller, 'ip': ip_address, 'timeout': timeout,
329 'result': 'expected' if result else 'unexpected'
330 })
331 return result
332
333 def check_vm_connectivity(self, ip_address,
334 username=None,
335 private_key=None,
336 should_connect=True,
337 mtu=None):
338 """Check server connectivity
339
340 :param ip_address: server to test against
341 :param username: server's ssh username
342 :param private_key: server's ssh private key to be used
343 :param should_connect: True/False indicates positive/negative test
344 positive - attempt ping and ssh
345 negative - attempt ping and fail if succeed
346 :param mtu: network MTU to use for connectivity validation
347
348 :raises: AssertError if the result of the connectivity check does
349 not match the value of the should_connect param
350 """
351 if should_connect:
352 msg = "Timed out waiting for %s to become reachable" % ip_address
353 else:
354 msg = "ip address %s is reachable" % ip_address
355 self.assertTrue(self.ping_ip_address(ip_address,
356 should_succeed=should_connect,
357 mtu=mtu),
358 msg=msg)
359 if should_connect:
360 # no need to check ssh for negative connectivity
361 self.get_remote_client(ip_address, username, private_key)
362
363 def check_public_network_connectivity(self, ip_address, username,
364 private_key, should_connect=True,
365 msg=None, servers=None, mtu=None):
366 # The target login is assumed to have been configured for
367 # key-based authentication by cloud-init.
368 LOG.debug('checking network connections to IP %s with user: %s',
369 ip_address, username)
370 try:
371 self.check_vm_connectivity(ip_address,
372 username,
373 private_key,
374 should_connect=should_connect,
375 mtu=mtu)
376 except Exception:
377 ex_msg = 'Public network connectivity check failed'
378 if msg:
379 ex_msg += ": " + msg
380 LOG.exception(ex_msg)
381 self._log_console_output(servers)
382 raise
383
384
385class NetworkScenarioTest(ScenarioTest):
386 """Base class for network scenario tests.
387
388 This class provide helpers for network scenario tests, using the neutron
389 API. Helpers from ancestor which use the nova network API are overridden
390 with the neutron API.
391
392 This Class also enforces using Neutron instead of novanetwork.
393 Subclassed tests will be skipped if Neutron is not enabled
394
395 """
396
397 credentials = ['primary', 'admin']
398
399 @classmethod
400 def skip_checks(cls):
401 super(NetworkScenarioTest, cls).skip_checks()
402 if not CONF.service_available.neutron:
403 raise cls.skipException('Neutron not available')
404 if not utils.is_extension_enabled('bgpvpn', 'network'):
405 msg = "Bgpvpn extension not enabled."
406 raise cls.skipException(msg)
407
408 def _create_network(self, networks_client=None,
409 tenant_id=None,
410 namestart='network-smoke-',
411 port_security_enabled=True):
412 if not networks_client:
413 networks_client = self.networks_client
414 if not tenant_id:
415 tenant_id = networks_client.tenant_id
416 name = data_utils.rand_name(namestart)
417 network_kwargs = dict(name=name, tenant_id=tenant_id)
418 # Neutron disables port security by default so we have to check the
419 # config before trying to create the network with port_security_enabled
420 if CONF.network_feature_enabled.port_security:
421 network_kwargs['port_security_enabled'] = port_security_enabled
422 result = networks_client.create_network(**network_kwargs)
423 network = result['network']
424
425 self.assertEqual(network['name'], name)
426 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
427 networks_client.delete_network,
428 network['id'])
429 return network
430
431 def _create_subnet(self, network, subnets_client=None,
432 routers_client=None, namestart='subnet-smoke',
433 **kwargs):
434 """Create a subnet for the given network
435
436 within the cidr block configured for tenant networks.
437 """
438 if not subnets_client:
439 subnets_client = self.subnets_client
440 if not routers_client:
441 routers_client = self.routers_client
442
443 def cidr_in_use(cidr, tenant_id):
444 """Check cidr existence
445
446 :returns: True if subnet with cidr already exist in tenant
447 False else
448 """
449 cidr_in_use = self.os_admin.subnets_client.list_subnets(
450 tenant_id=tenant_id, cidr=cidr)['subnets']
451 return len(cidr_in_use) != 0
452
453 ip_version = kwargs.pop('ip_version', 4)
454
455 if ip_version == 6:
456 tenant_cidr = netaddr.IPNetwork(
457 CONF.network.project_network_v6_cidr)
458 num_bits = CONF.network.project_network_v6_mask_bits
459 else:
460 tenant_cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
461 num_bits = CONF.network.project_network_mask_bits
462
463 result = None
464 str_cidr = None
465 # Repeatedly attempt subnet creation with sequential cidr
466 # blocks until an unallocated block is found.
467 for subnet_cidr in tenant_cidr.subnet(num_bits):
468 str_cidr = str(subnet_cidr)
469 if cidr_in_use(str_cidr, tenant_id=network['tenant_id']):
470 continue
471
472 subnet = dict(
473 name=data_utils.rand_name(namestart),
474 network_id=network['id'],
475 tenant_id=network['tenant_id'],
476 cidr=str_cidr,
477 ip_version=ip_version,
478 **kwargs
479 )
480 try:
481 result = subnets_client.create_subnet(**subnet)
482 break
483 except lib_exc.Conflict as e:
484 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
485 if not is_overlapping_cidr:
486 raise
487 self.assertIsNotNone(result, 'Unable to allocate tenant network')
488
489 subnet = result['subnet']
490 self.assertEqual(subnet['cidr'], str_cidr)
491
492 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
493 subnets_client.delete_subnet, subnet['id'])
494
495 return subnet
496
497 def _get_server_port_id_and_ip4(self, server, ip_addr=None):
498 ports = self.os_admin.ports_client.list_ports(
499 device_id=server['id'], fixed_ip=ip_addr)['ports']
500 # A port can have more than one IP address in some cases.
501 # If the network is dual-stack (IPv4 + IPv6), this port is associated
502 # with 2 subnets
503 p_status = ['ACTIVE']
504 # NOTE(vsaienko) With Ironic, instances live on separate hardware
505 # servers. Neutron does not bind ports for Ironic instances, as a
506 # result the port remains in the DOWN state.
507 # TODO(vsaienko) remove once bug: #1599836 is resolved.
508 if getattr(CONF.service_available, 'ironic', False):
509 p_status.append('DOWN')
510 port_map = [(p["id"], fxip["ip_address"])
511 for p in ports
512 for fxip in p["fixed_ips"]
Bernard Cafarellic3bec862020-09-10 13:59:49 +0200513 if netutils.is_valid_ipv4(fxip["ip_address"]) and
514 p['status'] in p_status]
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200515 inactive = [p for p in ports if p['status'] != 'ACTIVE']
516 if inactive:
517 LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
518
519 self.assertNotEqual(0, len(port_map),
520 "No IPv4 addresses found in: %s" % ports)
521 self.assertEqual(len(port_map), 1,
522 "Found multiple IPv4 addresses: %s. "
523 "Unable to determine which port to target."
524 % port_map)
525 return port_map[0]
526
527 def _get_network_by_name(self, network_name):
528 net = self.os_admin.networks_client.list_networks(
529 name=network_name)['networks']
530 self.assertNotEqual(len(net), 0,
531 "Unable to get network by name: %s" % network_name)
532 return net[0]
533
534 def create_floating_ip(self, thing, external_network_id=None,
535 port_id=None, client=None):
536 """Create a floating IP and associates to a resource/port on Neutron"""
537 if not external_network_id:
538 external_network_id = CONF.network.public_network_id
539 if not client:
540 client = self.floating_ips_client
541 if not port_id:
542 port_id, ip4 = self._get_server_port_id_and_ip4(thing)
543 else:
544 ip4 = None
545 result = client.create_floatingip(
546 floating_network_id=external_network_id,
547 port_id=port_id,
548 tenant_id=thing['tenant_id'],
549 fixed_ip_address=ip4
550 )
551 floating_ip = result['floatingip']
552 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
553 client.delete_floatingip,
554 floating_ip['id'])
555 return floating_ip
556
557 def _associate_floating_ip(self, floating_ip, server):
558 port_id, _ = self._get_server_port_id_and_ip4(server)
559 kwargs = dict(port_id=port_id)
560 floating_ip = self.floating_ips_client.update_floatingip(
561 floating_ip['id'], **kwargs)['floatingip']
562 self.assertEqual(port_id, floating_ip['port_id'])
563 return floating_ip
564
565 def _disassociate_floating_ip(self, floating_ip):
566 """:param floating_ip: floating_ips_client.create_floatingip"""
567 kwargs = dict(port_id=None)
568 floating_ip = self.floating_ips_client.update_floatingip(
569 floating_ip['id'], **kwargs)['floatingip']
570 self.assertIsNone(floating_ip['port_id'])
571 return floating_ip
572
573 def check_floating_ip_status(self, floating_ip, status):
574 """Verifies floatingip reaches the given status
575
576 :param dict floating_ip: floating IP dict to check status
577 :param status: target status
578 :raises: AssertionError if status doesn't match
579 """
580 floatingip_id = floating_ip['id']
581
582 def refresh():
583 result = (self.floating_ips_client.
584 show_floatingip(floatingip_id)['floatingip'])
585 return status == result['status']
586
587 test_utils.call_until_true(refresh,
588 CONF.network.build_timeout,
589 CONF.network.build_interval)
590 floating_ip = self.floating_ips_client.show_floatingip(
591 floatingip_id)['floatingip']
592 self.assertEqual(status, floating_ip['status'],
593 message="FloatingIP: {fp} is at status: {cst}. "
594 "failed to reach status: {st}"
595 .format(fp=floating_ip, cst=floating_ip['status'],
596 st=status))
597 LOG.info("FloatingIP: {fp} is at status: {st}"
598 .format(fp=floating_ip, st=status))
599
600 def _check_tenant_network_connectivity(self, server,
601 username,
602 private_key,
603 should_connect=True,
604 servers_for_debug=None):
605 if not CONF.network.project_networks_reachable:
606 msg = 'Tenant networks not configured to be reachable.'
607 LOG.info(msg)
608 return
609 # The target login is assumed to have been configured for
610 # key-based authentication by cloud-init.
611 try:
612 for net_name, ip_addresses in server['addresses'].items():
613 for ip_address in ip_addresses:
614 self.check_vm_connectivity(ip_address['addr'],
615 username,
616 private_key,
617 should_connect=should_connect)
618 except Exception as e:
619 LOG.exception('Tenant network connectivity check failed')
620 self._log_console_output(servers_for_debug)
621 self._log_net_info(e)
622 raise
623
624 def _check_remote_connectivity(self, source, dest, should_succeed=True,
625 nic=None):
626 """check ping server via source ssh connection
627
628 :param source: RemoteClient: an ssh connection from which to ping
629 :param dest: and IP to ping against
630 :param should_succeed: boolean should ping succeed or not
631 :param nic: specific network interface to ping from
632 :returns: boolean -- should_succeed == ping
633 :returns: ping is false if ping failed
634 """
635 def ping_remote():
636 try:
637 source.ping_host(dest, nic=nic)
638 except lib_exc.SSHExecCommandFailed:
639 LOG.warning('Failed to ping IP: %s via a ssh connection '
640 'from: %s.', dest, source.ssh_client.host)
641 return not should_succeed
642 return should_succeed
643
644 return test_utils.call_until_true(ping_remote,
645 CONF.validation.ping_timeout,
646 1)
647
648 def _create_security_group(self, security_group_rules_client=None,
649 tenant_id=None,
650 namestart='secgroup-smoke',
651 security_groups_client=None):
652 if security_group_rules_client is None:
653 security_group_rules_client = self.security_group_rules_client
654 if security_groups_client is None:
655 security_groups_client = self.security_groups_client
656 if tenant_id is None:
657 tenant_id = security_groups_client.tenant_id
658 secgroup = self._create_empty_security_group(
659 namestart=namestart, client=security_groups_client,
660 tenant_id=tenant_id)
661
662 # Add rules to the security group
663 rules = self._create_loginable_secgroup_rule(
664 security_group_rules_client=security_group_rules_client,
665 secgroup=secgroup,
666 security_groups_client=security_groups_client)
667 for rule in rules:
668 self.assertEqual(tenant_id, rule['tenant_id'])
669 self.assertEqual(secgroup['id'], rule['security_group_id'])
670 return secgroup
671
672 def _create_empty_security_group(self, client=None, tenant_id=None,
673 namestart='secgroup-smoke'):
674 """Create a security group without rules.
675
676 Default rules will be created:
677 - IPv4 egress to any
678 - IPv6 egress to any
679
680 :param tenant_id: secgroup will be created in this tenant
681 :returns: the created security group
682 """
683 if client is None:
684 client = self.security_groups_client
685 if not tenant_id:
686 tenant_id = client.tenant_id
687 sg_name = data_utils.rand_name(namestart)
688 sg_desc = sg_name + " description"
689 sg_dict = dict(name=sg_name,
690 description=sg_desc)
691 sg_dict['tenant_id'] = tenant_id
692 result = client.create_security_group(**sg_dict)
693
694 secgroup = result['security_group']
695 self.assertEqual(secgroup['name'], sg_name)
696 self.assertEqual(tenant_id, secgroup['tenant_id'])
697 self.assertEqual(secgroup['description'], sg_desc)
698
699 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
700 client.delete_security_group, secgroup['id'])
701 return secgroup
702
703 def _default_security_group(self, client=None, tenant_id=None):
704 """Get default secgroup for given tenant_id.
705
706 :returns: default secgroup for given tenant
707 """
708 if client is None:
709 client = self.security_groups_client
710 if not tenant_id:
711 tenant_id = client.tenant_id
712 sgs = [
713 sg for sg in list(client.list_security_groups().values())[0]
714 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
715 ]
716 msg = "No default security group for tenant %s." % (tenant_id)
717 self.assertGreater(len(sgs), 0, msg)
718 return sgs[0]
719
720 def _create_security_group_rule(self, secgroup=None,
721 sec_group_rules_client=None,
722 tenant_id=None,
723 security_groups_client=None, **kwargs):
724 """Create a rule from a dictionary of rule parameters.
725
726 Create a rule in a secgroup. if secgroup not defined will search for
727 default secgroup in tenant_id.
728
729 :param secgroup: the security group.
730 :param tenant_id: if secgroup not passed -- the tenant in which to
731 search for default secgroup
732 :param kwargs: a dictionary containing rule parameters:
733 for example, to allow incoming ssh:
734 rule = {
735 direction: 'ingress'
736 protocol:'tcp',
737 port_range_min: 22,
738 port_range_max: 22
739 }
740 """
741 if sec_group_rules_client is None:
742 sec_group_rules_client = self.security_group_rules_client
743 if security_groups_client is None:
744 security_groups_client = self.security_groups_client
745 if not tenant_id:
746 tenant_id = security_groups_client.tenant_id
747 if secgroup is None:
748 secgroup = self._default_security_group(
749 client=security_groups_client, tenant_id=tenant_id)
750
751 ruleset = dict(security_group_id=secgroup['id'],
752 tenant_id=secgroup['tenant_id'])
753 ruleset.update(kwargs)
754
755 sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
756 sg_rule = sg_rule['security_group_rule']
757
758 self.assertEqual(secgroup['tenant_id'], sg_rule['tenant_id'])
759 self.assertEqual(secgroup['id'], sg_rule['security_group_id'])
760
761 return sg_rule
762
763 def _create_loginable_secgroup_rule(self, security_group_rules_client=None,
764 secgroup=None,
765 security_groups_client=None):
766 """Create loginable security group rule
767
768 This function will create:
769 1. egress and ingress tcp port 22 allow rule in order to allow ssh
770 access for ipv4.
771 2. egress and ingress tcp port 80 allow rule in order to allow http
772 access for ipv4.
773 3. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6.
774 4. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4.
775 """
776
777 if security_group_rules_client is None:
778 security_group_rules_client = self.security_group_rules_client
779 if security_groups_client is None:
780 security_groups_client = self.security_groups_client
781 rules = []
782 rulesets = [
783 dict(
784 # ssh
785 protocol='tcp',
786 port_range_min=22,
787 port_range_max=22,
788 ),
789 dict(
790 # http
791 protocol='tcp',
792 port_range_min=80,
793 port_range_max=80,
794 ),
795 dict(
796 # ping
797 protocol='icmp',
798 ),
799 dict(
800 # ipv6-icmp for ping6
801 protocol='icmp',
802 ethertype='IPv6',
803 )
804 ]
805 sec_group_rules_client = security_group_rules_client
806 for ruleset in rulesets:
807 for r_direction in ['ingress', 'egress']:
808 ruleset['direction'] = r_direction
809 try:
810 sg_rule = self._create_security_group_rule(
811 sec_group_rules_client=sec_group_rules_client,
812 secgroup=secgroup,
813 security_groups_client=security_groups_client,
814 **ruleset)
815 except lib_exc.Conflict as ex:
816 # if rule already exist - skip rule and continue
817 msg = 'Security group rule already exists'
818 if msg not in ex._error_string:
819 raise ex
820 else:
821 self.assertEqual(r_direction, sg_rule['direction'])
822 rules.append(sg_rule)
823
824 return rules
825
826 def _get_router(self, client=None, tenant_id=None):
827 """Retrieve a router for the given tenant id.
828
829 If a public router has been configured, it will be returned.
830
831 If a public router has not been configured, but a public
832 network has, a tenant router will be created and returned that
833 routes traffic to the public network.
834 """
835 if not client:
836 client = self.routers_client
837 if not tenant_id:
838 tenant_id = client.tenant_id
839 router_id = CONF.network.public_router_id
840 network_id = CONF.network.public_network_id
841 if router_id:
842 body = client.show_router(router_id)
843 return body['router']
844 elif network_id:
845 router = self._create_router(client, tenant_id)
846 kwargs = {'external_gateway_info': dict(network_id=network_id)}
847 router = client.update_router(router['id'], **kwargs)['router']
848 return router
849 else:
850 raise Exception("Neither of 'public_router_id' or "
851 "'public_network_id' has been defined.")
852
853 def _create_router(self, client=None, tenant_id=None,
854 namestart='router-smoke'):
855 if not client:
856 client = self.routers_client
857 if not tenant_id:
858 tenant_id = client.tenant_id
859 name = data_utils.rand_name(namestart)
860 result = client.create_router(name=name,
861 admin_state_up=True,
862 tenant_id=tenant_id)
863 router = result['router']
864 self.assertEqual(router['name'], name)
865 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
866 client.delete_router,
867 router['id'])
868 return router
869
870 def _update_router_admin_state(self, router, admin_state_up):
871 kwargs = dict(admin_state_up=admin_state_up)
872 router = self.routers_client.update_router(
873 router['id'], **kwargs)['router']
874 self.assertEqual(admin_state_up, router['admin_state_up'])
875
876 def create_networks(self, networks_client=None,
877 routers_client=None, subnets_client=None,
878 tenant_id=None, dns_nameservers=None,
879 port_security_enabled=True):
880 """Create a network with a subnet connected to a router.
881
882 The baremetal driver is a special case since all nodes are
883 on the same shared network.
884
885 :param tenant_id: id of tenant to create resources in.
886 :param dns_nameservers: list of dns servers to send to subnet.
887 :returns: network, subnet, router
888 """
889 if CONF.network.shared_physical_network:
890 # NOTE(Shrews): This exception is for environments where tenant
891 # credential isolation is available, but network separation is
892 # not (the current baremetal case). Likely can be removed when
893 # test account mgmt is reworked:
894 # https://blueprints.launchpad.net/tempest/+spec/test-accounts
895 if not CONF.compute.fixed_network_name:
896 m = 'fixed_network_name must be specified in config'
897 raise lib_exc.InvalidConfiguration(m)
898 network = self._get_network_by_name(
899 CONF.compute.fixed_network_name)
900 router = None
901 subnet = None
902 else:
903 network = self._create_network(
904 networks_client=networks_client,
905 tenant_id=tenant_id,
906 port_security_enabled=port_security_enabled)
907 router = self._get_router(client=routers_client,
908 tenant_id=tenant_id)
909 subnet_kwargs = dict(network=network,
910 subnets_client=subnets_client,
911 routers_client=routers_client)
912 # use explicit check because empty list is a valid option
913 if dns_nameservers is not None:
914 subnet_kwargs['dns_nameservers'] = dns_nameservers
915 subnet = self._create_subnet(**subnet_kwargs)
916 if not routers_client:
917 routers_client = self.routers_client
918 router_id = router['id']
919 routers_client.add_router_interface(router_id,
920 subnet_id=subnet['id'])
921
922 # save a cleanup job to remove this association between
923 # router and subnet
924 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
925 routers_client.remove_router_interface, router_id,
926 subnet_id=subnet['id'])
927 return network, subnet, router
Roman Bubyr2957f312022-09-14 18:33:23 +0300928
929 def _create_security_group_for_test(self):
930 self.security_group = self._create_security_group(
931 tenant_id=self.bgpvpn_client.tenant_id)
932
933 def _create_subnet_with_cidr(self, network, subnets_client=None,
934 namestart='subnet-smoke', **kwargs):
935 if not subnets_client:
936 subnets_client = self.subnets_client
937 tenant_cidr = kwargs.get('cidr')
938 subnet = dict(
939 name=data_utils.rand_name(namestart),
940 network_id=network['id'],
941 tenant_id=network['tenant_id'],
942 **kwargs)
943 result = subnets_client.create_subnet(**subnet)
944 self.assertIsNotNone(result, 'Unable to allocate tenant network')
945 subnet = result['subnet']
946 self.assertEqual(subnet['cidr'], tenant_cidr)
947 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
948 subnets_client.delete_subnet, subnet['id'])
949 return subnet
950
951 def _create_fip_router(self, client=None, public_network_id=None,
952 subnet_id=None):
953 router = self._create_router(client, namestart='router-')
954 router_id = router['id']
955 if public_network_id is None:
956 public_network_id = CONF.network.public_network_id
957 if client is None:
958 client = self.routers_client
959 kwargs = {'external_gateway_info': {'network_id': public_network_id}}
960 router = client.update_router(router_id, **kwargs)['router']
961 if subnet_id is not None:
962 client.add_router_interface(router_id, subnet_id=subnet_id)
963 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
964 client.remove_router_interface, router_id,
965 subnet_id=subnet_id)
966 return router
967
968 def _associate_fip(self, server_index):
969 server = self.servers[server_index]
970 fip = self.create_floating_ip(
971 server, external_network_id=CONF.network.public_network_id,
972 port_id=self.ports[server['id']]['id'])
973 self.server_fips[server['id']] = fip
974 return fip
975
976 def _create_router_and_associate_fip(self, server_index, subnet):
977 router = self._create_fip_router(subnet_id=subnet['id'])
978 self._associate_fip(server_index)
979 return router
980
981 def _create_server(self, name, keypair, network, ip_address,
982 security_group_ids, clients, port_security):
983 security_groups = []
984 if port_security:
985 security_groups = security_group_ids
986 create_port_body = {'fixed_ips': [{'ip_address': ip_address}],
987 'namestart': 'port-smoke',
988 'security_groups': security_groups}
989 port = self._create_port(network_id=network['id'],
990 client=clients.ports_client,
991 **create_port_body)
992 create_server_kwargs = {
993 'key_name': keypair['name'],
994 'networks': [{'uuid': network['id'], 'port': port['id']}]
995 }
996 body, servers = compute.create_test_server(
997 clients, wait_until='ACTIVE', name=name, **create_server_kwargs)
998 self.addCleanup(waiters.wait_for_server_termination,
999 clients.servers_client, body['id'])
1000 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1001 clients.servers_client.delete_server, body['id'])
1002 server = clients.servers_client.show_server(body['id'])['server']
1003 LOG.debug('Created server: %s with status: %s', server['id'],
1004 server['status'])
1005 self.ports[server['id']] = port
1006 return server
1007
1008 def _create_servers(self, ports_config=None, port_security=True):
1009 keypair = self.create_keypair()
1010 security_group_ids = [self.security_group['id']]
Roman Bubyr3f4c4012022-12-23 18:43:49 +02001011 if not ports_config:
Roman Bubyr2957f312022-09-14 18:33:23 +03001012 ports_config = [[self.networks[NET_A], IP_A_S1_1],
1013 [self.networks[NET_B], IP_B_S1_1]]
Roman Bubyr3f4c4012022-12-23 18:43:49 +02001014
Roman Bubyr2957f312022-09-14 18:33:23 +03001015 for (i, port_config) in enumerate(ports_config):
1016 network = port_config[0]
1017 server = self._create_server(
1018 'server-' + str(i + 1), keypair, network, port_config[1],
1019 security_group_ids, self.os_primary, port_security)
1020 self.servers.append(server)
1021 self.servers_keypairs[server['id']] = keypair
1022 self.server_fixed_ips[server['id']] = (
1023 server['addresses'][network['name']][0]['addr'])
1024 self.assertTrue(self.servers_keypairs)
1025
1026 def _associate_all_nets_to_bgpvpn(self, bgpvpn=None):
1027 bgpvpn = bgpvpn or self.bgpvpn
1028 for network in self.networks.values():
1029 self.bgpvpn_client.create_network_association(
1030 bgpvpn['id'], network['id'])
1031 LOG.debug('BGPVPN network associations completed')
1032
1033 def _create_networks_and_subnets(self, names=None, subnet_cidrs=None,
1034 port_security=True):
1035 if not names:
1036 names = [NET_A, NET_B, NET_C]
1037 if not subnet_cidrs:
1038 subnet_cidrs = [[NET_A_S1], [NET_B_S1], [NET_C_S1]]
1039 for (name, subnet_cidrs) in zip(names, subnet_cidrs):
1040 network = self._create_network(
1041 namestart=name, port_security_enabled=port_security)
1042 self.networks[name] = network
1043 self.subnets[name] = []
1044 for (j, cidr) in enumerate(subnet_cidrs):
1045 sub_name = "subnet-%s-%d" % (name, j + 1)
1046 subnet = self._create_subnet_with_cidr(network,
1047 namestart=sub_name,
1048 cidr=cidr,
1049 ip_version=4)
1050 self.subnets[name].append(subnet)
1051
1052 def _setup_ssh_client(self, server):
1053 server_fip = self.server_fips[server['id']][
1054 'floating_ip_address']
1055 private_key = self.servers_keypairs[server['id']][
1056 'private_key']
1057 ssh_client = self.get_remote_client(server_fip,
1058 private_key=private_key)
1059 return ssh_client
1060
1061 def _check_l3_bgpvpn(self, from_server=None, to_server=None,
1062 should_succeed=True, validate_server=False):
1063 to_server = to_server or self.servers[1]
1064 destination_srv = None
1065 if validate_server:
1066 destination_srv = '%s:%s' % (to_server['name'], to_server['id'])
1067 destination_ip = self.server_fixed_ips[to_server['id']]
1068 self._check_l3_bgpvpn_by_specific_ip(from_server=from_server,
1069 to_server_ip=destination_ip,
1070 should_succeed=should_succeed,
1071 validate_server=destination_srv)
1072
1073 def _check_l3_bgpvpn_by_specific_ip(self, from_server=None,
1074 to_server_ip=None,
1075 should_succeed=True,
1076 validate_server=None,
1077 repeat_validate_server=10):
1078 from_server = from_server or self.servers[0]
1079 from_server_ip = self.server_fips[from_server['id']][
1080 'floating_ip_address']
1081 if to_server_ip is None:
1082 to_server_ip = self.server_fixed_ips[self.servers[1]['id']]
1083 ssh_client = self._setup_ssh_client(from_server)
1084 check_reachable = should_succeed or validate_server
1085 msg = ""
1086 if check_reachable:
1087 msg = "Timed out waiting for {ip} to become reachable".format(
1088 ip=to_server_ip)
1089 else:
1090 msg = ("Unexpected ping response from VM with IP address "
1091 "{dest} originated from VM with IP address "
1092 "{src}").format(dest=to_server_ip, src=from_server_ip)
1093 try:
1094 result = self._check_remote_connectivity(ssh_client,
1095 to_server_ip,
1096 check_reachable)
1097 # if a negative connectivity check was unsuccessful (unexpected
1098 # ping reply) then try to know more:
1099 if not check_reachable and not result:
1100 try:
1101 content = ssh_client.exec_command(
1102 "nc %s 80" % to_server_ip).strip()
1103 LOG.warning("Can connect to %s: %s", to_server_ip, content)
1104 except Exception:
1105 LOG.warning("Could ping %s, but no http", to_server_ip)
1106
1107 self.assertTrue(result, msg)
1108
1109 if validate_server and result:
1110 # repeating multiple times gives increased odds of avoiding
1111 # false positives in the case where the dataplane does
1112 # equal-cost multipath
1113 for i in range(repeat_validate_server):
1114 real_dest = ssh_client.exec_command(
1115 "nc %s 80" % to_server_ip).strip()
1116 result = real_dest == validate_server
1117 self.assertTrue(
1118 should_succeed == result,
1119 ("Destination server name is '%s', expected is '%s'" %
1120 (real_dest, validate_server)))
1121 LOG.info("nc server name check %d successful", i)
1122 except Exception:
1123 LOG.exception("Error validating connectivity to %s "
1124 "from VM with IP address %s: %s",
1125 to_server_ip, from_server_ip, msg)
1126 raise
1127
Roman Bubyr3f4c4012022-12-23 18:43:49 +02001128 def _associate_fip_and_check_l3_bgpvpn(self, subnet=None,
1129 should_succeed=True):
1130 if not subnet:
1131 subnet = self.subnets[NET_A][0]
1132 else:
1133 subnet = self.subnets[subnet][0]
1134
Roman Bubyr2957f312022-09-14 18:33:23 +03001135 self.router = self._create_router_and_associate_fip(0, subnet)
1136 self._check_l3_bgpvpn(should_succeed=should_succeed)
1137
1138 def _live_migrate(self, server_id, target_host, state,
1139 volume_backed=False):
1140 # If target_host is None,
1141 # check whether source host is different with
1142 # the new host after migration.
1143 if target_host is None:
1144 source_host = self.get_host_for_server(server_id)
1145 self._migrate_server_to(server_id, target_host, volume_backed)
1146 waiters.wait_for_server_status(self.servers_client, server_id, state)
1147 migration_list = (self.admin_migration_client.list_migrations()
1148 ['migrations'])
1149
1150 msg = ("Live Migration failed. Migrations list for Instance "
1151 "%s: [" % server_id)
1152 for live_migration in migration_list:
1153 if (live_migration['instance_uuid'] == server_id):
1154 msg += "\n%s" % live_migration
1155 msg += "]"
1156 if target_host is None:
1157 self.assertNotEqual(source_host,
1158 self.get_host_for_server(server_id), msg)
1159 else:
1160 self.assertEqual(target_host, self.get_host_for_server(server_id),
1161 msg)
1162
1163 def _migrate_server_to(self, server_id, dest_host, volume_backed=False):
1164 kwargs = dict()
1165 block_migration = getattr(self, 'block_migration', None)
1166 if self.block_migration is None:
1167 if self.is_requested_microversion_compatible('2.24'):
1168 kwargs['disk_over_commit'] = False
1169 block_migration = (CONF.compute_feature_enabled.
1170 block_migration_for_live_migration and
1171 not volume_backed)
1172 self.admin_servers_client.live_migrate_server(
1173 server_id, host=dest_host, block_migration=block_migration,
1174 **kwargs)
1175
1176 def _create_l3_bgpvpn(self, name='test-l3-bgpvpn', rts=None,
1177 import_rts=None, export_rts=None):
1178 if rts is None and import_rts is None and export_rts is None:
1179 rts = [self.RT1]
1180 import_rts = import_rts or []
1181 export_rts = export_rts or []
1182 self.bgpvpn = self.create_bgpvpn(
1183 self.bgpvpn_admin_client, tenant_id=self.bgpvpn_client.tenant_id,
1184 name=name, route_targets=rts, export_targets=export_rts,
1185 import_targets=import_rts)
1186 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1187 self.bgpvpn_admin_client.delete_bgpvpn,
1188 self.bgpvpn['id'])
1189 return self.bgpvpn
1190
1191 def _update_l3_bgpvpn(self, rts=None, import_rts=None, export_rts=None,
1192 bgpvpn=None):
1193 bgpvpn = bgpvpn or self.bgpvpn
1194 if rts is None:
1195 rts = [self.RT1]
1196 import_rts = import_rts or []
1197 export_rts = export_rts or []
1198 LOG.debug('Updating targets in BGPVPN %s', bgpvpn['id'])
1199 self.bgpvpn_admin_client.update_bgpvpn(bgpvpn['id'],
1200 route_targets=rts,
1201 export_targets=export_rts,
1202 import_targets=import_rts)
1203
1204 def _setup_ip_forwarding(self, server_index):
1205 server = self.servers[server_index]
1206 ssh_client = self._setup_ssh_client(server)
1207 ssh_client.exec_command("sudo sysctl -w net.ipv4.ip_forward=1")
1208
1209 def _setup_ip_address(self, server_index, cidr, device=None):
1210 self._setup_range_ip_address(server_index, [cidr], device=None)
1211
1212 def _setup_range_ip_address(self, server_index, cidrs, device=None):
1213 MAX_CIDRS = 50
1214 if device is None:
1215 device = 'lo'
1216 server = self.servers[server_index]
1217 ssh_client = self._setup_ssh_client(server)
1218 for i in range(0, len(cidrs), MAX_CIDRS):
1219 ips = ' '.join(cidrs[i:i + MAX_CIDRS])
1220 ssh_client.exec_command(
1221 ("for ip in {ips}; do sudo ip addr add $ip "
1222 "dev {dev}; done").format(ips=ips, dev=device))
1223
1224 def _setup_http_server(self, server_index):
1225 server = self.servers[server_index]
1226 ssh_client = self._setup_ssh_client(server)
1227 ssh_client.exec_command("sudo nc -kl -p 80 -e echo '%s:%s' &"
1228 % (server['name'], server['id']))