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:
parent
5ff3a43a28
commit
088707bf5c
@ -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']:
|
||||||
|
186
neutron_tempest_plugin/scenario/test_vlan_transparency.py
Normal file
186
neutron_tempest_plugin/scenario/test_vlan_transparency.py
Normal 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)
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user