diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ofswitch.py b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ofswitch.py index bf8c98a823f..cdb539f7701 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ofswitch.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ofswitch.py @@ -14,8 +14,12 @@ # License for the specific language governing permissions and limitations # under the License. +import functools +import random + import eventlet import netaddr +from neutron_lib import exceptions from oslo_config import cfg from oslo_log import log as logging from oslo_utils import excutils @@ -31,9 +35,14 @@ from neutron.agent.common import ovs_lib LOG = logging.getLogger(__name__) +BUNDLE_ID_WIDTH = 1 << 32 COOKIE_DEFAULT = object() +class ActiveBundleRunning(exceptions.NeutronException): + message = _("Another active bundle 0x%(bundle_id)x is running") + + class OpenFlowSwitchMixin(object): """Mixin to provide common convenient routines for an openflow switch. @@ -50,6 +59,7 @@ class OpenFlowSwitchMixin(object): def __init__(self, *args, **kwargs): self._app = kwargs.pop('ryu_app') + self.active_bundles = set() super(OpenFlowSwitchMixin, self).__init__(*args, **kwargs) def _get_dp_by_dpid(self, dpid_int): @@ -70,9 +80,14 @@ class OpenFlowSwitchMixin(object): eventlet.sleep(1) return dp - def _send_msg(self, msg, reply_cls=None, reply_multi=False): + def _send_msg(self, msg, reply_cls=None, reply_multi=False, + active_bundle=None): timeout_sec = cfg.CONF.OVS.of_request_timeout timeout = eventlet.Timeout(seconds=timeout_sec) + if active_bundle is not None: + (dp, ofp, ofpp) = self._get_dp() + msg = ofpp.ONFBundleAddMsg(dp, active_bundle['id'], + active_bundle['bundle_flags'], msg, []) try: result = ofctl_api.send_msg(self._app, msg, reply_cls, reply_multi) except ryu_exc.RyuException as e: @@ -107,7 +122,7 @@ class OpenFlowSwitchMixin(object): def uninstall_flows(self, table_id=None, strict=False, priority=0, cookie=COOKIE_DEFAULT, cookie_mask=0, - match=None, **match_kwargs): + match=None, active_bundle=None, **match_kwargs): (dp, ofp, ofpp) = self._get_dp() if table_id is None: table_id = ofp.OFPTT_ALL @@ -135,7 +150,7 @@ class OpenFlowSwitchMixin(object): priority=priority, out_group=ofp.OFPG_ANY, out_port=ofp.OFPP_ANY) - self._send_msg(msg) + self._send_msg(msg, active_bundle=active_bundle) def dump_flows(self, table_id=None): (dp, ofp, ofpp) = self._get_dp() @@ -160,8 +175,9 @@ class OpenFlowSwitchMixin(object): {'cookie': c}) self.uninstall_flows(cookie=c, cookie_mask=ovs_lib.UINT64_BITMASK) - def install_goto_next(self, table_id): - self.install_goto(table_id=table_id, dest_table_id=table_id + 1) + def install_goto_next(self, table_id, active_bundle=None): + self.install_goto(table_id=table_id, dest_table_id=table_id + 1, + active_bundle=active_bundle) def install_output(self, port, table_id=0, priority=0, match=None, **match_kwargs): @@ -194,7 +210,7 @@ class OpenFlowSwitchMixin(object): def install_instructions(self, instructions, table_id=0, priority=0, - match=None, **match_kwargs): + match=None, active_bundle=None, **match_kwargs): (dp, ofp, ofpp) = self._get_dp() match = self._match(ofp, ofpp, match, **match_kwargs) if isinstance(instructions, six.string_types): @@ -211,7 +227,7 @@ class OpenFlowSwitchMixin(object): match=match, priority=priority, instructions=instructions) - self._send_msg(msg) + self._send_msg(msg, active_bundle=active_bundle) def install_apply_actions(self, actions, table_id=0, priority=0, @@ -225,3 +241,81 @@ class OpenFlowSwitchMixin(object): match=match, instructions=instructions, **match_kwargs) + + def bundled(self, atomic=False, ordered=False): + return BundledOpenFlowBridge(self, atomic, ordered) + + +class BundledOpenFlowBridge(object): + def __init__(self, br, atomic, ordered): + self.br = br + self.active_bundle = None + self.bundle_flags = 0 + if not atomic and not ordered: + return + (dp, ofp, ofpp) = self.br._get_dp() + if atomic: + self.bundle_flags |= ofp.ONF_BF_ATOMIC + if ordered: + self.bundle_flags |= ofp.ONF_BF_ORDERED + + def __getattr__(self, name): + if name.startswith('install') or name.startswith('uninstall'): + under = getattr(self.br, name) + if self.active_bundle is None: + return under + return functools.partial(under, active_bundle=dict( + id=self.active_bundle, bundle_flags=self.bundle_flags)) + raise AttributeError("Only install_* or uninstall_* methods " + "can be used") + + def __enter__(self): + if self.active_bundle is not None: + raise ActiveBundleRunning(bundle_id=self.active_bundle) + while True: + self.active_bundle = random.randrange(BUNDLE_ID_WIDTH) + if self.active_bundle not in self.br.active_bundles: + self.br.active_bundles.add(self.active_bundle) + break + try: + (dp, ofp, ofpp) = self.br._get_dp() + msg = ofpp.ONFBundleCtrlMsg(dp, self.active_bundle, + ofp.ONF_BCT_OPEN_REQUEST, + self.bundle_flags, []) + reply = self.br._send_msg(msg, reply_cls=ofpp.ONFBundleCtrlMsg) + if reply.type != ofp.ONF_BCT_OPEN_REPLY: + raise RuntimeError( + "Unexpected reply type %d != ONF_BCT_OPEN_REPLY" % + reply.type) + return self + except Exception: + self.br.active_bundles.remove(self.active_bundle) + self.active_bundle = None + raise + + def __exit__(self, type, value, traceback): + (dp, ofp, ofpp) = self.br._get_dp() + if type is None: + ctrl_type = ofp.ONF_BCT_COMMIT_REQUEST + expected_reply = ofp.ONF_BCT_COMMIT_REPLY + else: + ctrl_type = ofp.ONF_BCT_DISCARD_REQUEST + expected_reply = ofp.ONF_BCT_DISCARD_REPLY + LOG.warning( + "Discarding bundle with ID 0x%(id)x due to an exception", + {'id': self.active_bundle}) + + try: + msg = ofpp.ONFBundleCtrlMsg(dp, self.active_bundle, + ctrl_type, + self.bundle_flags, []) + reply = self.br._send_msg(msg, reply_cls=ofpp.ONFBundleCtrlMsg) + if reply.type != expected_reply: + # The bundle ID may be in a bad state. Let's leave it + # in active_bundles so that we will never use it again. + raise RuntimeError("Unexpected reply type %d" % reply.type) + self.br.active_bundles.remove(self.active_bundle) + finally: + # It is possible the bundle is kept open, but this must be + # cleared or all subsequent __enter__ will fail. + self.active_bundle = None diff --git a/neutron/tests/functional/agent/test_ovs_flows.py b/neutron/tests/functional/agent/test_ovs_flows.py index 7cb15fbc980..86fb92f34c1 100644 --- a/neutron/tests/functional/agent/test_ovs_flows.py +++ b/neutron/tests/functional/agent/test_ovs_flows.py @@ -466,3 +466,23 @@ class OVSFlowTestCase(OVSAgentTestBase): "icmp_type=8,icmp_code=0,vlan_tci=%(vlan_tci)d" % kwargs) self.assertIn("pop_vlan,", trace["Datapath actions"]) + + def test_bundled_install(self): + if 'ovs_ofctl' in self.main_module: + self.skip("ovs-ofctl of_interface doesn't have bundled()") + + kwargs = {'in_port': 345, 'vlan_tci': 0x1321} + dst_p = self.useFixture( + net_helpers.OVSPortFixture(self.br_tun, self.namespace)).port + dst_ofp = self.br_tun.get_port_ofport(dst_p.name) + with self.br_tun.bundled() as br: + br.install_instructions("pop_vlan,output:%d" % dst_ofp, + priority=10, **kwargs) + trace = self._run_trace(self.br_tun.br_name, + "in_port=%(in_port)d,dl_src=12:34:56:78:aa:bb," + "dl_dst=24:12:56:78:aa:bb,dl_type=0x0800," + "nw_src=192.168.0.1,nw_dst=192.168.0.2," + "nw_proto=1,nw_tos=0,nw_ttl=128," + "icmp_type=8,icmp_code=0,vlan_tci=%(vlan_tci)d" + % kwargs) + self.assertIn("pop_vlan,", trace["Datapath actions"]) diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge_test_base.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge_test_base.py index 75d9a96635e..ced3df2d795 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge_test_base.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge_test_base.py @@ -65,7 +65,8 @@ class OVSBridgeTestBase(ovs_test_base.OVSRyuTestBase): instructions=[], match=ofpp.OFPMatch(in_port=in_port), priority=2, - table_id=0)), + table_id=0), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -85,7 +86,8 @@ class OVSBridgeTestBase(ovs_test_base.OVSRyuTestBase): ], match=ofpp.OFPMatch(in_port=in_port), priority=priority, - table_id=0)), + table_id=0), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -101,7 +103,8 @@ class OVSBridgeTestBase(ovs_test_base.OVSRyuTestBase): instructions=[], match=ofpp.OFPMatch(in_port=in_port), priority=priority, - table_id=0)), + table_id=0), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -121,7 +124,8 @@ class OVSBridgeTestBase(ovs_test_base.OVSRyuTestBase): ], match=ofpp.OFPMatch(in_port=in_port), priority=priority, - table_id=0)), + table_id=0), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -162,7 +166,8 @@ class OVSDVRProcessTestMixin(object): arp_tpa=gateway_ip, vlan_vid=vlan_tag | ofp.OFPVID_PRESENT), priority=3, - table_id=self.dvr_process_table_id)), + table_id=self.dvr_process_table_id), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -198,7 +203,8 @@ class OVSDVRProcessTestMixin(object): ip_proto=self.in_proto.IPPROTO_ICMPV6, vlan_vid=vlan_tag | ofp.OFPVID_PRESENT), priority=3, - table_id=self.dvr_process_table_id)), + table_id=self.dvr_process_table_id), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -235,7 +241,8 @@ class OVSDVRProcessTestMixin(object): eth_dst=vif_mac, vlan_vid=vlan_tag | ofp.OFPVID_PRESENT), priority=2, - table_id=self.dvr_process_table_id)), + table_id=self.dvr_process_table_id), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ @@ -249,7 +256,8 @@ class OVSDVRProcessTestMixin(object): eth_src=vif_mac, vlan_vid=vlan_tag | ofp.OFPVID_PRESENT), priority=1, - table_id=self.dvr_process_table_id)), + table_id=self.dvr_process_table_id), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py index e0a238d2bf5..5e264d61d19 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py @@ -38,7 +38,8 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): instructions=[], match=ofpp.OFPMatch(), priority=0, - table_id=23)), + table_id=23), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ @@ -46,7 +47,8 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): ], match=ofpp.OFPMatch(), priority=0, - table_id=0)), + table_id=0), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ @@ -57,13 +59,15 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): ], match=ofpp.OFPMatch(), priority=3, - table_id=60)), + table_id=60), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[], match=ofpp.OFPMatch(), priority=0, - table_id=24)), + table_id=24), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -88,7 +92,8 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): in_port=port, vlan_vid=segmentation_id | ofp.OFPVID_PRESENT), priority=3, - table_id=0)), + table_id=0), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -114,7 +119,8 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): in_port=port, vlan_vid=ofp.OFPVID_NONE), priority=3, - table_id=0)), + table_id=0), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -169,7 +175,8 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): eth_dst=dst_mac, vlan_vid=vlan_tag | ofp.OFPVID_PRESENT), priority=4, - table_id=1)), + table_id=1), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ @@ -182,7 +189,8 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): eth_dst=dst_mac, vlan_vid=vlan_tag | ofp.OFPVID_PRESENT), priority=4, - table_id=60)), + table_id=60), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -237,7 +245,8 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): eth_dst=dst_mac, vlan_vid=vlan_tag | ofp.OFPVID_PRESENT), priority=4, - table_id=2)), + table_id=2), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ @@ -250,7 +259,8 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): eth_dst=dst_mac, vlan_vid=vlan_tag | ofp.OFPVID_PRESENT), priority=4, - table_id=60)), + table_id=60), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -295,7 +305,8 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): eth_src=mac, in_port=port), priority=4, - table_id=0)) + table_id=0), + active_bundle=None) ] self.assertEqual(expected, self.mock.mock_calls) @@ -323,7 +334,8 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): eth_src=mac, in_port=port), priority=2, - table_id=0)) + table_id=0), + active_bundle=None) ] self.assertEqual(expected, self.mock.mock_calls) @@ -355,7 +367,8 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): in_port=8888, ), priority=2, - table_id=24)), + table_id=24), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ @@ -369,7 +382,8 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): in_port=8888, ), priority=2, - table_id=24)), + table_id=24), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ @@ -382,7 +396,8 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): in_port=8888, ), priority=10, - table_id=0)), + table_id=0), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -403,7 +418,8 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): in_port=8888, ), priority=2, - table_id=24)), + table_id=24), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ @@ -415,7 +431,8 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): in_port=8888 ), priority=2, - table_id=24)), + table_id=24), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ @@ -426,7 +443,8 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): in_port=8888, ), priority=10, - table_id=0)), + table_id=0), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_phys.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_phys.py index 7896a470a5d..e987698db3c 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_phys.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_phys.py @@ -52,7 +52,8 @@ class OVSPhysicalBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, ], match=ofpp.OFPMatch(), priority=0, - table_id=0)), + table_id=0), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -79,7 +80,8 @@ class OVSPhysicalBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, in_port=port, vlan_vid=lvid | ofp.OFPVID_PRESENT), priority=4, - table_id=0)), + table_id=0), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -105,7 +107,8 @@ class OVSPhysicalBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, in_port=port, vlan_vid=lvid | ofp.OFPVID_PRESENT), priority=4, - table_id=0)), + table_id=0), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -137,7 +140,8 @@ class OVSPhysicalBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, ], match=ofpp.OFPMatch(eth_src=mac), priority=2, - table_id=3)), + table_id=3), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_tun.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_tun.py index 5698fd04c7c..ee5bd9af26d 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_tun.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_tun.py @@ -59,41 +59,48 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, cookie=self.stamp, instructions=[ofpp.OFPInstructionGotoTable(table_id=2)], match=ofpp.OFPMatch(in_port=patch_int_ofport), - priority=1, table_id=0)), + priority=1, table_id=0), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[], match=ofpp.OFPMatch(), - priority=0, table_id=0)), + priority=0, table_id=0), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ofpp.OFPInstructionGotoTable(table_id=20)], match=ofpp.OFPMatch( eth_dst=('00:00:00:00:00:00', '01:00:00:00:00:00')), priority=0, - table_id=2)), + table_id=2), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ofpp.OFPInstructionGotoTable(table_id=22)], match=ofpp.OFPMatch( eth_dst=('01:00:00:00:00:00', '01:00:00:00:00:00')), priority=0, - table_id=2)), + table_id=2), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[], match=ofpp.OFPMatch(), - priority=0, table_id=3)), + priority=0, table_id=3), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[], match=ofpp.OFPMatch(), - priority=0, table_id=4)), + priority=0, table_id=4), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[], match=ofpp.OFPMatch(), - priority=0, table_id=6)), + priority=0, table_id=6), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ @@ -130,19 +137,22 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, ], match=ofpp.OFPMatch(), priority=1, - table_id=10)), + table_id=10), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ofpp.OFPInstructionGotoTable(table_id=22)], match=ofpp.OFPMatch(), priority=0, - table_id=20)), + table_id=20), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[], match=ofpp.OFPMatch(), priority=0, - table_id=22)) + table_id=22), + active_bundle=None) ] self.assertEqual(expected, self.mock.mock_calls) @@ -157,12 +167,14 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, cookie=self.stamp, instructions=[ofpp.OFPInstructionGotoTable(table_id=2)], match=ofpp.OFPMatch(in_port=patch_int_ofport), - priority=1, table_id=0)), + priority=1, table_id=0), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[], match=ofpp.OFPMatch(), - priority=0, table_id=0)), + priority=0, table_id=0), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ofpp.OFPInstructionGotoTable(table_id=21)], @@ -170,36 +182,42 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, eth_dst='ff:ff:ff:ff:ff:ff', eth_type=self.ether_types.ETH_TYPE_ARP), priority=1, - table_id=2)), + table_id=2), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ofpp.OFPInstructionGotoTable(table_id=20)], match=ofpp.OFPMatch( eth_dst=('00:00:00:00:00:00', '01:00:00:00:00:00')), priority=0, - table_id=2)), + table_id=2), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ofpp.OFPInstructionGotoTable(table_id=22)], match=ofpp.OFPMatch( eth_dst=('01:00:00:00:00:00', '01:00:00:00:00:00')), priority=0, - table_id=2)), + table_id=2), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[], match=ofpp.OFPMatch(), - priority=0, table_id=3)), + priority=0, table_id=3), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[], match=ofpp.OFPMatch(), - priority=0, table_id=4)), + priority=0, table_id=4), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[], match=ofpp.OFPMatch(), - priority=0, table_id=6)), + priority=0, table_id=6), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ @@ -236,25 +254,29 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, ], match=ofpp.OFPMatch(), priority=1, - table_id=10)), + table_id=10), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ofpp.OFPInstructionGotoTable(table_id=22)], match=ofpp.OFPMatch(), priority=0, - table_id=20)), + table_id=20), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[ofpp.OFPInstructionGotoTable(table_id=22)], match=ofpp.OFPMatch(), priority=0, - table_id=21)), + table_id=21), + active_bundle=None), call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp, instructions=[], match=ofpp.OFPMatch(), priority=0, - table_id=22)) + table_id=22), + active_bundle=None) ] self.assertEqual(expected, self.mock.mock_calls) @@ -280,7 +302,8 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, ], match=ofpp.OFPMatch(tunnel_id=segmentation_id), priority=1, - table_id=4)), + table_id=4), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -316,7 +339,8 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, ], match=ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT), priority=1, - table_id=22)), + table_id=22), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -353,7 +377,8 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, match=ofpp.OFPMatch( eth_dst=mac, vlan_vid=vlan | ofp.OFPVID_PRESENT), priority=2, - table_id=20)), + table_id=20), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -414,7 +439,8 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, arp_tpa=ip, vlan_vid=vlan | ofp.OFPVID_PRESENT), priority=1, - table_id=21)), + table_id=21), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -460,7 +486,8 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, ], match=ofpp.OFPMatch(in_port=port), priority=1, - table_id=0)), + table_id=0), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -488,7 +515,8 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, ], match=ofpp.OFPMatch(eth_src=mac), priority=1, - table_id=9)), + table_id=9), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_ofswitch.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_ofswitch.py new file mode 100644 index 00000000000..f7164a91212 --- /dev/null +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_ofswitch.py @@ -0,0 +1,106 @@ +# 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 ryu.ofproto import ofproto_v1_3 +from ryu.ofproto import ofproto_v1_3_parser + +from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \ + import ofswitch +from neutron.tests import base + + +class FakeReply(object): + def __init__(self, type): + self.type = type + + +class TestBundledOpenFlowBridge(base.BaseTestCase): + def setUp(self): + super(TestBundledOpenFlowBridge, self).setUp() + br = mock.Mock(spec=['install_instructions', 'foo']) + br._get_dp = lambda: (mock.Mock(), ofproto_v1_3, ofproto_v1_3_parser) + br.active_bundles = set() + self.br = ofswitch.BundledOpenFlowBridge(br, False, False) + + def test_method_calls(self): + self.br.install_instructions(dummy_arg=1) + self.br.br.install_instructions.assert_called_once_with(dummy_arg=1) + + def test_illegal_method_calls(self): + # With python3, this can be written as "with assertRaises..." + try: + self.br.uninstall_foo() + self.fail("Expected an exception") + except Exception as e: + self.assertIsInstance(e, AttributeError) + try: + self.br.foo() + self.fail("Expected an exception") + except Exception as e: + self.assertIsInstance(e, AttributeError) + + def test_normal_bundle_context(self): + self.assertIsNone(self.br.active_bundle) + self.br.br._send_msg = mock.Mock(side_effect=[ + FakeReply(ofproto_v1_3.ONF_BCT_OPEN_REPLY), + FakeReply(ofproto_v1_3.ONF_BCT_COMMIT_REPLY)]) + with self.br: + self.assertIsNotNone(self.br.active_bundle) + # Do nothing + # Assert that the active bundle is gone + self.assertIsNone(self.br.active_bundle) + + def test_aborted_bundle_context(self): + self.assertIsNone(self.br.active_bundle) + self.br.br._send_msg = mock.Mock(side_effect=[ + FakeReply(ofproto_v1_3.ONF_BCT_OPEN_REPLY), + FakeReply(ofproto_v1_3.ONF_BCT_DISCARD_REPLY)]) + try: + with self.br: + self.assertIsNotNone(self.br.active_bundle) + raise Exception() + except Exception: + pass + # Assert that the active bundle is gone + self.assertIsNone(self.br.active_bundle) + self.assertEqual(2, len(self.br.br._send_msg.mock_calls)) + args, kwargs = self.br.br._send_msg.call_args_list[0] + self.assertEqual(ofproto_v1_3.ONF_BCT_OPEN_REQUEST, + args[0].type) + args, kwargs = self.br.br._send_msg.call_args_list[1] + self.assertEqual(ofproto_v1_3.ONF_BCT_DISCARD_REQUEST, + args[0].type) + + def test_bundle_context_with_error(self): + self.assertIsNone(self.br.active_bundle) + self.br.br._send_msg = mock.Mock(side_effect=[ + FakeReply(ofproto_v1_3.ONF_BCT_OPEN_REPLY), + RuntimeError]) + try: + with self.br: + saved_bundle_id = self.br.active_bundle + self.assertIsNotNone(self.br.active_bundle) + self.fail("Expected an exception") + except RuntimeError: + pass + # Assert that the active bundle is gone + self.assertIsNone(self.br.active_bundle) + self.assertIn(saved_bundle_id, self.br.br.active_bundles) + + self.assertEqual(2, len(self.br.br._send_msg.mock_calls)) + args, kwargs = self.br.br._send_msg.call_args_list[0] + self.assertEqual(ofproto_v1_3.ONF_BCT_OPEN_REQUEST, + args[0].type) + args, kwargs = self.br.br._send_msg.call_args_list[1] + self.assertEqual(ofproto_v1_3.ONF_BCT_COMMIT_REQUEST, + args[0].type) diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_agent_extension_api.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_agent_extension_api.py index 9f88560a522..353574e0f02 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_agent_extension_api.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_agent_extension_api.py @@ -204,6 +204,7 @@ class TestOVSCookieBridgeRyu(native_ovs_bridge_test_base.OVSBridgeTestBase): instructions=[], match=ofpp.OFPMatch(in_port=in_port), priority=priority, - table_id=0)), + table_id=0), + active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls)