Merge "Add first VLAN Transparency tests"
diff --git a/neutron_tempest_plugin/common/ip.py b/neutron_tempest_plugin/common/ip.py
index d981770..7b172b0 100644
--- a/neutron_tempest_plugin/common/ip.py
+++ b/neutron_tempest_plugin/common/ip.py
@@ -57,6 +57,20 @@
return shell.execute(command_line, ssh_client=self.ssh_client,
timeout=self.timeout).stdout
+ def configure_vlan(self, addresses, port, vlan_tag, subport_ips):
+ port_device = get_port_device_name(addresses=addresses, port=port)
+ subport_device = '{!s}.{!s}'.format(port_device, vlan_tag)
+ LOG.debug('Configuring VLAN subport interface %r on top of interface '
+ '%r with IPs: %s', subport_device, port_device,
+ ', '.join(subport_ips))
+
+ self.add_link(link=port_device, name=subport_device, link_type='vlan',
+ segmentation_id=vlan_tag)
+ self.set_link(device=subport_device, state='up')
+ for subport_ip in subport_ips:
+ self.add_address(address=subport_ip, device=subport_device)
+ return subport_device
+
def configure_vlan_subport(self, port, subport, vlan_tag, subnets):
addresses = self.list_addresses()
try:
@@ -77,18 +91,19 @@
"Unable to get IP address and subnet prefix lengths for "
"subport")
- port_device = get_port_device_name(addresses=addresses, port=port)
- subport_device = '{!s}.{!s}'.format(port_device, vlan_tag)
- LOG.debug('Configuring VLAN subport interface %r on top of interface '
- '%r with IPs: %s', subport_device, port_device,
- ', '.join(subport_ips))
+ return self.configure_vlan(addresses, port, vlan_tag, subport_ips)
- self.add_link(link=port_device, name=subport_device, link_type='vlan',
- segmentation_id=vlan_tag)
- self.set_link(device=subport_device, state='up')
- for subport_ip in subport_ips:
- self.add_address(address=subport_ip, device=subport_device)
- return subport_device
+ def configure_vlan_transparent(self, port, vlan_tag, ip_addresses):
+ addresses = self.list_addresses()
+ try:
+ subport_device = get_vlan_device_name(addresses, ip_addresses)
+ except ValueError:
+ pass
+ else:
+ LOG.debug('Interface %r already configured.', subport_device)
+ return subport_device
+
+ return self.configure_vlan(addresses, port, vlan_tag, ip_addresses)
def list_namespaces(self):
namespaces_output = self.execute("netns")
@@ -128,6 +143,23 @@
# ip addr add 192.168.1.1/24 dev em1
return self.execute('address', 'add', address, 'dev', device)
+ def delete_address(self, address, device):
+ # ip addr del 192.168.1.1/24 dev em1
+ return self.execute('address', 'del', address, 'dev', device)
+
+ def add_route(self, address, device, gateway=None):
+ if gateway:
+ # ip route add 192.168.1.0/24 via 192.168.22.1 dev em1
+ return self.execute(
+ 'route', 'add', address, 'via', gateway, 'dev', device)
+ else:
+ # ip route add 192.168.1.0/24 dev em1
+ return self.execute('route', 'add', address, 'dev', device)
+
+ def delete_route(self, address, device):
+ # ip route del 192.168.1.0/24 dev em1
+ return self.execute('route', 'del', address, 'dev', device)
+
def list_routes(self, *args):
output = self.execute('route', 'show', *args)
return list(parse_routes(output))
@@ -312,6 +344,15 @@
raise ValueError(msg)
+def get_vlan_device_name(addresses, ip_addresses):
+ for address in list_ip_addresses(addresses=addresses,
+ ip_addresses=ip_addresses):
+ return address.device.name
+
+ msg = "Fixed IPs {0!r} not found on server.".format(' '.join(ip_addresses))
+ raise ValueError(msg)
+
+
def _get_ip_address_prefix_len_pairs(port, subnets):
subnets = {subnet['id']: subnet for subnet in subnets}
for fixed_ip in port['fixed_ips']:
diff --git a/neutron_tempest_plugin/scenario/test_vlan_transparency.py b/neutron_tempest_plugin/scenario/test_vlan_transparency.py
new file mode 100644
index 0000000..d9a529c
--- /dev/null
+++ b/neutron_tempest_plugin/scenario/test_vlan_transparency.py
@@ -0,0 +1,186 @@
+# Copyright (c) 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 oslo_log import log as logging
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+from neutron_tempest_plugin.common import ip
+from neutron_tempest_plugin.common import ssh
+from neutron_tempest_plugin import config
+from neutron_tempest_plugin.scenario import base
+
+
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+MIN_VLAN_ID = 1
+MAX_VLAN_ID = 4094
+
+
+class VlanTransparencyTest(base.BaseTempestTestCase):
+ credentials = ['primary', 'admin']
+ force_tenant_isolation = False
+
+ required_extensions = ['vlan-transparent', 'allowed-address-pairs']
+
+ @classmethod
+ def resource_setup(cls):
+ super(VlanTransparencyTest, 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,
+ vlan_transparent=True)
+ 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)
+ cls.vm_ports = []
+ cls.security_group = cls.create_security_group(name=cls.rand_name)
+ cls.create_loginable_secgroup_rule(cls.security_group['id'])
+
+ if CONF.neutron_plugin_options.default_image_is_advanced:
+ cls.flavor_ref = CONF.compute.flavor_ref
+ cls.image_ref = CONF.compute.image_ref
+ else:
+ cls.flavor_ref = \
+ CONF.neutron_plugin_options.advanced_image_flavor_ref
+ cls.image_ref = CONF.neutron_plugin_options.advanced_image_ref
+
+ @classmethod
+ def skip_checks(cls):
+ super(VlanTransparencyTest, cls).skip_checks()
+ if not (CONF.neutron_plugin_options.advanced_image_ref or
+ CONF.neutron_plugin_options.default_image_is_advanced):
+ raise cls.skipException(
+ 'Advanced image is required to run these tests.')
+
+ def _create_port_and_server(self, index,
+ port_security=True,
+ allowed_address_pairs=None):
+ server_name = 'server-%s-%d' % (self.rand_name, index)
+ port_name = 'port-%s-%d' % (self.rand_name, index)
+ if port_security:
+ sec_groups = [self.security_group['id']]
+ else:
+ sec_groups = None
+ self.vm_ports.append(
+ self.create_port(network=self.network, name=port_name,
+ security_groups=sec_groups,
+ port_security_enabled=port_security,
+ allowed_address_pairs=allowed_address_pairs))
+ return self.create_server(flavor_ref=self.flavor_ref,
+ image_ref=self.image_ref,
+ key_name=self.keypair['name'],
+ networks=[{'port': self.vm_ports[-1]['id']}],
+ name=server_name)['server']
+
+ def _configure_vlan_transparent(self, port, ssh_client,
+ vlan_tag, vlan_ip):
+ ip_command = ip.IPCommand(ssh_client=ssh_client)
+ addresses = ip_command.list_addresses(port=port)
+ port_iface = ip.get_port_device_name(addresses, port)
+ subport_iface = ip_command.configure_vlan_transparent(
+ port=port, vlan_tag=vlan_tag, ip_addresses=[vlan_ip])
+
+ for address in ip_command.list_addresses(ip_addresses=vlan_ip):
+ 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.")
+
+ def _create_ssh_client(self, floating_ip):
+ if CONF.neutron_plugin_options.default_image_is_advanced:
+ username = CONF.validation.image_ssh_user
+ else:
+ username = CONF.neutron_plugin_options.advanced_image_ssh_user
+ return ssh.Client(host=floating_ip['floating_ip_address'],
+ username=username,
+ pkey=self.keypair['private_key'])
+
+ def _test_basic_vlan_transparency_connectivity(
+ self, port_security=True, use_allowed_address_pairs=False):
+ vlan_tag = data_utils.rand_int_id(start=MIN_VLAN_ID, end=MAX_VLAN_ID)
+ vlan_ipmask_template = '192.168.%d.{ip_last_byte}/24' % (vlan_tag %
+ 256)
+ vms = []
+ vlan_ipmasks = []
+ floating_ips = []
+ ssh_clients = []
+
+ for i in range(2):
+ vlan_ipmasks.append(vlan_ipmask_template.format(
+ ip_last_byte=(i + 1) * 10))
+ if use_allowed_address_pairs:
+ allowed_address_pairs = [{'ip_address': vlan_ipmasks[i]}]
+ else:
+ allowed_address_pairs = None
+ vms.append(self._create_port_and_server(
+ index=i,
+ port_security=port_security,
+ allowed_address_pairs=allowed_address_pairs))
+ floating_ips.append(self.create_floatingip(port=self.vm_ports[-1]))
+ ssh_clients.append(
+ self._create_ssh_client(floating_ip=floating_ips[i]))
+
+ self.check_connectivity(
+ host=floating_ips[i]['floating_ip_address'],
+ ssh_client=ssh_clients[i])
+ self._configure_vlan_transparent(port=self.vm_ports[-1],
+ ssh_client=ssh_clients[i],
+ vlan_tag=vlan_tag,
+ vlan_ip=vlan_ipmasks[i])
+
+ if port_security:
+ # Ping from vm0 to vm1 via VLAN interface should fail because
+ # we haven't allowed ICMP
+ self.check_remote_connectivity(
+ ssh_clients[0],
+ vlan_ipmasks[1].split('/')[0],
+ servers=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'])
+
+ # Ping from vm0 to vm1 via VLAN interface should pass because
+ # either port security is disabled or the ICMP sec group rule has been
+ # added
+ self.check_remote_connectivity(
+ ssh_clients[0],
+ vlan_ipmasks[1].split('/')[0],
+ servers=vms)
+ # Ping from vm1 to vm0 and check untagged packets are not dropped
+ self.check_remote_connectivity(
+ ssh_clients[1],
+ self.vm_ports[-2]['fixed_ips'][0]['ip_address'],
+ servers=vms)
+
+ @decorators.idempotent_id('a2694e3a-6d4d-4a23-9fcc-c3ed3ef37b16')
+ def test_vlan_transparent_port_sec_disabled(self):
+ self._test_basic_vlan_transparency_connectivity(
+ port_security=False, use_allowed_address_pairs=False)
+
+ @decorators.idempotent_id('2dd03b4f-9c20-4cda-8c6a-40fa453ec69a')
+ def test_vlan_transparent_allowed_address_pairs(self):
+ self._test_basic_vlan_transparency_connectivity(
+ port_security=True, use_allowed_address_pairs=True)
diff --git a/zuul.d/master_jobs.yaml b/zuul.d/master_jobs.yaml
index 5325f67..ee7c6e3 100644
--- a/zuul.d/master_jobs.yaml
+++ b/zuul.d/master_jobs.yaml
@@ -173,15 +173,21 @@
pre-run: playbooks/linuxbridge-scenario-pre-run.yaml
vars:
network_api_extensions: *api_extensions
+ network_api_extensions_linuxbridge:
+ - vlan-transparent
network_available_features: *available_features
+ # TODO(eolivare): remove VLAN Transparency tests from blacklist
+ # when bug https://bugs.launchpad.net/neutron/+bug/1907548 will be fixed
+ tempest_black_regex: "(^neutron_tempest_plugin.scenario.test_vlan_transparency.VlanTransparencyTest)"
devstack_localrc:
Q_AGENT: linuxbridge
- NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+ NETWORK_API_EXTENSIONS: "{{ (network_api_extensions + network_api_extensions_linuxbridge) | join(',') }}"
devstack_local_conf:
post-config:
$NEUTRON_CONF:
DEFAULT:
enable_dvr: false
+ vlan_transparent: true
AGENT:
debug_iptables_rules: true
# NOTE(slaweq): We can get rid of this hardcoded absolute path when
@@ -190,6 +196,7 @@
/$NEUTRON_CORE_PLUGIN_CONF:
ml2:
type_drivers: flat,vlan,local,vxlan
+ mechanism_drivers: linuxbridge
test-config:
$TEMPEST_CONFIG:
network-feature-enabled:
@@ -204,6 +211,8 @@
timeout: 10000
vars:
network_api_extensions: *api_extensions
+ network_api_extensions_ovn:
+ - vlan-transparent
# TODO(haleyb): Remove IPv6Test from blacklist when
# https://bugs.launchpad.net/neutron/+bug/1881558 is fixed.
# TODO(slaweq): Remove test_trunk_subport_lifecycle test from the
@@ -217,7 +226,7 @@
(^neutron_tempest_plugin.scenario.test_mtu.NetworkWritableMtuTest)"
devstack_localrc:
Q_AGENT: ovn
- NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+ NETWORK_API_EXTENSIONS: "{{ (network_api_extensions + network_api_extensions_ovn) | join(',') }}"
Q_ML2_PLUGIN_MECHANISM_DRIVERS: ovn,logger
Q_ML2_PLUGIN_TYPE_DRIVERS: local,flat,vlan,geneve
Q_ML2_TENANT_NETWORK_TYPE: geneve
@@ -228,6 +237,11 @@
OVN_DBS_LOG_LEVEL: dbg
ENABLE_TLS: True
OVN_IGMP_SNOOPING_ENABLE: True
+ # TODO(eolivare): Remove OVN_BUILD_FROM_SOURCE once vlan-transparency
+ # is included in an ovn released version
+ OVN_BUILD_FROM_SOURCE: True
+ OVN_BRANCH: "v20.12.0"
+ OVS_BRANCH: "branch-2.15"
devstack_services:
br-ex-tcpdump: true
br-int-flows: true
@@ -259,6 +273,7 @@
$NEUTRON_CONF:
DEFAULT:
enable_dvr: false
+ vlan_transparent: true
/$NEUTRON_CORE_PLUGIN_CONF:
ml2:
type_drivers: local,flat,vlan,geneve
diff --git a/zuul.d/ussuri_jobs.yaml b/zuul.d/ussuri_jobs.yaml
index b71a460..9cc0621 100644
--- a/zuul.d/ussuri_jobs.yaml
+++ b/zuul.d/ussuri_jobs.yaml
@@ -163,6 +163,7 @@
OVN_BUILD_MODULES: True
# TODO(skaplons): v2.13.1 is incompatible with kernel 4.15.0-118, sticking to commit hash until new v2.13 tag is created
OVS_BRANCH: 0047ca3a0290f1ef954f2c76b31477cf4b9755f5
+ OVN_BRANCH: "v20.03.0"
- job:
name: neutron-tempest-plugin-dvr-multinode-scenario-ussuri