blob: b342bc62d1c377e0e67ccacbf208bcc77b010615 [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']]
1011 if ports_config is None:
1012 ports_config = [[self.networks[NET_A], IP_A_S1_1],
1013 [self.networks[NET_B], IP_B_S1_1]]
1014 for (i, port_config) in enumerate(ports_config):
1015 network = port_config[0]
1016 server = self._create_server(
1017 'server-' + str(i + 1), keypair, network, port_config[1],
1018 security_group_ids, self.os_primary, port_security)
1019 self.servers.append(server)
1020 self.servers_keypairs[server['id']] = keypair
1021 self.server_fixed_ips[server['id']] = (
1022 server['addresses'][network['name']][0]['addr'])
1023 self.assertTrue(self.servers_keypairs)
1024
1025 def _associate_all_nets_to_bgpvpn(self, bgpvpn=None):
1026 bgpvpn = bgpvpn or self.bgpvpn
1027 for network in self.networks.values():
1028 self.bgpvpn_client.create_network_association(
1029 bgpvpn['id'], network['id'])
1030 LOG.debug('BGPVPN network associations completed')
1031
1032 def _create_networks_and_subnets(self, names=None, subnet_cidrs=None,
1033 port_security=True):
1034 if not names:
1035 names = [NET_A, NET_B, NET_C]
1036 if not subnet_cidrs:
1037 subnet_cidrs = [[NET_A_S1], [NET_B_S1], [NET_C_S1]]
1038 for (name, subnet_cidrs) in zip(names, subnet_cidrs):
1039 network = self._create_network(
1040 namestart=name, port_security_enabled=port_security)
1041 self.networks[name] = network
1042 self.subnets[name] = []
1043 for (j, cidr) in enumerate(subnet_cidrs):
1044 sub_name = "subnet-%s-%d" % (name, j + 1)
1045 subnet = self._create_subnet_with_cidr(network,
1046 namestart=sub_name,
1047 cidr=cidr,
1048 ip_version=4)
1049 self.subnets[name].append(subnet)
1050
1051 def _setup_ssh_client(self, server):
1052 server_fip = self.server_fips[server['id']][
1053 'floating_ip_address']
1054 private_key = self.servers_keypairs[server['id']][
1055 'private_key']
1056 ssh_client = self.get_remote_client(server_fip,
1057 private_key=private_key)
1058 return ssh_client
1059
1060 def _check_l3_bgpvpn(self, from_server=None, to_server=None,
1061 should_succeed=True, validate_server=False):
1062 to_server = to_server or self.servers[1]
1063 destination_srv = None
1064 if validate_server:
1065 destination_srv = '%s:%s' % (to_server['name'], to_server['id'])
1066 destination_ip = self.server_fixed_ips[to_server['id']]
1067 self._check_l3_bgpvpn_by_specific_ip(from_server=from_server,
1068 to_server_ip=destination_ip,
1069 should_succeed=should_succeed,
1070 validate_server=destination_srv)
1071
1072 def _check_l3_bgpvpn_by_specific_ip(self, from_server=None,
1073 to_server_ip=None,
1074 should_succeed=True,
1075 validate_server=None,
1076 repeat_validate_server=10):
1077 from_server = from_server or self.servers[0]
1078 from_server_ip = self.server_fips[from_server['id']][
1079 'floating_ip_address']
1080 if to_server_ip is None:
1081 to_server_ip = self.server_fixed_ips[self.servers[1]['id']]
1082 ssh_client = self._setup_ssh_client(from_server)
1083 check_reachable = should_succeed or validate_server
1084 msg = ""
1085 if check_reachable:
1086 msg = "Timed out waiting for {ip} to become reachable".format(
1087 ip=to_server_ip)
1088 else:
1089 msg = ("Unexpected ping response from VM with IP address "
1090 "{dest} originated from VM with IP address "
1091 "{src}").format(dest=to_server_ip, src=from_server_ip)
1092 try:
1093 result = self._check_remote_connectivity(ssh_client,
1094 to_server_ip,
1095 check_reachable)
1096 # if a negative connectivity check was unsuccessful (unexpected
1097 # ping reply) then try to know more:
1098 if not check_reachable and not result:
1099 try:
1100 content = ssh_client.exec_command(
1101 "nc %s 80" % to_server_ip).strip()
1102 LOG.warning("Can connect to %s: %s", to_server_ip, content)
1103 except Exception:
1104 LOG.warning("Could ping %s, but no http", to_server_ip)
1105
1106 self.assertTrue(result, msg)
1107
1108 if validate_server and result:
1109 # repeating multiple times gives increased odds of avoiding
1110 # false positives in the case where the dataplane does
1111 # equal-cost multipath
1112 for i in range(repeat_validate_server):
1113 real_dest = ssh_client.exec_command(
1114 "nc %s 80" % to_server_ip).strip()
1115 result = real_dest == validate_server
1116 self.assertTrue(
1117 should_succeed == result,
1118 ("Destination server name is '%s', expected is '%s'" %
1119 (real_dest, validate_server)))
1120 LOG.info("nc server name check %d successful", i)
1121 except Exception:
1122 LOG.exception("Error validating connectivity to %s "
1123 "from VM with IP address %s: %s",
1124 to_server_ip, from_server_ip, msg)
1125 raise
1126
1127 def _associate_fip_and_check_l3_bgpvpn(self, should_succeed=True):
1128 subnet = self.subnets[NET_A][0]
1129 self.router = self._create_router_and_associate_fip(0, subnet)
1130 self._check_l3_bgpvpn(should_succeed=should_succeed)
1131
1132 def _live_migrate(self, server_id, target_host, state,
1133 volume_backed=False):
1134 # If target_host is None,
1135 # check whether source host is different with
1136 # the new host after migration.
1137 if target_host is None:
1138 source_host = self.get_host_for_server(server_id)
1139 self._migrate_server_to(server_id, target_host, volume_backed)
1140 waiters.wait_for_server_status(self.servers_client, server_id, state)
1141 migration_list = (self.admin_migration_client.list_migrations()
1142 ['migrations'])
1143
1144 msg = ("Live Migration failed. Migrations list for Instance "
1145 "%s: [" % server_id)
1146 for live_migration in migration_list:
1147 if (live_migration['instance_uuid'] == server_id):
1148 msg += "\n%s" % live_migration
1149 msg += "]"
1150 if target_host is None:
1151 self.assertNotEqual(source_host,
1152 self.get_host_for_server(server_id), msg)
1153 else:
1154 self.assertEqual(target_host, self.get_host_for_server(server_id),
1155 msg)
1156
1157 def _migrate_server_to(self, server_id, dest_host, volume_backed=False):
1158 kwargs = dict()
1159 block_migration = getattr(self, 'block_migration', None)
1160 if self.block_migration is None:
1161 if self.is_requested_microversion_compatible('2.24'):
1162 kwargs['disk_over_commit'] = False
1163 block_migration = (CONF.compute_feature_enabled.
1164 block_migration_for_live_migration and
1165 not volume_backed)
1166 self.admin_servers_client.live_migrate_server(
1167 server_id, host=dest_host, block_migration=block_migration,
1168 **kwargs)
1169
1170 def _create_l3_bgpvpn(self, name='test-l3-bgpvpn', rts=None,
1171 import_rts=None, export_rts=None):
1172 if rts is None and import_rts is None and export_rts is None:
1173 rts = [self.RT1]
1174 import_rts = import_rts or []
1175 export_rts = export_rts or []
1176 self.bgpvpn = self.create_bgpvpn(
1177 self.bgpvpn_admin_client, tenant_id=self.bgpvpn_client.tenant_id,
1178 name=name, route_targets=rts, export_targets=export_rts,
1179 import_targets=import_rts)
1180 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1181 self.bgpvpn_admin_client.delete_bgpvpn,
1182 self.bgpvpn['id'])
1183 return self.bgpvpn
1184
1185 def _update_l3_bgpvpn(self, rts=None, import_rts=None, export_rts=None,
1186 bgpvpn=None):
1187 bgpvpn = bgpvpn or self.bgpvpn
1188 if rts is None:
1189 rts = [self.RT1]
1190 import_rts = import_rts or []
1191 export_rts = export_rts or []
1192 LOG.debug('Updating targets in BGPVPN %s', bgpvpn['id'])
1193 self.bgpvpn_admin_client.update_bgpvpn(bgpvpn['id'],
1194 route_targets=rts,
1195 export_targets=export_rts,
1196 import_targets=import_rts)
1197
1198 def _setup_ip_forwarding(self, server_index):
1199 server = self.servers[server_index]
1200 ssh_client = self._setup_ssh_client(server)
1201 ssh_client.exec_command("sudo sysctl -w net.ipv4.ip_forward=1")
1202
1203 def _setup_ip_address(self, server_index, cidr, device=None):
1204 self._setup_range_ip_address(server_index, [cidr], device=None)
1205
1206 def _setup_range_ip_address(self, server_index, cidrs, device=None):
1207 MAX_CIDRS = 50
1208 if device is None:
1209 device = 'lo'
1210 server = self.servers[server_index]
1211 ssh_client = self._setup_ssh_client(server)
1212 for i in range(0, len(cidrs), MAX_CIDRS):
1213 ips = ' '.join(cidrs[i:i + MAX_CIDRS])
1214 ssh_client.exec_command(
1215 ("for ip in {ips}; do sudo ip addr add $ip "
1216 "dev {dev}; done").format(ips=ips, dev=device))
1217
1218 def _setup_http_server(self, server_index):
1219 server = self.servers[server_index]
1220 ssh_client = self._setup_ssh_client(server)
1221 ssh_client.exec_command("sudo nc -kl -p 80 -e echo '%s:%s' &"
1222 % (server['name'], server['id']))