From 4ae1b39ed21a4f9eedc501ad609708a80646ae15 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Fri, 17 Jul 2020 13:41:53 +0200 Subject: [PATCH] neutron: Check for non-frag connectivity at MTU size --- zaza/openstack/charm_tests/neutron/tests.py | 131 +++++++++++++++++--- 1 file changed, 113 insertions(+), 18 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index 2747569..bebe3b6 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -24,6 +24,8 @@ import copy import logging import tenacity +from neutronclient.common import exceptions as neutronexceptions + import zaza import zaza.openstack.charm_tests.nova.utils as nova_utils import zaza.openstack.charm_tests.test_utils as test_utils @@ -635,7 +637,8 @@ class NeutronNetworkingBase(test_utils.OpenStackBaseTest): def validate_instance_can_reach_other(self, instance_1, instance_2, - verify): + verify, + mtu=None): """ Validate that an instance can reach a fixed and floating of another. @@ -644,6 +647,12 @@ class NeutronNetworkingBase(test_utils.OpenStackBaseTest): :param instance_2: The instance to check networking from :type instance_2: nova_client.Server + + :param verify: callback to verify result + :type verify: callable + + :param mtu: Check that we can send non-fragmented packets of given size + :type mtu: Optional[int] """ floating_1 = floating_ips_from_instance(instance_1)[0] floating_2 = floating_ips_from_instance(instance_2)[0] @@ -653,19 +662,30 @@ class NeutronNetworkingBase(test_utils.OpenStackBaseTest): password = guest.boot_tests['bionic'].get('password') privkey = openstack_utils.get_private_key(nova_utils.KEYPAIR_NAME) - openstack_utils.ssh_command( - username, floating_1, 'instance-1', - 'ping -c 1 {}'.format(address_2), - password=password, privkey=privkey, verify=verify) + cmds = [ + 'ping -c 1', + ] + if mtu: + # the on-wire packet will be 28 bytes larger than the value + # provided to ping(8) -s parameter + packetsize = mtu - 28 + cmds.append( + 'ping -M do -s {} -c 1'.format(packetsize)) - openstack_utils.ssh_command( - username, floating_1, 'instance-1', - 'ping -c 1 {}'.format(floating_2), - password=password, privkey=privkey, verify=verify) + for cmd in cmds: + openstack_utils.ssh_command( + username, floating_1, 'instance-1', + '{} {}'.format(cmd, address_2), + password=password, privkey=privkey, verify=verify) + + openstack_utils.ssh_command( + username, floating_1, 'instance-1', + '{} {}'.format(cmd, floating_2), + password=password, privkey=privkey, verify=verify) @tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=60), reraise=True, stop=tenacity.stop_after_attempt(8)) - def validate_instance_can_reach_router(self, instance, verify): + def validate_instance_can_reach_router(self, instance, verify, mtu=None): """ Validate that an instance can reach it's primary gateway. @@ -676,6 +696,12 @@ class NeutronNetworkingBase(test_utils.OpenStackBaseTest): :param instance: The instance to check networking from :type instance: nova_client.Server + + :param verify: callback to verify result + :type verify: callable + + :param mtu: Check that we can send non-fragmented packets of given size + :type mtu: Optional[int] """ address = floating_ips_from_instance(instance)[0] @@ -683,9 +709,20 @@ class NeutronNetworkingBase(test_utils.OpenStackBaseTest): password = guest.boot_tests['bionic'].get('password') privkey = openstack_utils.get_private_key(nova_utils.KEYPAIR_NAME) - openstack_utils.ssh_command( - username, address, 'instance', 'ping -c 1 192.168.0.1', - password=password, privkey=privkey, verify=verify) + cmds = [ + 'ping -c 1', + ] + if mtu: + # the on-wire packet will be 28 bytes larger than the value + # provided to ping(8) -s parameter + packetsize = mtu - 28 + cmds.append( + 'ping -M do -s {} -c 1'.format(packetsize)) + + for cmd in cmds: + openstack_utils.ssh_command( + username, address, 'instance', '{} 192.168.0.1'.format(cmd), + password=password, privkey=privkey, verify=verify) @tenacity.retry(wait=tenacity.wait_exponential(min=5, max=60), reraise=True, stop=tenacity.stop_after_attempt(8), @@ -726,21 +763,67 @@ class NeutronNetworkingBase(test_utils.OpenStackBaseTest): assert agent['admin_state_up'] assert agent['alive'] + def effective_network_mtu(self, network_name): + """Retrieve effective MTU for a network. + + If the `instance-mtu` configuration option is set to a value lower than + the network MTU this method will return the value of that. Otherwise + Neutron's value for MTU on a network will be returned. + + :param network_name: Name of network to query + :type network_name: str + :returns: MTU for network + :rtype: int + """ + cfg_instance_mtu = None + for app in ('neutron-gateway', 'neutron-openvswitch'): + try: + cfg = zaza.model.get_application_config(app) + cfg_instance_mtu = int(cfg['instance-mtu']['value']) + break + except KeyError: + pass + + networks = self.neutron_client.show_network('', name=network_name) + network_mtu = int(next(iter(networks['networks']))['mtu']) + + if cfg_instance_mtu and cfg_instance_mtu < network_mtu: + logging.info('Using MTU from application "{}" config: {}' + .format(app, cfg_instance_mtu)) + return cfg_instance_mtu + else: + logging.info('Using MTU from network "{}": {}' + .format(network_name, network_mtu)) + return network_mtu + def check_connectivity(self, instance_1, instance_2): """Run North/South and East/West connectivity tests.""" def verify(stdin, stdout, stderr): """Validate that the SSH command exited 0.""" self.assertEqual(stdout.channel.recv_exit_status(), 0) + try: + mtu_1 = self.effective_network_mtu( + network_name_from_instance(instance_1)) + mtu_2 = self.effective_network_mtu( + network_name_from_instance(instance_2)) + mtu_min = min(mtu_1, mtu_2) + except neutronexceptions.NotFound: + # Older versions of OpenStack cannot look up network by name, just + # skip the check if that is the case. + mtu_1 = mtu_2 = mtu_min = None + # Verify network from 1 to 2 - self.validate_instance_can_reach_other(instance_1, instance_2, verify) + self.validate_instance_can_reach_other( + instance_1, instance_2, verify, mtu_min) # Verify network from 2 to 1 - self.validate_instance_can_reach_other(instance_2, instance_1, verify) + self.validate_instance_can_reach_other( + instance_2, instance_1, verify, mtu_min) # Validate tenant to external network routing - self.validate_instance_can_reach_router(instance_1, verify) - self.validate_instance_can_reach_router(instance_2, verify) + self.validate_instance_can_reach_router(instance_1, verify, mtu_1) + self.validate_instance_can_reach_router(instance_2, verify, mtu_2) def floating_ips_from_instance(instance): @@ -769,6 +852,17 @@ def fixed_ips_from_instance(instance): return ips_from_instance(instance, 'fixed') +def network_name_from_instance(instance): + """Retrieve name of primary network the instance is attached to. + + :param instance: The instance to fetch name of network from. + :type instance: nova_client.Server + :returns: Name of primary network the instance is attached to. + :rtype: str + """ + return next(iter(instance.addresses)) + + def ips_from_instance(instance, ip_type): """ Retrieve IPs of a certain type from an instance. @@ -786,7 +880,8 @@ def ips_from_instance(instance, ip_type): "Only 'floating' and 'fixed' are valid IP types to search for" ) return list([ - ip['addr'] for ip in instance.addresses['private'] + ip['addr'] for ip in instance.addresses[ + network_name_from_instance(instance)] if ip['OS-EXT-IPS:type'] == ip_type])