Use Ubuntu server with a working trunk VLAN subport

- It adds default vlan_id neutron config option
- Add a VLAN device to customized Ubuntu image configured
  to use DHCP
- Make Ubuntu server to use a working trunk VLAN subport
  connected to a new special network
- It pings such VLAN server port from a special CirrOS server connected
  to the new special network
- It fixes existing trunk test by providing a proven working
  vlan connection.

Change-Id: I07053dd264f26e7b3959f4ab1c7a6e054a702e77
This commit is contained in:
Federico Ressi 2021-09-21 11:37:53 +02:00
parent 094d3c0826
commit 4a275f1625
12 changed files with 320 additions and 112 deletions

View File

@ -65,15 +65,9 @@ OPTIONS = [
cfg.IntOpt('dscp_mark',
default=40,
help="The DSCP marking value for the QoS Policy Rule"),
cfg.StrOpt('trunk_subport_segmentation_type',
default='vlan',
help="Trunk subport segmentation type"),
cfg.IntOpt('trunk_subport_segmentation_id',
cfg.IntOpt('vlan_id',
default=101,
help="Trunk subport segmentation ID"),
cfg.StrOpt('trunk_subport_subnet_cidr',
default='192.168.101.0/24',
help="The CIDR block for trunk subport subnet")
help="VLAN trunk subport segmentation ID"),
]

View File

@ -25,12 +25,13 @@ from tobiko.openstack.stacks import _nova
from tobiko.openstack.stacks import _octavia
from tobiko.openstack.stacks import _qos
from tobiko.openstack.stacks import _ubuntu
from tobiko.openstack.stacks import _vlan
CentosFlavorStackFixture = _centos.CentosFlavorStackFixture
CentosImageFixture = _centos.CentosImageFixture
CentosServerStackFixture = _centos.CentosServerStackFixture
Centos7ServerStackFixture = _centos.Centos7ServerStackFixture
CentosTrunkServerStackFixture = _centos.CentosTrunkServerStackFixture
CirrosFlavorStackFixture = _cirros.CirrosFlavorStackFixture
CirrosImageFixture = _cirros.CirrosImageFixture
@ -52,7 +53,6 @@ FedoraServerStackFixture = _fedora.FedoraServerStackFixture
RedHatFlavorStackFixture = _redhat.RedHatFlavorStackFixture
RhelImageFixture = _redhat.RhelImageFixture
RedHatServerStackFixture = _redhat.RedHatServerStackFixture
RedHatTrunkServerStackFixture = _redhat.RedHatTrunkServerStackFixture
L3haNetworkStackFixture = _l3ha.L3haNetworkStackFixture
L3haServerStackFixture = _l3ha.L3haServerStackFixture
@ -80,6 +80,16 @@ ServerGroupStackFixture = _nova.ServerGroupStackFixture
AffinityServerGroupStackFixture = _nova.AffinityServerGroupStackFixture
AntiAffinityServerGroupStackFixture = _nova.AntiAffinityServerGroupStackFixture
OctaviaLoadbalancerStackFixture = _octavia.OctaviaLoadbalancerStackFixture
OctaviaListenerStackFixture = _octavia.OctaviaListenerStackFixture
OctaviaPoolStackFixture = _octavia.OctaviaPoolStackFixture
OctaviaMemberServerStackFixture = _octavia.OctaviaMemberServerStackFixture
OctaviaServerStackFixture = _octavia.OctaviaServerStackFixture
OctaviaClientServerStackFixture = _octavia.OctaviaClientServerStackFixture
OctaviaOtherServerStackFixture = _octavia.OctaviaOtherServerStackFixture
OctaviaOtherMemberServerStackFixture = (
_octavia.OctaviaOtherMemberServerStackFixture)
QosNetworkStackFixture = _qos.QosNetworkStackFixture
QosPolicyStackFixture = _qos.QosPolicyStackFixture
QosServerStackFixture = _qos.QosServerStackFixture
@ -91,12 +101,5 @@ UbuntuServerStackFixture = _ubuntu.UbuntuServerStackFixture
UbuntuMinimalServerStackFixture = _ubuntu.UbuntuMinimalServerStackFixture
UbuntuExternalServerStackFixture = _ubuntu.UbuntuExternalServerStackFixture
OctaviaLoadbalancerStackFixture = _octavia.OctaviaLoadbalancerStackFixture
OctaviaListenerStackFixture = _octavia.OctaviaListenerStackFixture
OctaviaPoolStackFixture = _octavia.OctaviaPoolStackFixture
OctaviaMemberServerStackFixture = _octavia.OctaviaMemberServerStackFixture
OctaviaServerStackFixture = _octavia.OctaviaServerStackFixture
OctaviaClientServerStackFixture = _octavia.OctaviaClientServerStackFixture
OctaviaOtherServerStackFixture = _octavia.OctaviaOtherServerStackFixture
OctaviaOtherMemberServerStackFixture = (
_octavia.OctaviaOtherMemberServerStackFixture)
VlanNetworkStackFixture = _vlan.VlanNetworkStackFixture
VlanProxyServerStackFixture = _vlan.VlanProxyServerStackFixture

View File

@ -71,27 +71,3 @@ class Centos7ServerStackFixture(CentosServerStackFixture):
#: Glance image used to create a Nova server instance
image_fixture = tobiko.required_setup_fixture(Centos7ImageFixture)
class CentosTrunkServerStackFixture(
CentosServerStackFixture, _nova.TrunkServerStackFixture):
interface = 'eth0'
@property
def user_data(self):
return ("#cloud-config\n"
"write_files:\n"
"- path: {path}/ifcfg-{interface}.{index}\n"
" owner: \"root\"\n"
" permissions: \"777\"\n"
" content: |\n"
" DEVICE=\"{interface}.{index}\"\n"
" BOOTPROTO=\"none\"\n"
" ONBOOT=\"yes\"\n"
" VLAN=\"yes\"\n"
" PERSISTENT_DHCLIENT=\"no\"\n"
"runcmd:\n"
"- [ sh, -c , \"systemctl restart NetworkManager\" ]".format(
path='/etc/sysconfig/network-scripts/',
interface=self.interface, index=self.trunk_subport_vlan))

View File

@ -13,7 +13,6 @@
# under the License.
from __future__ import absolute_import
import tobiko
from tobiko import config
from tobiko.openstack import glance
from tobiko.openstack.stacks import _nova

View File

@ -218,6 +218,8 @@ class ServerStackFixture(heat.HeatStackFixture, abc.ABC):
def fixed_ipv6(self):
return self.find_fixed_ip(ip_version=6)
has_vlan = False
#: Schedule on different host that this Nova server instance ID
different_host = None
@ -355,6 +357,12 @@ class ServerStackFixture(heat.HeatStackFixture, abc.ABC):
requirements['cores'] += (self.flavor_stack.vcpus or 1)
return requirements
@property
def neutron_required_quota_set(self) -> typing.Dict[str, int]:
requirements = super().neutron_required_quota_set
requirements['port'] += 1
return requirements
def assert_is_reachable(self):
ping.assert_reachable_hosts([self.ip_address])
@ -535,11 +543,3 @@ class AntiAffinityServerGroupStackFixture(tobiko.SharedFixture):
@property
def scheduler_group(self):
return self.server_group_stack.anti_affinity_server_group_id
@neutron.skip_if_missing_networking_extensions('trunk')
class TrunkServerStackFixture(CloudInitServerStackFixture):
has_trunk = True
segmentation_id = CONF.tobiko.neutron.trunk_subport_segmentation_id
segmentation_type = CONF.tobiko.neutron.trunk_subport_segmentation_type
trunk_subport_cidr = CONF.tobiko.neutron.trunk_subport_subnet_cidr

View File

@ -86,8 +86,3 @@ class RedHatServerStackFixture(_centos.CentosServerStackFixture):
#: Flavor used to create a Nova server instance
flavor_stack = tobiko.required_setup_fixture(RedHatFlavorStackFixture)
class RedHatTrunkServerStackFixture(
RedHatServerStackFixture, _centos.CentosTrunkServerStackFixture):
pass

View File

@ -15,10 +15,13 @@ from __future__ import absolute_import
import typing
import yaml
import tobiko
from tobiko import config
from tobiko.openstack import glance
from tobiko.openstack.stacks import _nova
from tobiko.openstack.stacks import _vlan
CONF = config.CONF
@ -71,6 +74,7 @@ class UbuntuImageFixture(UbuntuMinimalImageFixture,
- ping
- ncat
- nginx
- vlan
The image will also have below running services:
- nginx HTTP server listening on TCP port 80
@ -86,8 +90,10 @@ class UbuntuImageFixture(UbuntuMinimalImageFixture,
def install_packages(self) -> typing.List[str]:
return super().install_packages + ['iperf3',
'iputils-ping',
'nano',
'ncat',
'nginx']
'nginx',
'vlan']
# port of running HTTP server
http_port = 80
@ -103,8 +109,44 @@ class UbuntuImageFixture(UbuntuMinimalImageFixture,
'> /etc/systemd/system/iperf3-server@.service')
run_commands.append(
f"systemctl enable iperf3-server@{self.iperf3_port}")
run_commands.append('echo "8021q" >> /etc/modules')
return run_commands
@property
def ethernet_device(self) -> str:
return 'ens3'
@property
def vlan_id(self) -> int:
return tobiko.tobiko_config().neutron.vlan_id
@property
def vlan_device(self) -> str:
return f'vlan{self.vlan_id}'
@property
def vlan_config(self) -> typing.Dict[str, typing.Any]:
return {
'network': {
'version': 2,
'vlans': {
self.vlan_device: {
'link': self.ethernet_device,
'dhcp4': True,
'dhcp6': True,
'id': self.vlan_id,
}
}
}
}
@property
def write_files(self) -> typing.Dict[str, str]:
write_files = super().write_files
write_files['/etc/netplan/75-tobiko-vlan.yaml'] = yaml.dump(
self.vlan_config)
return write_files
class UbuntuFlavorStackFixture(_nova.FlavorStackFixture):
ram = 128
@ -120,7 +162,8 @@ class UbuntuMinimalServerStackFixture(_nova.CloudInitServerStackFixture):
flavor_stack = tobiko.required_setup_fixture(UbuntuFlavorStackFixture)
class UbuntuServerStackFixture(UbuntuMinimalServerStackFixture):
class UbuntuServerStackFixture(UbuntuMinimalServerStackFixture,
_vlan.VlanServerStackFixture):
"""Ubuntu server running an HTTP server
The server has additional commands compared to the minimal one:
@ -140,6 +183,14 @@ class UbuntuServerStackFixture(UbuntuMinimalServerStackFixture):
def iperf3_port(self) -> int:
return self.image_fixture.iperf3_port
@property
def vlan_id(self) -> int:
return self.image_fixture.vlan_id
@property
def vlan_device(self) -> str:
return self.image_fixture.vlan_device
class UbuntuExternalServerStackFixture(UbuntuServerStackFixture,
_nova.ExternalServerStackFixture):

View File

@ -0,0 +1,108 @@
# Copyright (c) 2021 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 __future__ import absolute_import
import abc
import typing
import netaddr
import tobiko
from tobiko import config
from tobiko.shell import ping
from tobiko.shell import ssh
from tobiko.openstack import neutron
from tobiko.openstack.stacks import _cirros
from tobiko.openstack.stacks import _neutron
from tobiko.openstack.stacks import _nova
CONF = config.CONF
class VlanNetworkStackFixture(_neutron.NetworkStackFixture):
pass
class VlanProxyServerStackFixture(_cirros.CirrosServerStackFixture):
network_stack = tobiko.required_fixture(VlanNetworkStackFixture)
@neutron.skip_if_missing_networking_extensions('trunk')
class VlanServerStackFixture(_nova.ServerStackFixture, abc.ABC):
has_vlan = True
#: stack with the newtwork where the trunk support is attached
vlan_network_stack = tobiko.required_fixture(VlanNetworkStackFixture)
@property
def vlan_id(self) -> int:
return CONF.tobiko.neutron.vlan_id
@property
def vlan_network(self) -> str:
return self.vlan_network_stack.network_id
@property
def vlan_fixed_ipv4(self):
return self.find_vlan_fixed_ip(ip_version=4)
@property
def vlan_fixed_ipv6(self):
return self.find_vlan_fixed_ip(ip_version=6)
def find_vlan_fixed_ip(self,
ip_version: int = None,
unique=False) -> netaddr.IPAddress:
vlan_ips = self.list_vlan_fixed_ips(ip_version=ip_version)
if unique:
return vlan_ips.unique
else:
return vlan_ips.first
def list_vlan_fixed_ips(self,
ip_version: int = None) \
-> tobiko.Selection[netaddr.IPAddress]:
fixed_ips: tobiko.Selection[netaddr.IPAddress] = tobiko.Selection(
netaddr.IPAddress(fixed_ip['ip_address'])
for fixed_ip in self.vlan_fixed_ips)
if ip_version is not None:
fixed_ips = fixed_ips.with_attributes(version=ip_version)
return fixed_ips
@property
def vlan_ssh_proxy_client(self) -> ssh.SSHClientType:
return tobiko.setup_fixture(VlanProxyServerStackFixture).ssh_client
def assert_vlan_is_reachable(self,
ip_version: int = None):
fixed_ips = self.list_vlan_fixed_ips(ip_version=ip_version)
ping.assert_reachable_hosts(fixed_ips,
ssh_client=self.vlan_ssh_proxy_client)
def assert_vlan_is_unreachable(self,
ip_version: int = None):
fixed_ips = self.list_vlan_fixed_ips(ip_version=ip_version)
ping.assert_unreachable_hosts(fixed_ips,
ssh_client=self.vlan_ssh_proxy_client)
@property
def neutron_required_quota_set(self) -> typing.Dict[str, int]:
requirements = super().neutron_required_quota_set
requirements['port'] += 1
requirements['trunk'] += 1
return requirements

View File

@ -72,23 +72,19 @@ parameters:
default: ''
description: Optional user_data to be passed to the server
has_trunk:
has_vlan:
type: boolean
default: false
trunk_subport_cidr:
vlan_network:
type: string
description: Network ID where vlan trunk support is attached
default: ''
segmentation_type:
type: string
description: Segmentation type for trunk subport
default: ''
segmentation_id:
vlan_id:
type: number
description: Segmentation ID for trunk subport
default: 0
description: Segmentation ID for vlan trunk subport
default: 101
conditions:
@ -98,8 +94,8 @@ conditions:
use_extra_dhcp_opts:
get_param: use_extra_dhcp_opts
has_trunk:
get_param: has_trunk
has_vlan:
get_param: has_vlan
resources:
@ -154,34 +150,26 @@ resources:
floating_network: {get_param: floating_network}
port_id: {get_resource: port}
trunk_subport_network:
type: OS::Neutron::Net
condition: has_trunk
trunk_subport_subnet:
type: OS::Neutron::Subnet
condition: has_trunk
properties:
network: {get_resource: trunk_subport_network}
cidr: {get_param: trunk_subport_cidr}
trunk_subport:
vlan_port:
type: OS::Neutron::Port
condition: has_trunk
description: Vlan trunk subport
condition: has_vlan
properties:
network: {get_resource: trunk_subport_network}
network: {get_param: vlan_network}
mac_address: {get_attr: [port, mac_address]}
port_security_enabled: {get_param: port_security_enabled}
security_groups: {get_param: security_groups}
trunk:
type: OS::Neutron::Trunk
description: Trunk port connected to the server
condition: has_trunk
description: Trunk connected to the server port
condition: has_vlan
properties:
port: {get_resource: port}
sub_ports:
- {port: {get_resource: trunk_subport},
segmentation_type: {get_param: segmentation_type},
segmentation_id: {get_param: segmentation_id}}
- port: {get_resource: vlan_port}
segmentation_type: vlan
segmentation_id: {get_param: vlan_id}
outputs:
@ -208,3 +196,12 @@ outputs:
port_id:
value: {get_resource: port}
vlan_port_id:
value: {get_resource: vlan_port}
condition: has_vlan
vlan_fixed_ips:
description: fixed IP addresses of server vlan port
value: {get_attr: [vlan_port, fixed_ips]}
condition: has_vlan

View File

@ -76,6 +76,22 @@ def list_ip_addresses(ip_version: int = None,
return ips
def find_ip_address(ip_version: int = None,
device: str = None,
scope: str = None,
unique: bool = False,
**execute_params) -> \
netaddr.IPAddress:
ips = list_ip_addresses(ip_version=ip_version,
device=device,
scope=scope,
**execute_params)
if unique:
return ips.unique
else:
return ips.first
def parse_ip_address(text: str) -> typing.Tuple[netaddr.IPAddress, int]:
if '/' in text:
# Remove netmask prefix length

View File

@ -0,0 +1,68 @@
# Copyright (c) 2019 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 __future__ import absolute_import
import testtools
import tobiko
from tobiko.openstack import stacks
from tobiko.shell import ip
from tobiko.tests.functional.openstack.stacks import test_cirros
class VlanProxyServerStackTest(test_cirros.CirrosServerStackTest):
#: Stack of resources with a server attached to a floating IP
stack = tobiko.required_fixture(stacks.VlanProxyServerStackFixture)
class UbuntuVlanServerTest(testtools.TestCase):
#: Stack of resources with a server attached to a floating IP
stack = tobiko.required_fixture(stacks.UbuntuServerStackFixture)
def test_vlan_ipv4_fixed_ip(self):
self._test_vlan_fixed_ip(ip_version=4)
def test_vlan_ipv6_fixed_ip(self):
self._test_vlan_fixed_ip(ip_version=6)
def _test_vlan_fixed_ip(self, ip_version: int):
expected_ip = self.get_vlan_fixed_ip(ip_version=ip_version)
for attempt in tobiko.retry(timeout=600.,
interval=10.):
try:
actual_ip = ip.find_ip_address(
device=self.stack.vlan_device,
ip_version=ip_version,
ssh_client=self.stack.ssh_client,
scope='global',
unique=True)
except tobiko.ObjectNotFound:
attempt.check_limits()
else:
break
else:
raise RuntimeError('Broken retry loop')
self.assertEqual(expected_ip, actual_ip)
self.stack.assert_vlan_is_reachable(ip_version=ip_version)
def get_vlan_fixed_ip(self, ip_version: int):
try:
return self.stack.find_vlan_fixed_ip(ip_version=ip_version)
except tobiko.ObjectNotFound:
self.skipTest(f"Server {self.stack.server_id} has any "
f"IPv{ip_version} address on VLAN device.")

View File

@ -20,7 +20,6 @@ import testtools
import tobiko
from tobiko import config
from tobiko.openstack import nova
from tobiko.openstack import stacks
@ -28,31 +27,33 @@ CONF = config.CONF
LOG = log.getLogger(__name__)
class CentosRebootTrunkServerStackFixture(
stacks.CentosTrunkServerStackFixture):
class RebootTrunkServerStackFixture(stacks.UbuntuServerStackFixture):
pass
class CentosTrunkTest(testtools.TestCase):
class TrunkTest(testtools.TestCase):
"""Tests trunk functionality"""
stack = tobiko.required_fixture(CentosRebootTrunkServerStackFixture)
stack = tobiko.required_fixture(RebootTrunkServerStackFixture)
vlan_proxy_stack = tobiko.required_fixture(
stacks.VlanProxyServerStackFixture)
@property
def vlan_proxy_ssh_client(self):
return self.vlan_proxy_stack.ssh_client
def test_activate_server(self):
self.stack.ensure_server_status('ACTIVE')
self.stack.assert_is_reachable()
self.stack.assert_vlan_is_reachable(ip_version=4)
def test_shutoff_server(self):
self.stack.ensure_server_status('SHUTOFF')
self.stack.assert_is_unreachable()
self.stack.assert_vlan_is_unreachable(ip_version=4)
@pytest.mark.ovn_migration
def test_after_server_reboot(self):
self.shutoff_server()
self.activate_server()
def test_after_server_shutoff(self):
self.activate_server()
self.shutoff_server()
def activate_server(self) -> nova.NovaServer:
server = self.stack.ensure_server_status('ACTIVE')
self.stack.assert_is_reachable()
return server
def shutoff_server(self) -> nova.NovaServer:
server = self.stack.ensure_server_status('SHUTOFF')
self.stack.assert_is_unreachable()
return server
def test_shutoff_then_activate_server(self):
self.test_shutoff_server()
self.test_activate_server()