# 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 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'] 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_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 get " "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 get " "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_trunk(trunk=vm.trunk) self._wait_for_port(port=vm.port) self._wait_for_port(port=vm.subport) 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_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) @testtools.skipUnless(CONF.neutron_plugin_options.advanced_image_ref, "Advanced image is required to run this test.") @decorators.idempotent_id('a8a02c9b-b453-49b5-89a2-cce7da66aafb') def test_subport_connectivity(self): vlan_tag = 10 vlan_network = self.create_network() vlan_subnet = self.create_subnet(network=vlan_network, gateway=None) vm1 = self._create_server_with_trunk_port(subport_network=vlan_network, segmentation_id=vlan_tag, use_advanced_image=True) vm2 = self._create_server_with_trunk_port(subport_network=vlan_network, segmentation_id=vlan_tag, use_advanced_image=True) for vm in [vm1, vm2]: self._configure_vlan_subport(vm=vm, vlan_tag=vlan_tag, vlan_subnet=vlan_subnet) # Ping from server1 to server2 via VLAN interface should fail because # we haven't allowed ICMP self.check_remote_connectivity( vm1.ssh_client, vm2.subport['fixed_ips'][0]['ip_address'], should_succeed=False) # allow intra-security-group traffic self.create_pingable_secgroup_rule(self.security_group['id']) self.check_remote_connectivity( vm1.ssh_client, vm2.subport['fixed_ips'][0]['ip_address']) @testtools.skipUnless( CONF.neutron_plugin_options.advanced_image_ref, "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']) # Create servers trunk_network_server = self._create_server_with_trunk_port( subport_network=vlan_network, segmentation_id=vlan_tag, use_advanced_image=True) normal_network_server = self._create_server_with_network(self.network) vlan_network_server = self._create_server_with_network(vlan_network) self._configure_vlan_subport(vm=trunk_network_server, vlan_tag=vlan_tag, vlan_subnet=vlan_subnet) for vm in [normal_network_server, vlan_network_server]: self.wait_for_server_active(vm.server) # allow ICMP traffic self.create_pingable_secgroup_rule(self.security_group['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) # 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) # 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) # 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)