|  | # Copyright 2020 Red Hat, Inc. | 
|  | # All Rights Reserved. | 
|  | # | 
|  | #    Licensed under the Apache License, Version 2.0 (the "License"); you may | 
|  | #    not use this file except in compliance with the License. You may obtain | 
|  | #    a copy of the License at | 
|  | # | 
|  | #         http://www.apache.org/licenses/LICENSE-2.0 | 
|  | # | 
|  | #    Unless required by applicable law or agreed to in writing, software | 
|  | #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | 
|  | #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | 
|  | #    License for the specific language governing permissions and limitations | 
|  | #    under the License. | 
|  |  | 
|  | from neutron_lib import constants as lib_constants | 
|  | from oslo_log import log | 
|  | from paramiko import ssh_exception as ssh_exc | 
|  | from tempest.common import utils as tempest_utils | 
|  | from tempest.lib.common.utils import data_utils | 
|  | from tempest.lib import decorators | 
|  | from tempest.lib import exceptions as lib_exc | 
|  | import testtools | 
|  |  | 
|  | from neutron_tempest_plugin.common import ip | 
|  | from neutron_tempest_plugin.common import ssh | 
|  | from neutron_tempest_plugin.common import utils | 
|  | from neutron_tempest_plugin import config | 
|  | from neutron_tempest_plugin.scenario import base | 
|  |  | 
|  | CONF = config.CONF | 
|  |  | 
|  | LOG = log.getLogger(__name__) | 
|  |  | 
|  |  | 
|  | def turn_nic6_on(ssh, ipv6_port, config_nic=True): | 
|  | """Turns the IPv6 vNIC on | 
|  |  | 
|  | Required because guest images usually set only the first vNIC on boot. | 
|  | Searches for the IPv6 vNIC's MAC and brings it up. | 
|  | # NOTE(slaweq): on RHEL based OS ifcfg file for new interface is | 
|  | # needed to make IPv6 working on it, so if | 
|  | # /etc/sysconfig/network-scripts directory exists ifcfg-%(nic)s file | 
|  | # should be added in it | 
|  |  | 
|  | @param ssh: RemoteClient ssh instance to server | 
|  | @param ipv6_port: port from IPv6 network attached to the server | 
|  | """ | 
|  | ip_command = ip.IPCommand(ssh) | 
|  | nic = ip_command.get_nic_name_by_mac(ipv6_port['mac_address']) | 
|  |  | 
|  | if config_nic: | 
|  | try: | 
|  | if sysconfig_network_scripts_dir_exists(ssh): | 
|  | ssh.execute_script( | 
|  | 'echo -e "DEVICE=%(nic)s\\nNAME=%(nic)s\\nIPV6INIT=yes" | ' | 
|  | 'tee /etc/sysconfig/network-scripts/ifcfg-%(nic)s; ' | 
|  | % {'nic': nic}, become_root=True) | 
|  | if nmcli_command_exists(ssh): | 
|  | ssh.execute_script('nmcli connection reload %s' % nic, | 
|  | become_root=True) | 
|  | ssh.execute_script('nmcli con mod %s ipv6.addr-gen-mode eui64' | 
|  | % nic, become_root=True) | 
|  | ssh.execute_script('nmcli connection up %s' % nic, | 
|  | become_root=True) | 
|  |  | 
|  | except lib_exc.SSHExecCommandFailed as e: | 
|  | # NOTE(slaweq): Sometimes it can happen that this SSH command | 
|  | # will fail because of some error from network manager in | 
|  | # guest os. | 
|  | # But even then doing ip link set up below is fine and | 
|  | # IP address should be configured properly. | 
|  | LOG.debug("Error creating NetworkManager profile. " | 
|  | "Error message: %(error)s", | 
|  | {'error': e}) | 
|  |  | 
|  | ip_command.set_link(nic, "up") | 
|  |  | 
|  |  | 
|  | def configure_eth_connection_profile_NM(ssh): | 
|  | """Prepare a Network manager profile for ipv6 port | 
|  |  | 
|  | By default the NetworkManager uses IPv6 privacy | 
|  | format it isn't supported by neutron then we create | 
|  | a ether profile with eui64 supported format | 
|  |  | 
|  | @param ssh: RemoteClient ssh instance to server | 
|  | """ | 
|  | # NOTE(ccamposr): on RHEL based OS we need a ether profile with | 
|  | # eui64 format | 
|  | if nmcli_command_exists(ssh): | 
|  | try: | 
|  | ssh.execute_script('nmcli connection add type ethernet con-name ' | 
|  | 'ether ifname "*"', become_root=True) | 
|  | ssh.execute_script('nmcli con mod ether ipv6.addr-gen-mode eui64', | 
|  | become_root=True) | 
|  |  | 
|  | except lib_exc.SSHExecCommandFailed as e: | 
|  | # NOTE(slaweq): Sometimes it can happen that this SSH command | 
|  | # will fail because of some error from network manager in | 
|  | # guest os. | 
|  | # But even then doing ip link set up below is fine and | 
|  | # IP address should be configured properly. | 
|  | LOG.debug("Error creating NetworkManager profile. " | 
|  | "Error message: %(error)s", | 
|  | {'error': e}) | 
|  |  | 
|  |  | 
|  | def sysconfig_network_scripts_dir_exists(ssh): | 
|  | return "False" not in ssh.execute_script( | 
|  | 'test -d /etc/sysconfig/network-scripts/ || echo "False"') | 
|  |  | 
|  |  | 
|  | def nmcli_command_exists(ssh): | 
|  | return "False" not in ssh.execute_script( | 
|  | 'if ! type nmcli > /dev/null ; then echo "False"; fi') | 
|  |  | 
|  |  | 
|  | class IPv6Test(base.BaseTempestTestCase): | 
|  | credentials = ['primary', 'admin'] | 
|  |  | 
|  | ipv6_ra_mode = 'slaac' | 
|  | ipv6_address_mode = 'slaac' | 
|  |  | 
|  | @classmethod | 
|  | def skip_checks(cls): | 
|  | super(IPv6Test, cls).skip_checks() | 
|  | if not CONF.network_feature_enabled.ipv6: | 
|  | raise cls.skipException("IPv6 is not enabled") | 
|  |  | 
|  | @classmethod | 
|  | @tempest_utils.requires_ext(extension="router", service="network") | 
|  | def resource_setup(cls): | 
|  | super(IPv6Test, cls).resource_setup() | 
|  | cls.reserve_external_subnet_cidrs() | 
|  | cls._setup_basic_resources() | 
|  |  | 
|  | @classmethod | 
|  | def _setup_basic_resources(cls): | 
|  | cls.network = cls.create_network() | 
|  | cls.subnet = cls.create_subnet(cls.network) | 
|  | cls.router = cls.create_router_by_client() | 
|  | cls.create_router_interface(cls.router['id'], cls.subnet['id']) | 
|  | cls.keypair = cls.create_keypair() | 
|  | cls.secgroup = cls.create_security_group( | 
|  | name=data_utils.rand_name('secgroup')) | 
|  | cls.create_loginable_secgroup_rule(secgroup_id=cls.secgroup['id']) | 
|  | cls.create_pingable_secgroup_rule(secgroup_id=cls.secgroup['id']) | 
|  |  | 
|  | def _test_ipv6_address_configured(self, ssh_client, vm, ipv6_port): | 
|  | ipv6_address = ipv6_port['fixed_ips'][0]['ip_address'] | 
|  | ip_command = ip.IPCommand(ssh_client) | 
|  |  | 
|  | def guest_has_address(expected_address): | 
|  | ip_addresses = [a.address for a in ip_command.list_addresses()] | 
|  | for ip_address in ip_addresses: | 
|  | if expected_address in ip_address: | 
|  | return True | 
|  | return False | 
|  | # Set NIC with IPv6 to be UP and wait until IPv6 address | 
|  | # will be configured on this NIC | 
|  | turn_nic6_on(ssh_client, ipv6_port, False) | 
|  | # And check if IPv6 address will be properly configured | 
|  | # on this NIC | 
|  | try: | 
|  | utils.wait_until_true( | 
|  | lambda: guest_has_address(ipv6_address), | 
|  | timeout=60) | 
|  | except utils.WaitTimeout: | 
|  | LOG.debug('Timeout without NM configuration') | 
|  | except (lib_exc.SSHTimeout, | 
|  | ssh_exc.AuthenticationException) as ssh_e: | 
|  | LOG.debug(ssh_e) | 
|  | self._log_console_output([vm]) | 
|  | self._log_local_network_status() | 
|  | raise | 
|  |  | 
|  | if not guest_has_address(ipv6_address): | 
|  | try: | 
|  | # Set NIC with IPv6 to be UP and wait until IPv6 address | 
|  | # will be configured on this NIC | 
|  | turn_nic6_on(ssh_client, ipv6_port) | 
|  | # And check if IPv6 address will be properly configured | 
|  | # on this NIC | 
|  | utils.wait_until_true( | 
|  | lambda: guest_has_address(ipv6_address), | 
|  | timeout=90, | 
|  | exception=RuntimeError( | 
|  | "Timed out waiting for IP address {!r} to be " | 
|  | "configured in the VM {!r}.".format(ipv6_address, | 
|  | vm['id']))) | 
|  | except (lib_exc.SSHTimeout, | 
|  | ssh_exc.AuthenticationException) as ssh_e: | 
|  | LOG.debug(ssh_e) | 
|  | self._log_console_output([vm]) | 
|  | self._log_local_network_status() | 
|  | raise | 
|  |  | 
|  | def _test_ipv6_hotplug(self, ra_mode, address_mode): | 
|  | ipv6_networks = [self.create_network() for _ in range(2)] | 
|  | for net in ipv6_networks: | 
|  | subnet = self.create_subnet( | 
|  | network=net, ip_version=6, | 
|  | ipv6_ra_mode=ra_mode, ipv6_address_mode=address_mode) | 
|  | self.create_router_interface(self.router['id'], subnet['id']) | 
|  |  | 
|  | server_kwargs = { | 
|  | 'flavor_ref': CONF.compute.flavor_ref, | 
|  | 'image_ref': CONF.compute.image_ref, | 
|  | 'key_name': self.keypair['name'], | 
|  | 'networks': [ | 
|  | {'uuid': self.network['id']}, | 
|  | {'uuid': ipv6_networks[0]['id']}], | 
|  | 'security_groups': [{'name': self.secgroup['name']}], | 
|  | } | 
|  | vm = self.create_server(**server_kwargs)['server'] | 
|  | self.wait_for_server_active(vm) | 
|  | self.wait_for_guest_os_ready(vm) | 
|  | ipv4_port = self.client.list_ports( | 
|  | network_id=self.network['id'], | 
|  | device_id=vm['id'])['ports'][0] | 
|  | fip = self.create_floatingip(port=ipv4_port) | 
|  | ssh_client = ssh.Client( | 
|  | fip['floating_ip_address'], CONF.validation.image_ssh_user, | 
|  | pkey=self.keypair['private_key']) | 
|  |  | 
|  | ipv6_port = self.client.list_ports( | 
|  | network_id=ipv6_networks[0]['id'], | 
|  | device_id=vm['id'])['ports'][0] | 
|  | self._test_ipv6_address_configured(ssh_client, vm, ipv6_port) | 
|  |  | 
|  | # Now remove this port IPv6 port from the VM and attach new one | 
|  | self.delete_interface(vm['id'], ipv6_port['id']) | 
|  |  | 
|  | # And plug VM to the second IPv6 network | 
|  | ipv6_port = self.create_port(ipv6_networks[1]) | 
|  | # Add NetworkManager profile with ipv6 eui64 format to guest OS | 
|  | configure_eth_connection_profile_NM(ssh_client) | 
|  | self.create_interface(vm['id'], ipv6_port['id']) | 
|  | ip.wait_for_interface_status( | 
|  | self.os_primary.interfaces_client, vm['id'], | 
|  | ipv6_port['id'], lib_constants.PORT_STATUS_ACTIVE, | 
|  | ssh_client=ssh_client, mac_address=ipv6_port['mac_address']) | 
|  | self._test_ipv6_address_configured(ssh_client, vm, ipv6_port) | 
|  |  | 
|  | @testtools.skipUnless(CONF.network_feature_enabled.ipv6_subnet_attributes, | 
|  | "DHCPv6 attributes are not enabled.") | 
|  | @decorators.idempotent_id('b13e5408-5250-4a42-8e46-6996ce613e91') | 
|  | def test_ipv6_hotplug_slaac(self): | 
|  | self._test_ipv6_hotplug("slaac", "slaac") | 
|  |  | 
|  | @testtools.skipUnless(CONF.network_feature_enabled.ipv6_subnet_attributes, | 
|  | "DHCPv6 attributes are not enabled.") | 
|  | @decorators.idempotent_id('9aaedbc4-986d-42d5-9177-3e721728e7e0') | 
|  | def test_ipv6_hotplug_dhcpv6stateless(self): | 
|  | self._test_ipv6_hotplug("dhcpv6-stateless", "dhcpv6-stateless") |