This patch introduces the TrunkManager for the OVS agent. This class is responsible for wiring the trunk and the subports. Partially-implements: blueprint vlan-aware-vms Co-Authored-By: Jakub Libosvar <libosvar@redhat.com> Change-Id: I498560798983177ce7b64e1a8f32f1a157558897changes/36/335536/23
parent
4478987fbd
commit
35ffbed6f7
@ -0,0 +1,19 @@
|
||||
# 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 neutron_lib import exceptions as n_exc
|
||||
|
||||
from neutron._i18n import _
|
||||
|
||||
|
||||
class TrunkBridgeNotFound(n_exc.NotFound):
|
||||
message = _("Trunk bridge %(bridge)s could not be found.")
|
@ -0,0 +1,254 @@
|
||||
# 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 contextlib
|
||||
|
||||
from neutron_lib import constants
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron.agent.common import ovs_lib
|
||||
from neutron.services.trunk.drivers.openvswitch.agent import exceptions as exc
|
||||
from neutron.services.trunk import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_br_int_port_name(prefix, port_id):
|
||||
"""Return the OVS port name for the given port ID.
|
||||
|
||||
The port name is the one that plumbs into the integration bridge.
|
||||
"""
|
||||
return ("%si-%s" % (prefix, port_id))[:constants.DEVICE_NAME_MAX_LEN]
|
||||
|
||||
|
||||
def get_br_trunk_port_name(prefix, port_id):
|
||||
"""Return the OVS port name for the given port ID.
|
||||
|
||||
The port name is the one that plumbs into the trunk bridge.
|
||||
"""
|
||||
return ("%st-%s" % (prefix, port_id))[:constants.DEVICE_NAME_MAX_LEN]
|
||||
|
||||
|
||||
def get_patch_peer_attrs(peer_name, port_mac=None, port_id=None):
|
||||
external_ids = {}
|
||||
if port_mac:
|
||||
external_ids['attached-mac'] = port_mac
|
||||
if port_id:
|
||||
external_ids['iface-id'] = port_id
|
||||
attrs = [('type', 'patch'),
|
||||
('options', {'peer': peer_name})]
|
||||
if external_ids:
|
||||
attrs.append(
|
||||
('external_ids', external_ids))
|
||||
return attrs
|
||||
|
||||
|
||||
class TrunkBridge(ovs_lib.OVSBridge):
|
||||
|
||||
def __init__(self, trunk_id):
|
||||
name = utils.gen_trunk_br_name(trunk_id)
|
||||
super(TrunkBridge, self).__init__(name)
|
||||
|
||||
def exists(self):
|
||||
return self.bridge_exists(self.br_name)
|
||||
|
||||
|
||||
class TrunkParentPort(object):
|
||||
DEV_PREFIX = 'tp'
|
||||
|
||||
def __init__(self, trunk_id, port_id, port_mac=None):
|
||||
self.trunk_id = trunk_id
|
||||
self.port_id = port_id
|
||||
self.port_mac = port_mac
|
||||
self.bridge = TrunkBridge(self.trunk_id)
|
||||
# The name has form of tpi-<hash>
|
||||
self.patch_port_int_name = get_br_int_port_name(
|
||||
self.DEV_PREFIX, port_id)
|
||||
# The name has form of tpt-<hash>
|
||||
self.patch_port_trunk_name = get_br_trunk_port_name(
|
||||
self.DEV_PREFIX, port_id)
|
||||
self._transaction = None
|
||||
|
||||
# TODO(jlibosva): Move nested transaction to ovs_lib
|
||||
@contextlib.contextmanager
|
||||
def ovsdb_transaction(self):
|
||||
"""Context manager for ovsdb transaction.
|
||||
|
||||
The object caches whether its already in transaction and if it is, the
|
||||
original transaction is returned. This behavior enables calling
|
||||
manager several times while always getting the same transaction.
|
||||
"""
|
||||
if self._transaction:
|
||||
yield self._transaction
|
||||
else:
|
||||
with self.bridge.ovsdb.transaction() as txn:
|
||||
self._transaction = txn
|
||||
try:
|
||||
yield txn
|
||||
finally:
|
||||
self._transaction = None
|
||||
|
||||
def plug(self, br_int):
|
||||
"""Create patch ports between trunk bridge and given bridge.
|
||||
|
||||
The method creates one patch port on the given bridge side using
|
||||
port mac and id as external ids. The other endpoint of patch port is
|
||||
attached to the trunk bridge. Everything is done in a single
|
||||
ovsdb transaction so either all operations succeed or fail.
|
||||
|
||||
:param br_int: An integration bridge where peer endpoint of patch port
|
||||
will be created.
|
||||
|
||||
"""
|
||||
# NOTE(jlibosva): osvdb is an api so it doesn't matter whether we
|
||||
# use self.bridge or br_int
|
||||
ovsdb = self.bridge.ovsdb
|
||||
patch_int_attrs = get_patch_peer_attrs(
|
||||
self.patch_port_trunk_name, self.port_mac, self.port_id)
|
||||
patch_trunk_attrs = get_patch_peer_attrs(self.patch_port_int_name)
|
||||
|
||||
with self.ovsdb_transaction() as txn:
|
||||
txn.add(ovsdb.add_port(br_int.br_name,
|
||||
self.patch_port_int_name))
|
||||
txn.add(ovsdb.db_set('Interface', self.patch_port_int_name,
|
||||
*patch_int_attrs))
|
||||
txn.add(ovsdb.add_port(self.bridge.br_name,
|
||||
self.patch_port_trunk_name))
|
||||
txn.add(ovsdb.db_set('Interface', self.patch_port_trunk_name,
|
||||
*patch_trunk_attrs))
|
||||
|
||||
def unplug(self, bridge):
|
||||
"""Unplug the trunk from bridge.
|
||||
|
||||
Method deletes in single ovsdb transaction the trunk bridge and patch
|
||||
port on provided bridge.
|
||||
|
||||
:param bridge: Bridge that has peer side of patch port for this
|
||||
subport.
|
||||
"""
|
||||
ovsdb = self.bridge.ovsdb
|
||||
with self.ovsdb_transaction() as txn:
|
||||
txn.add(ovsdb.del_br(self.bridge.br_name))
|
||||
txn.add(ovsdb.del_port(self.patch_port_int_name,
|
||||
bridge.br_name))
|
||||
|
||||
|
||||
class SubPort(TrunkParentPort):
|
||||
# Patch port names have form of spi-<hash> or spt-<hash> respectively.
|
||||
DEV_PREFIX = 'sp'
|
||||
|
||||
def __init__(self, trunk_id, port_id, port_mac=None, segmentation_id=None):
|
||||
super(SubPort, self).__init__(trunk_id, port_id, port_mac)
|
||||
self.segmentation_id = segmentation_id
|
||||
|
||||
def plug(self, br_int):
|
||||
"""Create patch ports between trunk bridge and given bridge.
|
||||
|
||||
The method creates one patch port on the given bridge side using
|
||||
port mac and id as external ids. The other endpoint of patch port is
|
||||
attached to the trunk bridge. Then it sets vlan tag represented by
|
||||
segmentation_id. Everything is done in a single ovsdb transaction so
|
||||
either all operations succeed or fail.
|
||||
|
||||
:param br_int: An integration bridge where peer endpoint of patch port
|
||||
will be created.
|
||||
|
||||
"""
|
||||
ovsdb = self.bridge.ovsdb
|
||||
with self.ovsdb_transaction() as txn:
|
||||
super(SubPort, self).plug(br_int)
|
||||
txn.add(ovsdb.db_set(
|
||||
"Port", self.patch_port_trunk_name,
|
||||
("tag", self.segmentation_id)))
|
||||
|
||||
def unplug(self, bridge):
|
||||
"""Unplug the sub port from the bridge.
|
||||
|
||||
Method deletes in single ovsdb transaction both endpoints of patch
|
||||
ports that represents the subport.
|
||||
|
||||
:param bridge: Bridge that has peer side of patch port for this
|
||||
subport.
|
||||
"""
|
||||
ovsdb = self.bridge.ovsdb
|
||||
with self.ovsdb_transaction() as txn:
|
||||
txn.add(ovsdb.del_port(self.patch_port_trunk_name,
|
||||
self.bridge.br_name))
|
||||
txn.add(ovsdb.del_port(self.patch_port_int_name,
|
||||
bridge.br_name))
|
||||
|
||||
|
||||
class TrunkManager(object):
|
||||
|
||||
def __init__(self, br_int):
|
||||
self.br_int = br_int
|
||||
|
||||
def create_trunk(self, trunk_id, port_id, port_mac):
|
||||
"""Create the trunk.
|
||||
|
||||
This patches the bridge for trunk_id with the integration bridge
|
||||
by means of parent port identified by port_id.
|
||||
|
||||
:param trunk_id: ID of the trunk.
|
||||
:param port_id: ID of the parent port.
|
||||
:param port_mac: the MAC address of the parent port.
|
||||
:raises: TrunkBridgeNotFound -- In case trunk bridge doesn't exist.
|
||||
|
||||
"""
|
||||
trunk = TrunkParentPort(trunk_id, port_id, port_mac)
|
||||
if not trunk.bridge.exists():
|
||||
raise exc.TrunkBridgeNotFound(bridge=trunk.bridge.br_name)
|
||||
# Once the bridges are connected with the following patch ports,
|
||||
# the ovs agent will recognize the ports for processing and it will
|
||||
# take over the wiring process and everything that entails.
|
||||
# REVISIT(rossella_s): revisit this integration part, should tighter
|
||||
# control over the wiring logic for trunk ports be required.
|
||||
trunk.plug(self.br_int)
|
||||
|
||||
def remove_trunk(self, trunk_id, port_id):
|
||||
"""Remove the trunk bridge."""
|
||||
trunk = TrunkParentPort(trunk_id, port_id)
|
||||
if trunk.bridge.exists():
|
||||
trunk.unplug(self.br_int)
|
||||
else:
|
||||
LOG.debug("Trunk bridge with ID %s doesn't exist.", trunk_id)
|
||||
|
||||
def add_sub_port(self, trunk_id, port_id, port_mac, segmentation_id):
|
||||
"""Create a sub_port.
|
||||
|
||||
:param trunk_id: ID of the trunk
|
||||
:param port_id: ID of the child port
|
||||
:param segmentation_id: segmentation ID associated with this sub-port
|
||||
:param port_mac: MAC address of the child port
|
||||
|
||||
"""
|
||||
sub_port = SubPort(trunk_id, port_id, port_mac, segmentation_id)
|
||||
# If creating of parent trunk bridge takes longer than API call for
|
||||
# creating subport then bridge doesn't exist yet.
|
||||
if not sub_port.bridge.exists():
|
||||
raise exc.TrunkBridgeNotFound(bridge=sub_port.bridge.br_name)
|
||||
sub_port.plug(self.br_int)
|
||||
|
||||
def remove_sub_port(self, trunk_id, port_id):
|
||||
"""Remove a sub_port.
|
||||
|
||||
:param trunk_id: ID of the trunk
|
||||
:param port_id: ID of the child port
|
||||
"""
|
||||
sub_port = SubPort(trunk_id, port_id)
|
||||
|
||||
# Trunk bridge might have been deleted by calling delete_trunk() before
|
||||
# remove_sub_port().
|
||||
if sub_port.bridge.exists():
|
||||
sub_port.unplug(self.br_int)
|
||||
else:
|
||||
LOG.debug("Trunk bridge with ID %s doesn't exist.", trunk_id)
|
@ -0,0 +1,13 @@
|
||||
# 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.
|
||||
|
||||
VLAN_COUNT = 4096
|
@ -0,0 +1,231 @@
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import uuidutils
|
||||
import testtools
|
||||
|
||||
from neutron.common import utils as common_utils
|
||||
from neutron.services.trunk.drivers.openvswitch.agent import trunk_manager
|
||||
from neutron.services.trunk import utils
|
||||
from neutron.tests.common import conn_testers
|
||||
from neutron.tests.common import helpers
|
||||
from neutron.tests.common import net_helpers
|
||||
from neutron.tests.functional import base
|
||||
from neutron.tests.functional import constants as test_constants
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
VLAN_RANGE = set(range(test_constants.VLAN_COUNT))
|
||||
|
||||
|
||||
class FakeOVSDBException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class TrunkParentPortTestCase(base.BaseSudoTestCase):
|
||||
def setUp(self):
|
||||
super(TrunkParentPortTestCase, self).setUp()
|
||||
trunk_id = uuidutils.generate_uuid()
|
||||
port_id = uuidutils.generate_uuid()
|
||||
port_mac = common_utils.get_random_mac('fa:16:3e:00:00:00'.split(':'))
|
||||
self.trunk = trunk_manager.TrunkParentPort(trunk_id, port_id, port_mac)
|
||||
self.trunk.bridge = self.useFixture(
|
||||
net_helpers.OVSTrunkBridgeFixture(
|
||||
self.trunk.bridge.br_name)).bridge
|
||||
self.br_int = self.useFixture(net_helpers.OVSBridgeFixture()).bridge
|
||||
|
||||
def test_plug(self):
|
||||
self.trunk.plug(self.br_int)
|
||||
self.assertIn(self.trunk.patch_port_trunk_name,
|
||||
self.trunk.bridge.get_port_name_list())
|
||||
self.assertIn(self.trunk.patch_port_int_name,
|
||||
self.br_int.get_port_name_list())
|
||||
|
||||
def test_plug_failure_doesnt_create_ports(self):
|
||||
with mock.patch.object(
|
||||
self.trunk.bridge.ovsdb, 'db_set',
|
||||
side_effect=FakeOVSDBException):
|
||||
with testtools.ExpectedException(FakeOVSDBException):
|
||||
self.trunk.plug(self.br_int)
|
||||
self.assertNotIn(self.trunk.patch_port_trunk_name,
|
||||
self.trunk.bridge.get_port_name_list())
|
||||
self.assertNotIn(self.trunk.patch_port_int_name,
|
||||
self.br_int.get_port_name_list())
|
||||
|
||||
def test_unplug(self):
|
||||
self.trunk.plug(self.br_int)
|
||||
self.trunk.unplug(self.br_int)
|
||||
self.assertFalse(
|
||||
self.trunk.bridge.bridge_exists(self.trunk.bridge.br_name))
|
||||
self.assertNotIn(self.trunk.patch_port_int_name,
|
||||
self.br_int.get_port_name_list())
|
||||
|
||||
def test_unplug_failure_doesnt_delete_bridge(self):
|
||||
self.trunk.plug(self.br_int)
|
||||
with mock.patch.object(
|
||||
self.trunk.bridge.ovsdb, 'del_port',
|
||||
side_effect=FakeOVSDBException):
|
||||
with testtools.ExpectedException(FakeOVSDBException):
|
||||
self.trunk.unplug(self.br_int)
|
||||
self.assertTrue(
|
||||
self.trunk.bridge.bridge_exists(self.trunk.bridge.br_name))
|
||||
self.assertIn(self.trunk.patch_port_trunk_name,
|
||||
self.trunk.bridge.get_port_name_list())
|
||||
self.assertIn(self.trunk.patch_port_int_name,
|
||||
self.br_int.get_port_name_list())
|
||||
|
||||
|
||||
class SubPortTestCase(base.BaseSudoTestCase):
|
||||
def setUp(self):
|
||||
super(SubPortTestCase, self).setUp()
|
||||
trunk_id = uuidutils.generate_uuid()
|
||||
port_id = uuidutils.generate_uuid()
|
||||
port_mac = common_utils.get_random_mac('fa:16:3e:00:00:00'.split(':'))
|
||||
trunk_bridge_name = utils.gen_trunk_br_name(trunk_id)
|
||||
trunk_bridge = self.useFixture(
|
||||
net_helpers.OVSTrunkBridgeFixture(trunk_bridge_name)).bridge
|
||||
segmentation_id = helpers.get_not_used_vlan(
|
||||
trunk_bridge, VLAN_RANGE)
|
||||
self.subport = trunk_manager.SubPort(
|
||||
trunk_id, port_id, port_mac, segmentation_id)
|
||||
self.subport.bridge = trunk_bridge
|
||||
self.br_int = self.useFixture(net_helpers.OVSBridgeFixture()).bridge
|
||||
|
||||
def test_plug(self):
|
||||
self.subport.plug(self.br_int)
|
||||
self.assertIn(self.subport.patch_port_trunk_name,
|
||||
self.subport.bridge.get_port_name_list())
|
||||
self.assertIn(self.subport.patch_port_int_name,
|
||||
self.br_int.get_port_name_list())
|
||||
self.assertEqual(
|
||||
self.subport.segmentation_id,
|
||||
self.subport.bridge.db_get_val(
|
||||
'Port', self.subport.patch_port_trunk_name, 'tag'))
|
||||
|
||||
def test_plug_failure_doesnt_create_ports(self):
|
||||
with mock.patch.object(
|
||||
self.subport.bridge.ovsdb, 'db_set',
|
||||
side_effect=FakeOVSDBException):
|
||||
with testtools.ExpectedException(FakeOVSDBException):
|
||||
self.subport.plug(self.br_int)
|
||||
self.assertNotIn(self.subport.patch_port_trunk_name,
|
||||
self.subport.bridge.get_port_name_list())
|
||||
self.assertNotIn(self.subport.patch_port_int_name,
|
||||
self.br_int.get_port_name_list())
|
||||
|
||||
def test_unplug(self):
|
||||
self.subport.plug(self.br_int)
|
||||
self.subport.unplug(self.br_int)
|
||||
self.assertNotIn(self.subport.patch_port_trunk_name,
|
||||
self.subport.bridge.get_port_name_list())
|
||||
self.assertNotIn(self.subport.patch_port_int_name,
|
||||
self.br_int.get_port_name_list())
|
||||
|
||||
def test_unplug_failure(self):
|
||||
self.subport.plug(self.br_int)
|
||||
with mock.patch.object(
|
||||
self.subport.bridge.ovsdb, 'del_port',
|
||||
side_effect=FakeOVSDBException):
|
||||
with testtools.ExpectedException(FakeOVSDBException):
|
||||
self.subport.unplug(self.br_int)
|
||||
self.assertIn(self.subport.patch_port_trunk_name,
|
||||
self.subport.bridge.get_port_name_list())
|
||||
self.assertIn(self.subport.patch_port_int_name,
|
||||
self.br_int.get_port_name_list())
|
||||
|
||||
|
||||
class TrunkManagerTestCase(base.BaseSudoTestCase):
|
||||
net1_cidr = '192.178.0.1/24'
|
||||
net2_cidr = '192.168.0.1/24'
|
||||
|
||||
def setUp(self):
|
||||
super(TrunkManagerTestCase, self).setUp()
|
||||
trunk_id = uuidutils.generate_uuid()
|
||||
self.tester = self.useFixture(
|
||||
conn_testers.OVSTrunkConnectionTester(
|
||||
self.net1_cidr, utils.gen_trunk_br_name(trunk_id)))
|
||||
self.trunk_manager = trunk_manager.TrunkManager(
|
||||
self.tester.bridge)
|
||||
self.trunk = trunk_manager.TrunkParentPort(
|
||||
trunk_id, uuidutils.generate_uuid())
|
||||
|
||||
# TODO(jlibosva): Replace all tester methods with more robust tests
|
||||
def test_connectivity(self):
|
||||
"""Test connectivity with trunk and sub ports.
|
||||
|
||||
In this test we create a vm that has a trunk on net1 and a vm peer on
|
||||
the same network. We check connectivity between the peer and the vm.
|
||||
We create a sub port on net2 and a peer, check connectivity again.
|
||||
|
||||
"""
|
||||
vlan_net1 = helpers.get_not_used_vlan(self.tester.bridge, VLAN_RANGE)
|
||||
vlan_net2 = helpers.get_not_used_vlan(self.tester.bridge, VLAN_RANGE)
|
||||
trunk_mac = common_utils.get_random_mac('fa:16:3e:00:00:00'.split(':'))
|
||||
sub_port_mac = common_utils.get_random_mac(
|
||||
'fa:16:3e:00:00:00'.split(':'))
|
||||
sub_port_segmentation_id = helpers.get_not_used_vlan(
|
||||
self.tester.bridge, VLAN_RANGE)
|
||||
LOG.debug("Using %(n1)d vlan tag as local vlan ID for net1 and %(n2)d "
|
||||
"for local vlan ID for net2", {
|
||||
'n1': vlan_net1, 'n2': vlan_net2})
|
||||
self.tester.set_peer_tag(vlan_net1)
|
||||
self.trunk_manager.create_trunk(self.trunk.trunk_id,
|
||||
self.trunk.port_id,
|
||||
trunk_mac)
|
||||
|
||||
# tag the patch port, this should be done by the ovs agent but we mock
|
||||
# it for this test
|
||||
conn_testers.OVSBaseConnectionTester.set_tag(
|
||||
self.trunk.patch_port_int_name, self.tester.bridge, vlan_net1)
|
||||
|
||||
self.tester.assert_connection(protocol=self.tester.ICMP,
|
||||
direction=self.tester.INGRESS)
|
||||
self.tester.assert_connection(protocol=self.tester.ICMP,
|
||||
direction=self.tester.EGRESS)
|
||||
|
||||
self.tester.add_vlan_interface_and_peer(sub_port_segmentation_id,
|
||||
self.net2_cidr)
|
||||
conn_testers.OVSBaseConnectionTester.set_tag(
|
||||
self.tester._peer2.port.name, self.tester.bridge, vlan_net2)
|
||||
|
||||
sub_port = trunk_manager.SubPort(self.trunk.trunk_id,
|
||||
uuidutils.generate_uuid(),
|
||||
sub_port_mac,
|
||||
sub_port_segmentation_id)
|
||||
|
||||
self.trunk_manager.add_sub_port(sub_port.trunk_id,
|
||||
sub_port.port_id,
|
||||
sub_port.port_mac,
|
||||
sub_port.segmentation_id)
|
||||
# tag the patch port, this should be done by the ovs agent but we mock
|
||||
# it for this test
|
||||
conn_testers.OVSBaseConnectionTester.set_tag(
|
||||
sub_port.patch_port_int_name, self.tester.bridge, vlan_net2)
|
||||
|
||||
self.tester.test_sub_port_icmp_connectivity(self.tester.INGRESS)
|
||||
self.tester.test_sub_port_icmp_connectivity(self.tester.EGRESS)
|
||||
|
||||
self.trunk_manager.remove_sub_port(sub_port.trunk_id,
|
||||
sub_port.port_id)
|
||||
self.tester.test_sub_port_icmp_no_connectivity(self.tester.INGRESS)
|
||||
self.tester.test_sub_port_icmp_no_connectivity(self.tester.EGRESS)
|
||||
|
||||
self.trunk_manager.remove_trunk(self.trunk.trunk_id,
|
||||
self.trunk.port_id)
|
||||
self.tester.assert_no_connection(protocol=self.tester.ICMP,
|
||||
direction=self.tester.INGRESS)
|
@ -0,0 +1,67 @@
|
||||
# Copyright (c) 2016 Red Hat
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
import testtools
|
||||
|
||||
from neutron.common import utils as common_utils
|
||||
from neutron.services.trunk.drivers.openvswitch.agent import trunk_manager
|
||||
from neutron.tests import base
|
||||
|
||||
NATIVE_OVSDB_CONNECTION = (
|
||||
'neutron.agent.ovsdb.impl_idl.OvsdbIdl.ovsdb_connection')
|
||||
|
||||
|
||||
class TrunkParentPortTestCase(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TrunkParentPortTestCase, self).setUp()
|
||||
# Mock out connecting to ovsdb
|
||||
mock.patch(NATIVE_OVSDB_CONNECTION).start()
|
||||
trunk_id = uuidutils.generate_uuid()
|
||||
port_id = uuidutils.generate_uuid()
|
||||
trunk_mac = common_utils.get_random_mac('fa:16:3e:00:00:00'.split(':'))
|
||||
self.trunk = trunk_manager.TrunkParentPort(
|
||||
trunk_id, port_id, trunk_mac)
|
||||
|
||||
def test_multiple_transactions(self):
|
||||
def method_inner(trunk):
|
||||
with trunk.ovsdb_transaction() as txn:
|
||||
return id(txn)
|
||||
|
||||
def method_outer(trunk):
|
||||
with trunk.ovsdb_transaction() as txn:
|
||||
return method_inner(trunk), id(txn)
|
||||
|
||||
with self.trunk.ovsdb_transaction() as txn1:
|
||||
mock_commit = mock.patch.object(txn1, 'commit').start()
|
||||
txn_inner_id, txn_outer_id = method_outer(self.trunk)
|
||||
self.assertFalse(mock_commit.called)
|
||||
self.assertTrue(mock_commit.called)
|
||||
self.assertTrue(id(txn1) == txn_inner_id == txn_outer_id)
|
||||
|
||||
def test_transaction_raises_error(self):
|
||||
class MyException(Exception):
|
||||
pass
|
||||
|
||||
with testtools.ExpectedException(MyException):
|
||||
with self.trunk.ovsdb_transaction() as txn1:
|
||||
mock.patch.object(txn1, 'commit').start()
|
||||
raise MyException()
|
||||
self.assertIsNone(self.trunk._transaction)
|
||||
with self.trunk.ovsdb_transaction() as txn2:
|
||||
mock.patch.object(txn2, 'commit').start()
|
||||
self.assertIsNot(txn1, txn2)
|
Loading…
Reference in new issue