blob: b65407dedf20755e6739727ea27e3d11fb1d14a9 [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
Pavlo Shchelokovskyy0a898802023-10-04 17:59:03 +000017import os
18
19import netaddr
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020020from oslo_log import log
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020021
Pavlo Shchelokovskyy0a898802023-10-04 17:59:03 +000022from tempest.common import compute
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020023from tempest.common import utils
Pavlo Shchelokovskyy0a898802023-10-04 17:59:03 +000024from tempest.common import waiters
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020025from tempest import config
26from tempest.lib.common.utils import data_utils
27from tempest.lib.common.utils import test_utils
28from tempest.lib import exceptions as lib_exc
Roman Popelkafd509eb2022-04-07 15:10:08 +020029from tempest.scenario import manager
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020030
Pavlo Shchelokovskyy0a898802023-10-04 17:59:03 +000031
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020032CONF = config.CONF
33
34LOG = log.getLogger(__name__)
35
Pavlo Shchelokovskyy0a898802023-10-04 17:59:03 +000036NET_A = 'A'
37NET_A_BIS = 'A-Bis'
38NET_B = 'B'
39NET_C = 'C'
40
41if "SUBNETPOOL_PREFIX_V4" in os.environ:
42 subnet_base = netaddr.IPNetwork(os.environ['SUBNETPOOL_PREFIX_V4'])
43 if subnet_base.prefixlen > 21:
44 raise Exception("if SUBNETPOOL_PREFIX_V4 is set, it needs to offer "
45 "space for at least 8 /24 subnets")
46else:
47 subnet_base = netaddr.IPNetwork("10.100.0.0/16")
48
49
50def assign_24(idx):
51 # how many addresses in a /24:
52 range_size = 2 ** (32 - 24)
53 return netaddr.cidr_merge(
54 subnet_base[range_size * idx:range_size * (idx + 1)])[0]
55
56
57S1A = assign_24(1)
58S2A = assign_24(2)
59S1B = assign_24(4)
60S2B = assign_24(6)
61S1C = assign_24(6)
62NET_A_S1 = str(S1A)
63NET_A_S2 = str(S2A)
64NET_B_S1 = str(S1B)
65NET_B_S2 = str(S2B)
66NET_C_S1 = str(S1C)
67IP_A_S1_1 = str(S1A[10])
68IP_B_S1_1 = str(S1B[20])
69IP_C_S1_1 = str(S1C[30])
70IP_A_S1_2 = str(S1A[30])
71IP_B_S1_2 = str(S1B[40])
72IP_A_S1_3 = str(S1A[50])
73IP_B_S1_3 = str(S1B[60])
74IP_A_S2_1 = str(S2A[50])
75IP_B_S2_1 = str(S2B[60])
76IP_A_BIS_S1_1 = IP_A_S1_1
77IP_A_BIS_S1_2 = IP_A_S1_2
78IP_A_BIS_S1_3 = IP_A_S1_3
79IP_A_BIS_S2_1 = IP_A_S2_1
80
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020081
Roman Popelkafd509eb2022-04-07 15:10:08 +020082class ScenarioTest(manager.NetworkScenarioTest):
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020083 """Base class for scenario tests. Uses tempest own clients. """
84
85 credentials = ['primary']
86
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020087
88class NetworkScenarioTest(ScenarioTest):
89 """Base class for network scenario tests.
90
91 This class provide helpers for network scenario tests, using the neutron
92 API. Helpers from ancestor which use the nova network API are overridden
93 with the neutron API.
94
95 This Class also enforces using Neutron instead of novanetwork.
96 Subclassed tests will be skipped if Neutron is not enabled
97
98 """
99
100 credentials = ['primary', 'admin']
101
102 @classmethod
103 def skip_checks(cls):
104 super(NetworkScenarioTest, cls).skip_checks()
105 if not CONF.service_available.neutron:
106 raise cls.skipException('Neutron not available')
107 if not utils.is_extension_enabled('bgpvpn', 'network'):
108 msg = "Bgpvpn extension not enabled."
109 raise cls.skipException(msg)
110
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200111 def _check_remote_connectivity(self, source, dest, should_succeed=True,
112 nic=None):
113 """check ping server via source ssh connection
114
115 :param source: RemoteClient: an ssh connection from which to ping
116 :param dest: and IP to ping against
117 :param should_succeed: boolean should ping succeed or not
118 :param nic: specific network interface to ping from
119 :returns: boolean -- should_succeed == ping
120 :returns: ping is false if ping failed
121 """
122 def ping_remote():
123 try:
124 source.ping_host(dest, nic=nic)
125 except lib_exc.SSHExecCommandFailed:
126 LOG.warning('Failed to ping IP: %s via a ssh connection '
127 'from: %s.', dest, source.ssh_client.host)
128 return not should_succeed
129 return should_succeed
130
131 return test_utils.call_until_true(ping_remote,
132 CONF.validation.ping_timeout,
133 1)
134
Roman Popelka8235e3b2022-04-12 11:29:17 +0200135 def create_loginable_secgroup_rule(self, security_group_rules_client=None,
136 secgroup=None,
137 security_groups_client=None):
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200138 """Create loginable security group rule
139
140 This function will create:
141 1. egress and ingress tcp port 22 allow rule in order to allow ssh
142 access for ipv4.
143 2. egress and ingress tcp port 80 allow rule in order to allow http
144 access for ipv4.
145 3. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6.
146 4. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4.
147 """
148
149 if security_group_rules_client is None:
150 security_group_rules_client = self.security_group_rules_client
151 if security_groups_client is None:
152 security_groups_client = self.security_groups_client
153 rules = []
154 rulesets = [
155 dict(
156 # ssh
157 protocol='tcp',
158 port_range_min=22,
159 port_range_max=22,
160 ),
161 dict(
162 # http
163 protocol='tcp',
164 port_range_min=80,
165 port_range_max=80,
166 ),
167 dict(
168 # ping
169 protocol='icmp',
170 ),
171 dict(
172 # ipv6-icmp for ping6
173 protocol='icmp',
174 ethertype='IPv6',
175 )
176 ]
177 sec_group_rules_client = security_group_rules_client
178 for ruleset in rulesets:
179 for r_direction in ['ingress', 'egress']:
180 ruleset['direction'] = r_direction
181 try:
Roman Popelka46d4dda2022-04-12 11:36:39 +0200182 sg_rule = self.create_security_group_rule(
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200183 sec_group_rules_client=sec_group_rules_client,
184 secgroup=secgroup,
185 security_groups_client=security_groups_client,
186 **ruleset)
187 except lib_exc.Conflict as ex:
188 # if rule already exist - skip rule and continue
189 msg = 'Security group rule already exists'
190 if msg not in ex._error_string:
191 raise ex
192 else:
193 self.assertEqual(r_direction, sg_rule['direction'])
194 rules.append(sg_rule)
195
196 return rules
197
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200198 def _create_router(self, client=None, tenant_id=None,
199 namestart='router-smoke'):
200 if not client:
Pavlo Shchelokovskyy0a898802023-10-04 17:59:03 +0000201 client = self.routers_client
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200202 if not tenant_id:
Pavlo Shchelokovskyy0a898802023-10-04 17:59:03 +0000203 tenant_id = client.tenant_id
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200204 name = data_utils.rand_name(namestart)
205 result = client.create_router(name=name,
206 admin_state_up=True,
207 tenant_id=tenant_id)
208 router = result['router']
209 self.assertEqual(router['name'], name)
210 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
211 client.delete_router,
212 router['id'])
213 return router
Pavlo Shchelokovskyy0a898802023-10-04 17:59:03 +0000214
215 def _create_security_group_for_test(self):
216 self.security_group = self.create_security_group(
217 project_id=self.bgpvpn_client.project_id)
218
219 def _create_networks_and_subnets(self, names=None, subnet_cidrs=None,
220 port_security=True):
221 if not names:
222 names = [NET_A, NET_B, NET_C]
223 if not subnet_cidrs:
224 subnet_cidrs = [[NET_A_S1], [NET_B_S1], [NET_C_S1]]
225 for (name, subnet_cidrs) in zip(names, subnet_cidrs):
226 network = super(NetworkScenarioTest, self).create_network(
227 namestart=name,
228 port_security_enabled=port_security)
229 self.networks[name] = network
230 self.subnets[name] = []
231 for (j, cidr) in enumerate(subnet_cidrs):
232 sub_name = "subnet-%s-%d" % (name, j + 1)
233 subnet = self._create_subnet_with_cidr(network,
234 namestart=sub_name,
235 cidr=cidr,
236 ip_version=4)
237 self.subnets[name].append(subnet)
238
239 def _create_subnet_with_cidr(self, network, subnets_client=None,
240 namestart='subnet-smoke', **kwargs):
241 if not subnets_client:
242 subnets_client = self.subnets_client
243 tenant_cidr = kwargs.get('cidr')
Oleksandr Kononenkoc4673d12025-03-19 12:27:38 +0200244 # reserving pool for Neutron service ports
245 net = netaddr.IPNetwork(tenant_cidr)
246 allocation_pools = [{'start': str(net[2]), 'end': str(net[9])}]
Pavlo Shchelokovskyy0a898802023-10-04 17:59:03 +0000247 subnet = dict(
248 name=data_utils.rand_name(namestart),
249 network_id=network['id'],
250 tenant_id=network['tenant_id'],
Oleksandr Kononenkoc4673d12025-03-19 12:27:38 +0200251 allocation_pools=allocation_pools,
Pavlo Shchelokovskyy0a898802023-10-04 17:59:03 +0000252 **kwargs)
253 result = subnets_client.create_subnet(**subnet)
254 self.assertIsNotNone(result, 'Unable to allocate tenant network')
255 subnet = result['subnet']
256 self.assertEqual(subnet['cidr'], tenant_cidr)
257 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
258 subnets_client.delete_subnet, subnet['id'])
259 return subnet
260
261 def _create_fip_router(self, client=None, public_network_id=None,
262 subnet_id=None):
263 router = self._create_router(client, namestart='router-')
264 router_id = router['id']
265 if public_network_id is None:
266 public_network_id = CONF.network.public_network_id
267 if client is None:
268 client = self.routers_client
269 kwargs = {'external_gateway_info': {'network_id': public_network_id}}
270 router = client.update_router(router_id, **kwargs)['router']
271 if subnet_id is not None:
272 client.add_router_interface(router_id, subnet_id=subnet_id)
273 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
274 client.remove_router_interface, router_id,
275 subnet_id=subnet_id)
276 return router
277
278 def _associate_fip(self, server_index):
279 server = self.servers[server_index]
280 fip = self.create_floating_ip(
281 server, external_network_id=CONF.network.public_network_id,
282 port_id=self.ports[server['id']]['id'])
283 self.server_fips[server['id']] = fip
284 return fip
285
286 def _create_router_and_associate_fip(self, server_index, subnet):
287 router = self._create_fip_router(subnet_id=subnet['id'])
288 self._associate_fip(server_index)
289 return router
290
291 def _create_server(self, name, keypair, network, ip_address,
292 security_group_ids, clients, port_security):
293 security_groups = []
294 if port_security:
295 security_groups = security_group_ids
296 create_port_body = {'fixed_ips': [{'ip_address': ip_address}],
297 'namestart': 'port-smoke',
298 'security_groups': security_groups}
299
300 port = super(NetworkScenarioTest, self).create_port(
301 network_id=network['id'],
302 client=clients.ports_client,
303 **create_port_body)
304
305 create_server_kwargs = {
306 'key_name': keypair['name'],
307 'networks': [{'uuid': network['id'], 'port': port['id']}]
308 }
309 body, servers = compute.create_test_server(
310 clients, wait_until='ACTIVE', name=name, **create_server_kwargs)
311 self.addCleanup(waiters.wait_for_server_termination,
312 clients.servers_client, body['id'])
313 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
314 clients.servers_client.delete_server, body['id'])
315 server = clients.servers_client.show_server(body['id'])['server']
316 LOG.debug('Created server: %s with status: %s', server['id'],
317 server['status'])
318 self.ports[server['id']] = port
319 return server
320
321 def _create_servers(self, ports_config=None, port_security=True):
322 keypair = self.create_keypair()
323 security_group_ids = [self.security_group['id']]
324 if not ports_config:
325 ports_config = [[self.networks[NET_A], IP_A_S1_1],
326 [self.networks[NET_B], IP_B_S1_1]]
327
328 for (i, port_config) in enumerate(ports_config):
329 network = port_config[0]
330 server = self._create_server(
331 'server-' + str(i + 1), keypair, network, port_config[1],
332 security_group_ids, self.os_primary, port_security)
333 self.servers.append(server)
334 self.servers_keypairs[server['id']] = keypair
335 self.server_fixed_ips[server['id']] = (
336 server['addresses'][network['name']][0]['addr'])
337 self.assertTrue(self.servers_keypairs)
338
339 def _create_l3_bgpvpn(self, name='test-l3-bgpvpn', rts=None,
340 import_rts=None, export_rts=None):
341 if rts is None and import_rts is None and export_rts is None:
342 rts = [self.RT1]
343 import_rts = import_rts or []
344 export_rts = export_rts or []
345 self.bgpvpn = self.create_bgpvpn(
346 self.bgpvpn_admin_client, tenant_id=self.bgpvpn_client.tenant_id,
347 name=name, route_targets=rts, export_targets=export_rts,
348 import_targets=import_rts)
349 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
350 self.bgpvpn_admin_client.delete_bgpvpn,
351 self.bgpvpn['id'])
352 return self.bgpvpn
353
354 def _update_l3_bgpvpn(self, rts=None, import_rts=None, export_rts=None,
355 bgpvpn=None):
356 bgpvpn = bgpvpn or self.bgpvpn
357 if rts is None:
358 rts = [self.RT1]
359 import_rts = import_rts or []
360 export_rts = export_rts or []
361 LOG.debug('Updating targets in BGPVPN %s', bgpvpn['id'])
362 self.bgpvpn_admin_client.update_bgpvpn(bgpvpn['id'],
363 route_targets=rts,
364 export_targets=export_rts,
365 import_targets=import_rts)
366
367 def _associate_all_nets_to_bgpvpn(self, bgpvpn=None):
368 bgpvpn = bgpvpn or self.bgpvpn
369 for network in self.networks.values():
370 self.bgpvpn_client.create_network_association(
371 bgpvpn['id'], network['id'])
372 LOG.debug('BGPVPN network associations completed')
373
374 def _setup_ssh_client(self, server):
375 server_fip = self.server_fips[server['id']][
376 'floating_ip_address']
377 private_key = self.servers_keypairs[server['id']][
378 'private_key']
379 ssh_client = self.get_remote_client(server_fip,
380 private_key=private_key,
381 server=server)
382 return ssh_client
383
384 def _setup_http_server(self, server_index):
385 server = self.servers[server_index]
386 ssh_client = self._setup_ssh_client(server)
387 ssh_client.exec_command("sudo nc -kl -p 80 -e echo '%s:%s' &"
388 % (server['name'], server['id']))
389
390 def _setup_ip_forwarding(self, server_index):
391 server = self.servers[server_index]
392 ssh_client = self._setup_ssh_client(server)
393 ssh_client.exec_command("sudo sysctl -w net.ipv4.ip_forward=1")
394
395 def _setup_ip_address(self, server_index, cidr, device=None):
396 self._setup_range_ip_address(server_index, [cidr], device=None)
397
398 def _setup_range_ip_address(self, server_index, cidrs, device=None):
399 MAX_CIDRS = 50
400 if device is None:
401 device = 'lo'
402 server = self.servers[server_index]
403 ssh_client = self._setup_ssh_client(server)
404 for i in range(0, len(cidrs), MAX_CIDRS):
405 ips = ' '.join(cidrs[i:i + MAX_CIDRS])
406 ssh_client.exec_command(
407 ("for ip in {ips}; do sudo ip addr add $ip "
408 "dev {dev}; done").format(ips=ips, dev=device))
409
410 def _check_l3_bgpvpn(self, from_server=None, to_server=None,
411 should_succeed=True, validate_server=False):
412 to_server = to_server or self.servers[1]
413 destination_srv = None
414 if validate_server:
415 destination_srv = '%s:%s' % (to_server['name'], to_server['id'])
416 destination_ip = self.server_fixed_ips[to_server['id']]
417 self._check_l3_bgpvpn_by_specific_ip(from_server=from_server,
418 to_server_ip=destination_ip,
419 should_succeed=should_succeed,
420 validate_server=destination_srv)
421
422 def _check_l3_bgpvpn_by_specific_ip(self, from_server=None,
423 to_server_ip=None,
424 should_succeed=True,
425 validate_server=None,
426 repeat_validate_server=10):
427 from_server = from_server or self.servers[0]
428 from_server_ip = self.server_fips[from_server['id']][
429 'floating_ip_address']
430 if to_server_ip is None:
431 to_server_ip = self.server_fixed_ips[self.servers[1]['id']]
432 ssh_client = self._setup_ssh_client(from_server)
433 check_reachable = should_succeed or validate_server
434 msg = ""
435 if check_reachable:
436 msg = "Timed out waiting for {ip} to become reachable".format(
437 ip=to_server_ip)
438 else:
439 msg = ("Unexpected ping response from VM with IP address "
440 "{dest} originated from VM with IP address "
441 "{src}").format(dest=to_server_ip, src=from_server_ip)
442 try:
443 result = self._check_remote_connectivity(ssh_client,
444 to_server_ip,
445 check_reachable)
446 # if a negative connectivity check was unsuccessful (unexpected
447 # ping reply) then try to know more:
448 if not check_reachable and not result:
449 try:
450 content = ssh_client.exec_command(
451 "nc %s 80" % to_server_ip).strip()
452 LOG.warning("Can connect to %s: %s", to_server_ip, content)
453 except Exception:
454 LOG.warning("Could ping %s, but no http", to_server_ip)
455
456 self.assertTrue(result, msg)
457
458 if validate_server and result:
459 # repeating multiple times gives increased odds of avoiding
460 # false positives in the case where the dataplane does
461 # equal-cost multipath
462 for i in range(0, repeat_validate_server):
463 real_dest = ssh_client.exec_command(
464 "nc %s 80" % to_server_ip).strip()
465 result = real_dest == validate_server
466 self.assertTrue(
467 should_succeed == result,
468 ("Destination server name is '%s', expected is '%s'" %
469 (real_dest, validate_server)))
470 LOG.info("nc server name check %d successful", i)
471 except Exception:
472 LOG.exception("Error validating connectivity to %s "
473 "from VM with IP address %s: %s",
474 to_server_ip, from_server_ip, msg)
475 raise
476
477 def _associate_fip_and_check_l3_bgpvpn(self, subnet=None,
478 should_succeed=True):
479 if not subnet:
480 subnet = self.subnets[NET_A][0]
481 else:
482 subnet = self.subnets[subnet][0]
483
484 self.router = self._create_router_and_associate_fip(0, subnet)
485 self._check_l3_bgpvpn(should_succeed=should_succeed)
486
487 def _live_migrate(self, server_id, target_host, state,
488 volume_backed=False):
489 # If target_host is None,
490 # check whether source host is different with
491 # the new host after migration.
492 if target_host is None:
493 source_host = self.get_host_for_server(server_id)
494 self._migrate_server_to(server_id, target_host, volume_backed)
495 waiters.wait_for_server_status(self.servers_client, server_id, state)
496 migration_list = (self.admin_migration_client.list_migrations()
497 ['migrations'])
498 msg = ("Live Migration failed. Migrations list for Instance "
499 "%s: [" % server_id)
500 for live_migration in migration_list:
501 if (live_migration['instance_uuid'] == server_id):
502 msg += "\n%s" % live_migration
503 msg += "]"
504 if target_host is None:
505 self.assertNotEqual(source_host,
506 self.get_host_for_server(server_id), msg)
507 else:
508 self.assertEqual(target_host, self.get_host_for_server(server_id),
509 msg)
510
511 def _migrate_server_to(self, server_id, dest_host, volume_backed=False):
512 kwargs = dict()
513 block_migration = getattr(self, 'block_migration', None)
514 if self.block_migration is None:
515 block_migration = (CONF.compute_feature_enabled.
516 block_migration_for_live_migration and
517 not volume_backed)
518 self.admin_servers_client.live_migrate_server(
519 server_id, host=dest_host, block_migration=block_migration,
520 **kwargs)