f0439a04ad
Patch tests basic life-cycle of a trunk associated with a port. Test creates a trunk with one subport - this tests interaction between agent and ovsdb handler that calls via RPC to server. Later a new subport is added which tests RPC interaction between server and agent. Then deletes the first created subport. Finally trunk is removed and checked that no patch ports remain on the integration bridge. Future work: - Run this test with linuxbridge - Test re-using port associated with trunk. - Test re-using subports. - Test with OVS firewall. Partially-implements: blueprint vlan-aware-vms Change-Id: Ie79a010e6751c1f1c2be5b1bf52511b9e100ad20
305 lines
11 KiB
Python
305 lines
11 KiB
Python
# Copyright 2016 Red Hat, Inc.
|
|
#
|
|
# 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 functools
|
|
|
|
import eventlet
|
|
import netaddr
|
|
from neutron_lib import constants
|
|
from oslo_utils import uuidutils
|
|
|
|
from neutron.common import utils
|
|
from neutron.services.trunk.drivers.openvswitch.agent import ovsdb_handler
|
|
from neutron.services.trunk.drivers.openvswitch.agent import trunk_manager
|
|
from neutron.services.trunk.drivers.openvswitch import utils as trunk_ovs_utils
|
|
from neutron.tests.fullstack import base
|
|
from neutron.tests.fullstack.resources import environment
|
|
from neutron.tests.fullstack.resources import machine
|
|
|
|
|
|
def trunk_bridge_does_not_exist(trunk_id):
|
|
"""Return true if trunk bridge for given ID does not exists."""
|
|
bridge = trunk_manager.TrunkBridge(trunk_id)
|
|
return not bridge.exists()
|
|
|
|
|
|
def make_ip_network(port, network):
|
|
"""Make an IPNetwork object from port and network.
|
|
|
|
Function returns IPNetwork object containing fixed IP address from port
|
|
dictionary with prefixlen from network object.
|
|
|
|
:param port: Port dictionary returned by Neutron API
|
|
:param network: IPNetwork object in which the port's IP will be assigned.
|
|
"""
|
|
ip_address = netaddr.IPAddress(
|
|
port['fixed_ips'][0]['ip_address'])
|
|
return netaddr.IPNetwork(
|
|
(ip_address.value, network.prefixlen))
|
|
|
|
|
|
class TrunkTestException(Exception):
|
|
pass
|
|
|
|
|
|
class Network(object):
|
|
"""A helper class to keep persistent info about assigned addresses."""
|
|
def __init__(self, prefix, network_cidr, tag=None):
|
|
self.prefix = prefix
|
|
self.network = netaddr.IPNetwork(network_cidr)
|
|
self.neutron_network = None
|
|
self.neutron_subnet = None
|
|
self.tag = tag
|
|
# Currently, only vlan is supported. Pass via __init__ once more are
|
|
# supported.
|
|
self.segmentation_type = 'vlan'
|
|
|
|
@property
|
|
def cidr(self):
|
|
return str(self.network.cidr)
|
|
|
|
@property
|
|
def gateway(self):
|
|
"""Return lowest possible IP in the given subnet."""
|
|
return str(netaddr.IPAddress(self.network.first + 1))
|
|
|
|
@property
|
|
def id(self):
|
|
return self.neutron_network['id']
|
|
|
|
@property
|
|
def name(self):
|
|
return "%s-network" % self.prefix
|
|
|
|
@property
|
|
def subnet_name(self):
|
|
return "%s-subnet" % self.prefix
|
|
|
|
|
|
class TestTrunkPlugin(base.BaseFullStackTestCase):
|
|
def setUp(self):
|
|
host_desc = [environment.HostDescription(
|
|
l3_agent=False,
|
|
l2_agent_type=constants.AGENT_TYPE_OVS)]
|
|
env_desc = environment.EnvironmentDescription()
|
|
env = environment.Environment(env_desc, host_desc)
|
|
super(TestTrunkPlugin, self).setUp(env)
|
|
|
|
self.tenant_id = uuidutils.generate_uuid()
|
|
self.trunk_network = Network('trunk', '10.0.0.0/24')
|
|
self.vlan1_network = Network('vlan1', '192.168.0.0/24', tag=10)
|
|
self.vlan2_network = Network('vlan2', '192.168.1.0/24', tag=20)
|
|
|
|
self.host = self.environment.hosts[0]
|
|
|
|
for network in (
|
|
self.trunk_network, self.vlan1_network, self.vlan2_network):
|
|
self.create_network_and_subnet(network)
|
|
|
|
def create_network_and_subnet(self, network):
|
|
"""Create network and subnet resources in Neutron based on network
|
|
object.
|
|
|
|
The resource names will be <prefix>-network and <prefix>-subnet, where
|
|
prefix is taken from network object.
|
|
|
|
:param network: Network object from this module.
|
|
"""
|
|
network.neutron_network = self.safe_client.create_network(
|
|
self.tenant_id, network.name)
|
|
network.neutron_subnet = self.safe_client.create_subnet(
|
|
self.tenant_id,
|
|
network.id,
|
|
cidr=network.cidr,
|
|
gateway_ip=network.gateway,
|
|
name=network.subnet_name,
|
|
enable_dhcp=False)
|
|
|
|
def create_vlan_aware_vm(self, trunk_network, vlan_networks):
|
|
"""Create a fake machine with one untagged port and subports
|
|
according vlan_networks parameter.
|
|
|
|
:param trunk_network: Instance of Network where trunk port should be
|
|
created.
|
|
:param vlan_networks: List of Network instances where subports should
|
|
be created.
|
|
"""
|
|
trunk_parent_port = self.safe_client.create_port(
|
|
self.tenant_id, trunk_network.id)
|
|
|
|
vlan_subports = [
|
|
self.safe_client.create_port(self.tenant_id, vlan_network.id,
|
|
mac_address=trunk_parent_port['mac_address'])
|
|
for vlan_network in vlan_networks]
|
|
|
|
trunk = self.safe_client.create_trunk(
|
|
self.tenant_id,
|
|
name='mytrunk',
|
|
port_id=trunk_parent_port['id'],
|
|
sub_ports=[
|
|
{'port_id': vlan_subport['id'],
|
|
'segmentation_type': 'vlan',
|
|
'segmentation_id': vlan_network.tag}
|
|
for vlan_subport, vlan_network in zip(vlan_subports,
|
|
vlan_networks)
|
|
],
|
|
)
|
|
|
|
vm = self.useFixture(
|
|
machine.FakeFullstackTrunkMachine(
|
|
trunk,
|
|
self.host,
|
|
trunk_network.id,
|
|
self.tenant_id,
|
|
self.safe_client,
|
|
neutron_port=trunk_parent_port,
|
|
bridge_name=trunk_ovs_utils.gen_trunk_br_name(trunk['id'])))
|
|
|
|
for port, vlan_network in zip(vlan_subports, vlan_networks):
|
|
ip_network = make_ip_network(port, vlan_network.network)
|
|
vm.add_vlan_interface(
|
|
port['mac_address'], ip_network, vlan_network.tag)
|
|
vm.block_until_boot()
|
|
|
|
return vm
|
|
|
|
def create_vm_in_network(self, network):
|
|
"""Create a fake machine in given network."""
|
|
return self.useFixture(
|
|
machine.FakeFullstackMachine(
|
|
self.host,
|
|
network.id,
|
|
self.tenant_id,
|
|
self.safe_client
|
|
)
|
|
)
|
|
|
|
def add_subport_to_vm(self, vm, subport_network):
|
|
"""Add subport from subport_network to given vm.
|
|
|
|
:param vm: FakeFullstackMachine instance to with subport should be
|
|
added.
|
|
:param subport_network: Network object representing network containing
|
|
port for subport.
|
|
"""
|
|
subport = self.safe_client.create_port(
|
|
self.tenant_id, subport_network.id,
|
|
mac_address=vm.neutron_port['mac_address'])
|
|
subport_spec = {
|
|
'port_id': subport['id'],
|
|
'segmentation_type': subport_network.segmentation_type,
|
|
'segmentation_id': subport_network.tag
|
|
}
|
|
|
|
self.safe_client.trunk_add_subports(
|
|
self.tenant_id, vm.trunk['id'], [subport_spec])
|
|
ip_network = make_ip_network(subport, subport_network.network)
|
|
vm.add_vlan_interface(
|
|
subport['mac_address'], ip_network, subport_network.tag)
|
|
|
|
def test_trunk_lifecycle(self):
|
|
"""Test life-cycle of a fake VM with trunk port.
|
|
|
|
This test uses 4 fake machines:
|
|
- vlan_aware_vm (A) that is at the beginning connected to a trunk
|
|
network and a vlan1 network.
|
|
- trunk_network_vm (B) that is connected to the trunk network.
|
|
- vlan1_network_vm (C) that is connected to the vlan1 network.
|
|
- vlan2_network_vm (D) that is connected to a vlan2 network.
|
|
|
|
Scenario steps:
|
|
- all the vms from above are created
|
|
- A can talk with B (over the trunk network)
|
|
- A can talk with C (over the vlan1 network)
|
|
- A can not talk with D (no leg on the vlan2 network)
|
|
|
|
- subport from the vlan2 network is added to A
|
|
- A can now talk with D (over the vlan2 network)
|
|
|
|
- subport from the vlan1 network is removed from A
|
|
- A can talk with B (over the trunk network)
|
|
- A can not talk with C (no leg on the vlan1 network)
|
|
- A can talk with D (over the vlan2 network)
|
|
|
|
- A is deleted which leads to removal of trunk bridge
|
|
- no leftovers like patch ports to the trunk bridge should remain on
|
|
an integration bridge
|
|
"""
|
|
|
|
vlan_aware_vm = self.create_vlan_aware_vm(
|
|
self.trunk_network,
|
|
[self.vlan1_network]
|
|
)
|
|
trunk_id = vlan_aware_vm.trunk['id']
|
|
|
|
# Create helper vms with different networks
|
|
trunk_network_vm = self.create_vm_in_network(self.trunk_network)
|
|
vlan1_network_vm = self.create_vm_in_network(self.vlan1_network)
|
|
vlan2_network_vm = self.create_vm_in_network(self.vlan2_network)
|
|
|
|
for vm in trunk_network_vm, vlan1_network_vm, vlan2_network_vm:
|
|
vm.block_until_boot()
|
|
|
|
# Test connectivity to trunk and subport
|
|
vlan_aware_vm.block_until_ping(trunk_network_vm.ip)
|
|
vlan_aware_vm.block_until_ping(vlan1_network_vm.ip)
|
|
|
|
# Subport for vlan2 hasn't been added yet
|
|
vlan_aware_vm.block_until_no_ping(vlan2_network_vm.ip)
|
|
|
|
# Add another subport and test
|
|
self.add_subport_to_vm(vlan_aware_vm, self.vlan2_network)
|
|
vlan_aware_vm.block_until_ping(vlan2_network_vm.ip)
|
|
|
|
# Remove the first subport
|
|
self.safe_client.trunk_remove_subports(
|
|
self.tenant_id,
|
|
trunk_id,
|
|
[vlan_aware_vm.trunk['sub_ports'][0]])
|
|
|
|
# vlan1_network_vm now shouldn't be able to talk to vlan_aware_vm
|
|
vlan_aware_vm.block_until_no_ping(vlan1_network_vm.ip)
|
|
|
|
# but trunk and vlan2 should be able to ping
|
|
vlan_aware_vm.block_until_ping(trunk_network_vm.ip)
|
|
vlan_aware_vm.block_until_ping(vlan2_network_vm.ip)
|
|
|
|
# Delete vm and check that patch ports and trunk bridge are gone
|
|
vlan_aware_vm.destroy()
|
|
bridge_doesnt_exist_predicate = functools.partial(
|
|
trunk_bridge_does_not_exist, trunk_id)
|
|
utils.wait_until_true(
|
|
bridge_doesnt_exist_predicate,
|
|
exception=TrunkTestException(
|
|
'Trunk bridge with ID %s has not been removed' %
|
|
trunk_id)
|
|
)
|
|
|
|
integration_bridge = self.host.get_bridge(None)
|
|
no_patch_ports_predicate = functools.partial(
|
|
lambda bridge: not ovsdb_handler.bridge_has_service_port(bridge),
|
|
integration_bridge,
|
|
)
|
|
try:
|
|
utils.wait_until_true(no_patch_ports_predicate)
|
|
except eventlet.TimeoutError:
|
|
raise TrunkTestException(
|
|
"Integration bridge %s still has following ports while some of"
|
|
" them are patch ports for trunk that were supposed to be "
|
|
"removed: %s" % (
|
|
integration_bridge.br_name,
|
|
integration_bridge.get_iface_name_list()
|
|
)
|
|
)
|