Add first VLAN Transparency tests

These tests are only executed if the vlan-transparent extension is installed
Advanced images are required because VLANs need to be configured on
the servers, which is not possible with cirros
Connectivity between servers via VLAN interface is verified

Functions add_route, delete_route and delete_address are added to class
IPCommand because they are needed for some VLAN Transparency downstream tests

Change-Id: I448203ead31f17a51f756667f6b3fc8e70a77ed2
This commit is contained in:
Eduardo Olivares 2020-12-01 21:13:45 +01:00
parent 5ff3a43a28
commit 088707bf5c
4 changed files with 256 additions and 13 deletions

View File

@ -57,6 +57,20 @@ class IPCommand(object):
return shell.execute(command_line, ssh_client=self.ssh_client, return shell.execute(command_line, ssh_client=self.ssh_client,
timeout=self.timeout).stdout 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): def configure_vlan_subport(self, port, subport, vlan_tag, subnets):
addresses = self.list_addresses() addresses = self.list_addresses()
try: try:
@ -77,18 +91,19 @@ class IPCommand(object):
"Unable to get IP address and subnet prefix lengths for " "Unable to get IP address and subnet prefix lengths for "
"subport") "subport")
port_device = get_port_device_name(addresses=addresses, port=port) return self.configure_vlan(addresses, port, vlan_tag, subport_ips)
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', def configure_vlan_transparent(self, port, vlan_tag, ip_addresses):
segmentation_id=vlan_tag) addresses = self.list_addresses()
self.set_link(device=subport_device, state='up') try:
for subport_ip in subport_ips: subport_device = get_vlan_device_name(addresses, ip_addresses)
self.add_address(address=subport_ip, device=subport_device) except ValueError:
return subport_device 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): def list_namespaces(self):
namespaces_output = self.execute("netns") namespaces_output = self.execute("netns")
@ -128,6 +143,23 @@ class IPCommand(object):
# ip addr add 192.168.1.1/24 dev em1 # ip addr add 192.168.1.1/24 dev em1
return self.execute('address', 'add', address, 'dev', device) 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): def list_routes(self, *args):
output = self.execute('route', 'show', *args) output = self.execute('route', 'show', *args)
return list(parse_routes(output)) return list(parse_routes(output))
@ -312,6 +344,15 @@ def get_port_device_name(addresses, port):
raise ValueError(msg) 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): def _get_ip_address_prefix_len_pairs(port, subnets):
subnets = {subnet['id']: subnet for subnet in subnets} subnets = {subnet['id']: subnet for subnet in subnets}
for fixed_ip in port['fixed_ips']: for fixed_ip in port['fixed_ips']:

View File

@ -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)

View File

@ -173,15 +173,21 @@
pre-run: playbooks/linuxbridge-scenario-pre-run.yaml pre-run: playbooks/linuxbridge-scenario-pre-run.yaml
vars: vars:
network_api_extensions: *api_extensions network_api_extensions: *api_extensions
network_api_extensions_linuxbridge:
- vlan-transparent
network_available_features: *available_features 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: devstack_localrc:
Q_AGENT: linuxbridge Q_AGENT: linuxbridge
NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}" NETWORK_API_EXTENSIONS: "{{ (network_api_extensions + network_api_extensions_linuxbridge) | join(',') }}"
devstack_local_conf: devstack_local_conf:
post-config: post-config:
$NEUTRON_CONF: $NEUTRON_CONF:
DEFAULT: DEFAULT:
enable_dvr: false enable_dvr: false
vlan_transparent: true
AGENT: AGENT:
debug_iptables_rules: true debug_iptables_rules: true
# NOTE(slaweq): We can get rid of this hardcoded absolute path when # NOTE(slaweq): We can get rid of this hardcoded absolute path when
@ -190,6 +196,7 @@
/$NEUTRON_CORE_PLUGIN_CONF: /$NEUTRON_CORE_PLUGIN_CONF:
ml2: ml2:
type_drivers: flat,vlan,local,vxlan type_drivers: flat,vlan,local,vxlan
mechanism_drivers: linuxbridge
test-config: test-config:
$TEMPEST_CONFIG: $TEMPEST_CONFIG:
network-feature-enabled: network-feature-enabled:
@ -204,6 +211,8 @@
timeout: 10000 timeout: 10000
vars: vars:
network_api_extensions: *api_extensions network_api_extensions: *api_extensions
network_api_extensions_ovn:
- vlan-transparent
# TODO(haleyb): Remove IPv6Test from blacklist when # TODO(haleyb): Remove IPv6Test from blacklist when
# https://bugs.launchpad.net/neutron/+bug/1881558 is fixed. # https://bugs.launchpad.net/neutron/+bug/1881558 is fixed.
# TODO(slaweq): Remove test_trunk_subport_lifecycle test from the # TODO(slaweq): Remove test_trunk_subport_lifecycle test from the
@ -217,7 +226,7 @@
(^neutron_tempest_plugin.scenario.test_mtu.NetworkWritableMtuTest)" (^neutron_tempest_plugin.scenario.test_mtu.NetworkWritableMtuTest)"
devstack_localrc: devstack_localrc:
Q_AGENT: ovn 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_MECHANISM_DRIVERS: ovn,logger
Q_ML2_PLUGIN_TYPE_DRIVERS: local,flat,vlan,geneve Q_ML2_PLUGIN_TYPE_DRIVERS: local,flat,vlan,geneve
Q_ML2_TENANT_NETWORK_TYPE: geneve Q_ML2_TENANT_NETWORK_TYPE: geneve
@ -228,6 +237,11 @@
OVN_DBS_LOG_LEVEL: dbg OVN_DBS_LOG_LEVEL: dbg
ENABLE_TLS: True ENABLE_TLS: True
OVN_IGMP_SNOOPING_ENABLE: 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: devstack_services:
br-ex-tcpdump: true br-ex-tcpdump: true
br-int-flows: true br-int-flows: true
@ -259,6 +273,7 @@
$NEUTRON_CONF: $NEUTRON_CONF:
DEFAULT: DEFAULT:
enable_dvr: false enable_dvr: false
vlan_transparent: true
/$NEUTRON_CORE_PLUGIN_CONF: /$NEUTRON_CORE_PLUGIN_CONF:
ml2: ml2:
type_drivers: local,flat,vlan,geneve type_drivers: local,flat,vlan,geneve

View File

@ -163,6 +163,7 @@
OVN_BUILD_MODULES: True 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 # 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 OVS_BRANCH: 0047ca3a0290f1ef954f2c76b31477cf4b9755f5
OVN_BRANCH: "v20.03.0"
- job: - job:
name: neutron-tempest-plugin-dvr-multinode-scenario-ussuri name: neutron-tempest-plugin-dvr-multinode-scenario-ussuri