Add a fullstack fake VM, basic connectivity test

* Full stack tests' fake VMs are represented via a namespace,
  MAC, IP address and default gateway. They're plugged to an OVS
  bridge via an OVS internal port. As opposed to the current
  fake machine class used in functional testing, this new fake
  machine also creates a Neutron port via the API and sets the
  IP and MAC according to it. It also sets additional attributes
  on the OVS port to allow the OVS agent to do its magic.
* The functional fake machine and the full stack fake machine
  should continue to share commonalities.
* The fullstack fake machine currently takes the IP address
  from the port and statically assigns it to the namespace
  device. Later when I'll add support for the DHCP agent
  in full stack testing this assignment will look for the dhcp
  attribute of the subnet and either assign the IP address
  via 'ip' or call a dhcp client.
* Added a basic L2 connectivity test between two such machines
  on the same Neutron network.
* OVSPortFixture now uses OVSInterfaceDriver to plug the port
  instead of replicate a lot of the code. I had to make a
  small change to _setup_arp_spoof_for_port since all OVS ports
  are now created with their external-ids set.

Change-Id: Ib985b7e742f58f1a6eb6fc598df3cbac31046951
This commit is contained in:
Assaf Muller 2015-06-12 15:07:17 -04:00 committed by John Schwarz
parent a5b0e6eaf9
commit 53fe9ddd6c
10 changed files with 200 additions and 59 deletions

View File

@ -55,10 +55,10 @@ through the API and then assert that a namespace was created for it.
Full stack tests run in the Neutron tree with Neutron resources alone. You
may use the Neutron API (The Neutron server is set to NOAUTH so that Keystone
is out of the picture). instances may be simulated with a helper class that
contains a container-like object in its own namespace and IP address. It has
helper methods to send different kinds of traffic. The "instance" may be
connected to br-int or br-ex, to simulate internal or external traffic.
is out of the picture). VMs may be simulated with a container-like class:
neutron.tests.fullstack.resources.machine.FakeFullstackMachine.
An example of its usage may be found at:
neutron/tests/fullstack/test_connectivity.py.
Full stack testing can simulate multi node testing by starting an agent
multiple times. Specifically, each node would have its own copy of the

View File

@ -19,7 +19,39 @@ from neutron.agent.linux import ip_lib
from neutron.tests.common import net_helpers
class FakeMachine(fixtures.Fixture):
class FakeMachineBase(fixtures.Fixture):
def __init__(self):
self.port = None
def _setUp(self):
ns_fixture = self.useFixture(
net_helpers.NamespaceFixture())
self.namespace = ns_fixture.name
def execute(self, *args, **kwargs):
ns_ip_wrapper = ip_lib.IPWrapper(self.namespace)
return ns_ip_wrapper.netns.execute(*args, **kwargs)
def assert_ping(self, dst_ip):
net_helpers.assert_ping(self.namespace, dst_ip)
def assert_no_ping(self, dst_ip):
net_helpers.assert_no_ping(self.namespace, dst_ip)
@property
def ip(self):
raise NotImplementedError()
@property
def ip_cidr(self):
raise NotImplementedError()
@property
def mac_address(self):
return self.port.link.address
class FakeMachine(FakeMachineBase):
"""Create a fake machine.
:ivar bridge: bridge on which the fake machine is bound
@ -43,9 +75,7 @@ class FakeMachine(fixtures.Fixture):
self.gateway_ip = gateway_ip
def _setUp(self):
ns_fixture = self.useFixture(
net_helpers.NamespaceFixture())
self.namespace = ns_fixture.name
super(FakeMachine, self)._setUp()
self.port = self.useFixture(
net_helpers.PortFixture.get(self.bridge, self.namespace)).port
@ -68,26 +98,12 @@ class FakeMachine(fixtures.Fixture):
self.port.addr.delete(self._ip_cidr)
self._ip_cidr = ip_cidr
@property
def mac_address(self):
return self.port.link.address
@mac_address.setter
@FakeMachineBase.mac_address.setter
def mac_address(self, mac_address):
self.port.link.set_down()
self.port.link.set_address(mac_address)
self.port.link.set_up()
def execute(self, *args, **kwargs):
ns_ip_wrapper = ip_lib.IPWrapper(self.namespace)
return ns_ip_wrapper.netns.execute(*args, **kwargs)
def assert_ping(self, dst_ip):
net_helpers.assert_ping(self.namespace, dst_ip)
def assert_no_ping(self, dst_ip):
net_helpers.assert_no_ping(self.namespace, dst_ip)
class PeerMachines(fixtures.Fixture):
"""Create 'amount' peered machines on an ip_cidr.

View File

@ -25,15 +25,18 @@ import subprocess
import fixtures
import netaddr
from oslo_config import cfg
from oslo_utils import uuidutils
import six
from neutron.agent.common import config
from neutron.agent.common import ovs_lib
from neutron.agent.linux import bridge_lib
from neutron.agent.linux import interface
from neutron.agent.linux import ip_lib
from neutron.agent.linux import utils
from neutron.common import constants as n_const
from neutron.db import db_base_plugin_common
from neutron.tests import base as tests_base
from neutron.tests.common import base as common_base
from neutron.tests import tools
@ -420,10 +423,13 @@ class PortFixture(fixtures.Fixture):
:ivar bridge: port bridge
"""
def __init__(self, bridge=None, namespace=None):
def __init__(self, bridge=None, namespace=None, mac=None, port_id=None):
super(PortFixture, self).__init__()
self.bridge = bridge
self.namespace = namespace
self.mac = (
mac or db_base_plugin_common.DbBasePluginCommon._generate_mac())
self.port_id = port_id or uuidutils.generate_uuid()
@abc.abstractmethod
def _create_bridge_fixture(self):
@ -436,10 +442,10 @@ class PortFixture(fixtures.Fixture):
self.bridge = self.useFixture(self._create_bridge_fixture()).bridge
@classmethod
def get(cls, bridge, namespace=None):
def get(cls, bridge, namespace=None, mac=None, port_id=None):
"""Deduce PortFixture class from bridge type and instantiate it."""
if isinstance(bridge, ovs_lib.OVSBridge):
return OVSPortFixture(bridge, namespace)
return OVSPortFixture(bridge, namespace, mac, port_id)
if isinstance(bridge, bridge_lib.BridgeDevice):
return LinuxBridgePortFixture(bridge, namespace)
if isinstance(bridge, VethBridge):
@ -468,30 +474,26 @@ class OVSBridgeFixture(fixtures.Fixture):
class OVSPortFixture(PortFixture):
def __init__(self, bridge=None, namespace=None, attrs=None):
super(OVSPortFixture, self).__init__(bridge, namespace)
if attrs is None:
attrs = []
self.attrs = attrs
def _create_bridge_fixture(self):
return OVSBridgeFixture()
def _setUp(self):
super(OVSPortFixture, self)._setUp()
port_name = common_base.create_resource(PORT_PREFIX, self.create_port)
interface_config = cfg.ConfigOpts()
interface_config.register_opts(interface.OPTS)
ovs_interface = interface.OVSInterfaceDriver(interface_config)
port_name = tests_base.get_rand_device_name(PORT_PREFIX)
ovs_interface.plug_new(
None,
self.port_id,
port_name,
self.mac,
bridge=self.bridge.br_name,
namespace=self.namespace)
self.addCleanup(self.bridge.delete_port, port_name)
self.port = ip_lib.IPDevice(port_name)
ns_ip_wrapper = ip_lib.IPWrapper(self.namespace)
ns_ip_wrapper.add_device_to_namespace(self.port)
self.port.link.set_up()
def create_port(self, name):
self.attrs.insert(0, ('type', 'internal'))
self.bridge.add_port(name, *self.attrs)
return name
self.port = ip_lib.IPDevice(port_name, self.namespace)
class LinuxBridgeFixture(fixtures.Fixture):

View File

@ -65,6 +65,13 @@ class ClientFixture(fixtures.Fixture):
return self._create_resource(resource_type, spec)
def create_port(self, tenant_id, network_id, hostname):
return self._create_resource(
'port',
{'network_id': network_id,
'tenant_id': tenant_id,
'binding:host_id': hostname})
def add_router_interface(self, router_id, subnet_id):
body = {'subnet_id': subnet_id}
self.client.add_interface_router(router=router_id, body=body)

View File

@ -98,6 +98,10 @@ class Host(fixtures.Fixture):
net_helpers.create_patch_ports(
self.central_external_bridge, host_external_bridge)
@property
def hostname(self):
return self.neutron_config.config.DEFAULT.host
@property
def l3_agent(self):
return self.agents['l3']

View File

@ -0,0 +1,71 @@
# Copyright 2015 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 netaddr
from neutron.agent.linux import utils
from neutron.tests.common import machine_fixtures
from neutron.tests.common import net_helpers
class FakeFullstackMachine(machine_fixtures.FakeMachineBase):
def __init__(self, host, network_id, tenant_id, safe_client):
super(FakeFullstackMachine, self).__init__()
self.bridge = host.ovs_agent.br_int
self.host_binding = host.hostname
self.tenant_id = tenant_id
self.network_id = network_id
self.safe_client = safe_client
def _setUp(self):
super(FakeFullstackMachine, self)._setUp()
self.neutron_port = self.safe_client.create_port(
network_id=self.network_id,
tenant_id=self.tenant_id,
hostname=self.host_binding)
self.neutron_port_id = self.neutron_port['id']
mac_address = self.neutron_port['mac_address']
self.port = self.useFixture(
net_helpers.PortFixture.get(
self.bridge, self.namespace, mac_address,
self.neutron_port_id)).port
self._ip = self.neutron_port['fixed_ips'][0]['ip_address']
subnet_id = self.neutron_port['fixed_ips'][0]['subnet_id']
subnet = self.safe_client.client.show_subnet(subnet_id)
prefixlen = netaddr.IPNetwork(subnet['subnet']['cidr']).prefixlen
self._ip_cidr = '%s/%s' % (self._ip, prefixlen)
# TODO(amuller): Support DHCP
self.port.addr.add(self.ip_cidr)
self.gateway_ip = subnet['subnet']['gateway_ip']
if self.gateway_ip:
net_helpers.set_namespace_gateway(self.port, self.gateway_ip)
@property
def ip(self):
return self._ip
@property
def ip_cidr(self):
return self._ip_cidr
def block_until_boot(self):
utils.wait_until_true(
lambda: (self.safe_client.client.show_port(self.neutron_port_id)
['port']['status'] == 'ACTIVE'),
sleep=3)

View File

@ -0,0 +1,49 @@
# Copyright 2015 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.
from oslo_utils import uuidutils
from neutron.tests.fullstack import base
from neutron.tests.fullstack.resources import environment
from neutron.tests.fullstack.resources import machine
class TestConnectivitySameNetwork(base.BaseFullStackTestCase):
def __init__(self, *args, **kwargs):
host_descriptions = [
environment.HostDescription(l3_agent=False) for _ in range(2)]
env = environment.Environment(host_descriptions)
super(TestConnectivitySameNetwork, self).__init__(env, *args, **kwargs)
def test_connectivity(self):
tenant_uuid = uuidutils.generate_uuid()
network = self.safe_client.create_network(tenant_uuid)
self.safe_client.create_subnet(
tenant_uuid, network['id'], '20.0.0.0/24')
vms = [
self.useFixture(
machine.FakeFullstackMachine(
self.environment.hosts[i],
network['id'],
tenant_uuid,
self.safe_client))
for i in range(2)]
for vm in vms:
vm.block_until_boot()
vms[0].assert_ping(vms[1].ip)

View File

@ -26,10 +26,9 @@ from neutron.tests.fullstack.resources import environment
class TestLegacyL3Agent(base.BaseFullStackTestCase):
def __init__(self, *args, **kwargs):
super(TestLegacyL3Agent, self).__init__(
environment.Environment(
[environment.HostDescription(l3_agent=True)]),
*args, **kwargs)
host_descriptions = [environment.HostDescription(l3_agent=True)]
env = environment.Environment(host_descriptions)
super(TestLegacyL3Agent, self).__init__(env, *args, **kwargs)
def _get_namespace(self, router_id):
return namespaces.build_ns_name(l3_agent.NS_PREFIX, router_id)

View File

@ -135,12 +135,9 @@ class TestSimpleInterfaceMonitor(BaseMonitorTest):
devices = self.monitor.get_events()
self.assertTrue(devices.get('added'),
'Initial call should always be true')
p_attrs = [('external_ids', {'iface-status': 'active'})]
br = self.useFixture(net_helpers.OVSBridgeFixture())
p1 = self.useFixture(net_helpers.OVSPortFixture(
br.bridge, None, p_attrs))
p2 = self.useFixture(net_helpers.OVSPortFixture(
br.bridge, None, p_attrs))
p1 = self.useFixture(net_helpers.OVSPortFixture(br.bridge))
p2 = self.useFixture(net_helpers.OVSPortFixture(br.bridge))
added_devices = [p1.port.name, p2.port.name]
utils.wait_until_true(
lambda: self._expected_devices_events(added_devices, 'added'))

View File

@ -179,19 +179,15 @@ class _ARPSpoofTestCase(object):
net_helpers.assert_ping(self.src_namespace, self.dst_addr, count=2)
def _setup_arp_spoof_for_port(self, port, addrs, psec=True):
of_port_map = self.br.get_vif_port_to_ofport_map()
class VifPort(object):
ofport = of_port_map[port]
port_name = port
vif = next(
vif for vif in self.br.get_vif_ports() if vif.port_name == port)
ip_addr = addrs.pop()
details = {'port_security_enabled': psec,
'fixed_ips': [{'ip_address': ip_addr}],
'allowed_address_pairs': [
dict(ip_address=ip) for ip in addrs]}
ovsagt.OVSNeutronAgent.setup_arp_spoofing_protection(
self.br_int, VifPort(), details)
self.br_int, vif, details)
class ARPSpoofOFCtlTestCase(_ARPSpoofTestCase, _OVSAgentOFCtlTestBase):