|  | # 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. | 
|  |  | 
|  | import collections | 
|  |  | 
|  | from neutron_lib import constants | 
|  | from neutron_lib.utils import test | 
|  | from oslo_log import log as logging | 
|  | from tempest.common import utils as tutils | 
|  | from tempest.lib.common.utils import data_utils | 
|  | from tempest.lib import decorators | 
|  | 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 | 
|  |  | 
|  |  | 
|  | LOG = logging.getLogger(__name__) | 
|  | CONF = config.CONF | 
|  |  | 
|  |  | 
|  | ServerWithTrunkPort = collections.namedtuple( | 
|  | 'ServerWithTrunkPort', | 
|  | ['port', 'subport', 'trunk', 'floating_ip', 'server', | 
|  | 'ssh_client']) | 
|  |  | 
|  |  | 
|  | class TrunkTest(base.BaseTempestTestCase): | 
|  | credentials = ['primary', 'admin'] | 
|  | force_tenant_isolation = False | 
|  |  | 
|  | @classmethod | 
|  | @tutils.requires_ext(extension="trunk", service="network") | 
|  | def resource_setup(cls): | 
|  | super(TrunkTest, cls).resource_setup() | 
|  | # setup basic topology for servers we can log into | 
|  | cls.rand_name = data_utils.rand_name( | 
|  | cls.__name__.rsplit('.', 1)[-1]) | 
|  | cls.network = cls.create_network(name=cls.rand_name) | 
|  | cls.subnet = cls.create_subnet(network=cls.network, | 
|  | name=cls.rand_name) | 
|  | cls.router = cls.create_router_by_client() | 
|  | cls.create_router_interface(cls.router['id'], cls.subnet['id']) | 
|  | cls.keypair = cls.create_keypair(name=cls.rand_name) | 
|  |  | 
|  | def setUp(self): | 
|  | super(TrunkTest, self).setUp() | 
|  | self.security_group = self.create_security_group(name=self.rand_name) | 
|  | self.create_loginable_secgroup_rule(self.security_group['id']) | 
|  |  | 
|  | def _create_server_with_network(self, network, use_advanced_image=False): | 
|  | port = self._create_server_port(network=network) | 
|  | floating_ip = self.create_floatingip(port=port) | 
|  | ssh_client = self._create_ssh_client( | 
|  | floating_ip=floating_ip, use_advanced_image=use_advanced_image) | 
|  | server = self._create_server(port=port, | 
|  | use_advanced_image=use_advanced_image) | 
|  | return ServerWithTrunkPort(port=port, subport=None, trunk=None, | 
|  | floating_ip=floating_ip, server=server, | 
|  | ssh_client=ssh_client) | 
|  |  | 
|  | def _create_server_with_trunk_port(self, subport_network=None, | 
|  | segmentation_id=None, | 
|  | use_advanced_image=False): | 
|  | port = self._create_server_port() | 
|  | floating_ip = self.create_floatingip(port=port) | 
|  | ssh_client = self._create_ssh_client( | 
|  | floating_ip=floating_ip, use_advanced_image=use_advanced_image) | 
|  |  | 
|  | subport = None | 
|  | subports = None | 
|  | if subport_network: | 
|  | subport = self._create_server_port( | 
|  | network=subport_network, mac_address=port['mac_address']) | 
|  | subports = [{'port_id': subport['id'], | 
|  | 'segmentation_type': 'vlan', | 
|  | 'segmentation_id': segmentation_id}] | 
|  | trunk = self.create_trunk(port=port, subports=subports) | 
|  |  | 
|  | server = self._create_server(port=port, | 
|  | use_advanced_image=use_advanced_image) | 
|  | return ServerWithTrunkPort(port=port, subport=subport, trunk=trunk, | 
|  | floating_ip=floating_ip, server=server, | 
|  | ssh_client=ssh_client) | 
|  |  | 
|  | def _create_advanced_servers_with_trunk_port(self, num_servers=1, | 
|  | subport_network=None, | 
|  | segmentation_id=None, | 
|  | vlan_subnet=None, | 
|  | use_advanced_image=False): | 
|  | server_list = [] | 
|  | for _ in range(0, num_servers): | 
|  | vm = self._create_server_with_trunk_port( | 
|  | subport_network, | 
|  | segmentation_id, | 
|  | use_advanced_image) | 
|  | server_list.append(vm) | 
|  | self._configure_vlan_subport( | 
|  | vm=vm, | 
|  | vlan_tag=segmentation_id, | 
|  | vlan_subnet=vlan_subnet) | 
|  |  | 
|  | return server_list | 
|  |  | 
|  | def _check_servers_remote_connectivity(self, vms=None, | 
|  | should_succeed=True): | 
|  | self.check_remote_connectivity( | 
|  | vms[0].ssh_client, | 
|  | vms[1].subport['fixed_ips'][0]['ip_address'], | 
|  | should_succeed=should_succeed) | 
|  |  | 
|  | def _create_server_port(self, network=None, **params): | 
|  | network = network or self.network | 
|  | return self.create_port(network=network, name=self.rand_name, | 
|  | security_groups=[self.security_group['id']], | 
|  | **params) | 
|  |  | 
|  | def _create_server(self, port, use_advanced_image=False, **params): | 
|  | if use_advanced_image: | 
|  | flavor_ref = CONF.neutron_plugin_options.advanced_image_flavor_ref | 
|  | image_ref = CONF.neutron_plugin_options.advanced_image_ref | 
|  | else: | 
|  | flavor_ref = CONF.compute.flavor_ref | 
|  | image_ref = CONF.compute.image_ref | 
|  | return self.create_server(flavor_ref=flavor_ref, | 
|  | image_ref=image_ref, | 
|  | key_name=self.keypair['name'], | 
|  | networks=[{'port': port['id']}], | 
|  | **params)['server'] | 
|  |  | 
|  | def _show_port(self, port, update=False): | 
|  | observed = self.client.show_port(port['id'])['port'] | 
|  | if update: | 
|  | port.update(observed) | 
|  | return observed | 
|  |  | 
|  | def _show_trunk(self, trunk, update=False): | 
|  | observed = self.client.show_trunk(trunk['id'])['trunk'] | 
|  | if update: | 
|  | trunk.update(observed) | 
|  | return observed | 
|  |  | 
|  | def _is_trunk_status(self, trunk, status, update=False): | 
|  | return self._show_trunk(trunk, update)['status'] == status | 
|  |  | 
|  | def _is_port_status(self, port, status, update=False): | 
|  | return self._show_port(port, update)['status'] == status | 
|  |  | 
|  | def _wait_for_port(self, port, status=constants.ACTIVE): | 
|  | utils.wait_until_true( | 
|  | lambda: self._is_port_status(port, status), | 
|  | exception=RuntimeError( | 
|  | "Timed out waiting for port {!r} to transition to " | 
|  | "status {!r}.".format(port['id'], status))) | 
|  |  | 
|  | def _wait_for_trunk(self, trunk, status=constants.ACTIVE): | 
|  | utils.wait_until_true( | 
|  | lambda: self._is_trunk_status(trunk, status), | 
|  | exception=RuntimeError( | 
|  | "Timed out waiting for trunk {!r} to transition to " | 
|  | "status {!r}.".format(trunk['id'], status))) | 
|  |  | 
|  | def _create_ssh_client(self, floating_ip, use_advanced_image=False): | 
|  | if use_advanced_image: | 
|  | username = CONF.neutron_plugin_options.advanced_image_ssh_user | 
|  | else: | 
|  | username = CONF.validation.image_ssh_user | 
|  | return ssh.Client(host=floating_ip['floating_ip_address'], | 
|  | username=username, | 
|  | pkey=self.keypair['private_key']) | 
|  |  | 
|  | def _assert_has_ssh_connectivity(self, ssh_client): | 
|  | ssh_client.exec_command("true") | 
|  |  | 
|  | def _configure_vlan_subport(self, vm, vlan_tag, vlan_subnet): | 
|  | self.wait_for_server_active(server=vm.server) | 
|  | self.wait_for_guest_os_ready(vm.server) | 
|  | self._wait_for_trunk(trunk=vm.trunk) | 
|  | self._wait_for_port(port=vm.port) | 
|  | self._wait_for_port(port=vm.subport) | 
|  | self.check_connectivity( | 
|  | ssh_client=vm.ssh_client, | 
|  | servers=[vm.server]) | 
|  |  | 
|  | ip_command = ip.IPCommand(ssh_client=vm.ssh_client) | 
|  | for address in ip_command.list_addresses(port=vm.port): | 
|  | port_iface = address.device.name | 
|  | break | 
|  | else: | 
|  | self.fail("Parent port fixed IP not found on server.") | 
|  |  | 
|  | subport_iface = ip_command.configure_vlan_subport( | 
|  | port=vm.port, subport=vm.subport, vlan_tag=vlan_tag, | 
|  | subnets=[vlan_subnet]) | 
|  | for address in ip_command.list_addresses(port=vm.subport): | 
|  | self.assertEqual(subport_iface, address.device.name) | 
|  | self.assertEqual(port_iface, address.device.parent) | 
|  | break | 
|  | else: | 
|  | self.fail("Sub-port fixed IP not found on server.") | 
|  |  | 
|  | @decorators.idempotent_id('bb13fe28-f152-4000-8131-37890a40c79e') | 
|  | def test_trunk_subport_lifecycle(self): | 
|  | """Test trunk creation and subport transition to ACTIVE status. | 
|  |  | 
|  | This is a basic test for the trunk extension to ensure that we | 
|  | can create a trunk, attach it to a server, add/remove subports, | 
|  | while ensuring the status transitions as appropriate. | 
|  |  | 
|  | This test does not assert any dataplane behavior for the subports. | 
|  | It's just a high-level check to ensure the agents claim to have | 
|  | wired the port correctly and that the trunk port itself maintains | 
|  | connectivity. | 
|  | """ | 
|  | vm1 = self._create_server_with_trunk_port() | 
|  | vm2 = self._create_server_with_trunk_port() | 
|  | for vm in (vm1, vm2): | 
|  | self.wait_for_server_active(server=vm.server) | 
|  | self.wait_for_guest_os_ready(vm.server) | 
|  | self._wait_for_trunk(vm.trunk) | 
|  | self._assert_has_ssh_connectivity(vm.ssh_client) | 
|  |  | 
|  | # create a few more networks and ports for subports | 
|  | # check limit of networks per project | 
|  | segment_ids = range( | 
|  | 3, 3 + CONF.neutron_plugin_options.max_networks_per_project) | 
|  | tagged_networks = [self.create_network() for _ in segment_ids] | 
|  | tagged_ports = [self.create_port(network=network) | 
|  | for network in tagged_networks] | 
|  | subports = [{'port_id': tagged_ports[i]['id'], | 
|  | 'segmentation_type': 'vlan', | 
|  | 'segmentation_id': segment_id} | 
|  | for i, segment_id in enumerate(segment_ids)] | 
|  |  | 
|  | # add all subports to server1 | 
|  | self.client.add_subports(vm1.trunk['id'], subports) | 
|  | self._wait_for_trunk(vm1.trunk) | 
|  | for port in tagged_ports: | 
|  | self._wait_for_port(port) | 
|  |  | 
|  | # ensure main data-plane wasn't interrupted | 
|  | self._assert_has_ssh_connectivity(vm1.ssh_client) | 
|  |  | 
|  | # move subports over to other server | 
|  | self.client.remove_subports(vm1.trunk['id'], subports) | 
|  | # ensure all subports go down | 
|  | for port in tagged_ports: | 
|  | self._wait_for_port(port, status=constants.DOWN) | 
|  |  | 
|  | self.client.add_subports(vm2.trunk['id'], subports) | 
|  |  | 
|  | # wait for both trunks to go back to ACTIVE | 
|  | for vm in [vm1, vm2]: | 
|  | self._wait_for_trunk(vm.trunk) | 
|  |  | 
|  | # ensure subports come up on other trunk | 
|  | for port in tagged_ports: | 
|  | self._wait_for_port(port) | 
|  |  | 
|  | # final connectivity check | 
|  | for vm in [vm1, vm2]: | 
|  | self._wait_for_trunk(vm.trunk) | 
|  | self._assert_has_ssh_connectivity(vm1.ssh_client) | 
|  |  | 
|  | @test.unstable_test("bug 2033887 / bug 2024160") | 
|  | @testtools.skipUnless( | 
|  | (CONF.neutron_plugin_options.advanced_image_ref or | 
|  | CONF.neutron_plugin_options.default_image_is_advanced), | 
|  | "Advanced image is required to run this test.") | 
|  | @testtools.skipUnless( | 
|  | (CONF.neutron_plugin_options.reboots_in_test > 0), | 
|  | "Number of reboots > 0 is reqired for this test") | 
|  | @decorators.idempotent_id('a8a02c9b-b453-49b5-89a2-cce7da6680fb') | 
|  | def test_subport_connectivity_soft_reboot(self): | 
|  | vlan_tag = 10 | 
|  | vlan_network = self.create_network() | 
|  | vlan_subnet = self.create_subnet(network=vlan_network, gateway=None) | 
|  | use_advanced_image = ( | 
|  | not CONF.neutron_plugin_options.default_image_is_advanced) | 
|  |  | 
|  | # allow intra-security-group traffic | 
|  | sg_rule = self.create_pingable_secgroup_rule(self.security_group['id']) | 
|  | self.addCleanup( | 
|  | self.os_primary.network_client.delete_security_group_rule, | 
|  | sg_rule['id']) | 
|  |  | 
|  | vms = self._create_advanced_servers_with_trunk_port( | 
|  | num_servers=2, | 
|  | subport_network=vlan_network, | 
|  | segmentation_id=vlan_tag, | 
|  | vlan_subnet=vlan_subnet, | 
|  | use_advanced_image=use_advanced_image) | 
|  | # check remote connectivity true before reboots | 
|  | self._check_servers_remote_connectivity(vms=vms) | 
|  | client = self.os_tempest.compute.ServersClient() | 
|  | for _ in range(CONF.neutron_plugin_options.reboots_in_test): | 
|  | client.reboot_server(vms[1].server['id'], | 
|  | **{'type': 'SOFT'}) | 
|  | self.wait_for_server_active(vms[1].server) | 
|  | self._configure_vlan_subport(vm=vms[1], | 
|  | vlan_tag=vlan_tag, | 
|  | vlan_subnet=vlan_subnet) | 
|  | self._check_servers_remote_connectivity(vms=vms) | 
|  |  | 
|  | @testtools.skipUnless( | 
|  | (CONF.neutron_plugin_options.advanced_image_ref or | 
|  | CONF.neutron_plugin_options.default_image_is_advanced), | 
|  | "Advanced image is required to run this test.") | 
|  | @decorators.idempotent_id('a8a02c9b-b453-49b5-89a2-cce7da66bbcb') | 
|  | def test_subport_connectivity(self): | 
|  | vlan_tag = 10 | 
|  | vlan_network = self.create_network() | 
|  | vlan_subnet = self.create_subnet(network=vlan_network, gateway=None) | 
|  | use_advanced_image = ( | 
|  | not CONF.neutron_plugin_options.default_image_is_advanced) | 
|  | vms = self._create_advanced_servers_with_trunk_port( | 
|  | num_servers=2, | 
|  | subport_network=vlan_network, | 
|  | segmentation_id=vlan_tag, | 
|  | vlan_subnet=vlan_subnet, | 
|  | use_advanced_image=use_advanced_image) | 
|  | # Ping from server1 to server2 via VLAN interface should fail because | 
|  | # we haven't allowed ICMP | 
|  | self._check_servers_remote_connectivity(vms=vms, | 
|  | should_succeed=False) | 
|  | # allow intra-security-group traffic | 
|  | sg_rule = self.create_pingable_secgroup_rule(self.security_group['id']) | 
|  | self.addCleanup( | 
|  | self.os_primary.network_client.delete_security_group_rule, | 
|  | sg_rule['id']) | 
|  | self._check_servers_remote_connectivity(vms=vms) | 
|  |  | 
|  | @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration, | 
|  | 'Cold migration is not available.') | 
|  | @testtools.skipUnless(CONF.compute.min_compute_nodes > 1, | 
|  | 'Less than 2 compute nodes, skipping multinode ' | 
|  | 'tests.') | 
|  | @testtools.skipUnless( | 
|  | (CONF.neutron_plugin_options.advanced_image_ref or | 
|  | CONF.neutron_plugin_options.default_image_is_advanced), | 
|  | "Advanced image is required to run this test.") | 
|  | @decorators.attr(type='slow') | 
|  | @decorators.idempotent_id('ecd7de30-1c90-4280-b97c-1bed776d5d07') | 
|  | def test_trunk_vm_migration(self): | 
|  | '''Test connectivity after migration of the server with trunk | 
|  |  | 
|  | A successfully migrated server shows a VERIFY_RESIZE status that | 
|  | requires confirmation. Need to reconfigure VLAN interface on server | 
|  | side after migration is finished as the configuration doesn't survive | 
|  | the reboot. | 
|  | ''' | 
|  | vlan_tag = 10 | 
|  | vlan_network = self.create_network() | 
|  | vlan_subnet = self.create_subnet(vlan_network) | 
|  | sg_rule = self.create_pingable_secgroup_rule(self.security_group['id']) | 
|  | self.addCleanup( | 
|  | self.os_primary.network_client.delete_security_group_rule, | 
|  | sg_rule['id']) | 
|  |  | 
|  | use_advanced_image = ( | 
|  | not CONF.neutron_plugin_options.default_image_is_advanced) | 
|  | servers = {} | 
|  | for role in ['migrate', 'connection_test']: | 
|  | servers[role] = self._create_server_with_trunk_port( | 
|  | subport_network=vlan_network, | 
|  | segmentation_id=vlan_tag, | 
|  | use_advanced_image=use_advanced_image) | 
|  | for role in ['migrate', 'connection_test']: | 
|  | self.wait_for_server_active(servers[role].server) | 
|  | self.wait_for_guest_os_ready(servers[role].server) | 
|  | self._configure_vlan_subport(vm=servers[role], | 
|  | vlan_tag=vlan_tag, | 
|  | vlan_subnet=vlan_subnet) | 
|  |  | 
|  | self.check_remote_connectivity( | 
|  | servers['connection_test'].ssh_client, | 
|  | servers['migrate'].subport['fixed_ips'][0]['ip_address']) | 
|  |  | 
|  | client = self.os_admin.compute.ServersClient() | 
|  | client.migrate_server(servers['migrate'].server['id']) | 
|  | self.wait_for_server_status(servers['migrate'].server, | 
|  | 'VERIFY_RESIZE') | 
|  | client.confirm_resize_server(servers['migrate'].server['id']) | 
|  | self._configure_vlan_subport(vm=servers['migrate'], | 
|  | vlan_tag=vlan_tag, | 
|  | vlan_subnet=vlan_subnet) | 
|  |  | 
|  | self.check_remote_connectivity( | 
|  | servers['connection_test'].ssh_client, | 
|  | servers['migrate'].subport['fixed_ips'][0]['ip_address']) | 
|  |  | 
|  | @testtools.skipUnless( | 
|  | (CONF.neutron_plugin_options.advanced_image_ref or | 
|  | CONF.neutron_plugin_options.default_image_is_advanced), | 
|  | "Advanced image is required to run this test.") | 
|  | @testtools.skipUnless( | 
|  | CONF.neutron_plugin_options.q_agent == "linuxbridge", | 
|  | "Linux bridge agent is required to run this test.") | 
|  | @decorators.idempotent_id('d61cbdf6-1896-491c-b4b4-871caf7fbffe') | 
|  | def test_parent_port_connectivity_after_trunk_deleted_lb(self): | 
|  | vlan_tag = 10 | 
|  | vlan_network = self.create_network() | 
|  | vlan_subnet = self.create_subnet(vlan_network) | 
|  | self.create_router_interface(self.router['id'], vlan_subnet['id']) | 
|  |  | 
|  | use_advanced_image = ( | 
|  | not CONF.neutron_plugin_options.default_image_is_advanced) | 
|  |  | 
|  | # Create servers | 
|  | trunk_network_server = self._create_server_with_trunk_port( | 
|  | subport_network=vlan_network, | 
|  | segmentation_id=vlan_tag, | 
|  | use_advanced_image=use_advanced_image) | 
|  | normal_network_server = self._create_server_with_network(self.network) | 
|  | vlan_network_server = self._create_server_with_network(vlan_network) | 
|  | vms = [normal_network_server, vlan_network_server] | 
|  |  | 
|  | self._configure_vlan_subport(vm=trunk_network_server, | 
|  | vlan_tag=vlan_tag, | 
|  | vlan_subnet=vlan_subnet) | 
|  | for vm in vms: | 
|  | self.wait_for_server_active(vm.server) | 
|  | self.wait_for_guest_os_ready(vm.server) | 
|  |  | 
|  | # allow ICMP traffic | 
|  | sg_rule = self.create_pingable_secgroup_rule(self.security_group['id']) | 
|  | self.addCleanup( | 
|  | self.os_primary.network_client.delete_security_group_rule, | 
|  | sg_rule['id']) | 
|  |  | 
|  | # Ping from trunk_network_server to normal_network_server | 
|  | # via parent port | 
|  | self.check_remote_connectivity( | 
|  | trunk_network_server.ssh_client, | 
|  | normal_network_server.port['fixed_ips'][0]['ip_address'], | 
|  | should_succeed=True, | 
|  | servers=vms) | 
|  |  | 
|  | # Ping from trunk_network_server to vlan_network_server via VLAN | 
|  | # interface should success | 
|  | self.check_remote_connectivity( | 
|  | trunk_network_server.ssh_client, | 
|  | vlan_network_server.port['fixed_ips'][0]['ip_address'], | 
|  | should_succeed=True, | 
|  | servers=vms) | 
|  |  | 
|  | # Delete the trunk | 
|  | self.delete_trunk( | 
|  | trunk_network_server.trunk, | 
|  | detach_parent_port=False) | 
|  | LOG.debug("Trunk %s is deleted.", | 
|  | trunk_network_server.trunk['id']) | 
|  |  | 
|  | # Ping from trunk_network_server to normal_network_server | 
|  | # via parent port success after trunk deleted | 
|  | self.check_remote_connectivity( | 
|  | trunk_network_server.ssh_client, | 
|  | normal_network_server.port['fixed_ips'][0]['ip_address'], | 
|  | should_succeed=True, | 
|  | servers=vms) | 
|  |  | 
|  | # Ping from trunk_network_server to vlan_network_server via VLAN | 
|  | # interface should fail after trunk deleted | 
|  | self.check_remote_connectivity( | 
|  | trunk_network_server.ssh_client, | 
|  | vlan_network_server.port['fixed_ips'][0]['ip_address'], | 
|  | should_succeed=False) |