diff --git a/dragonflow/controller/dnat_app.py b/dragonflow/controller/dnat_app.py index dcf1f68fb..a376d7768 100644 --- a/dragonflow/controller/dnat_app.py +++ b/dragonflow/controller/dnat_app.py @@ -16,10 +16,8 @@ import collections import netaddr -from neutron.agent.ovsdb.native import idlutils from neutron_lib import constants as n_const from oslo_config import cfg -from oslo_service import loopingcall from ryu.ofproto import ether import six @@ -39,8 +37,6 @@ DF_DNAT_APP_OPTS = [ cfg.StrOpt('ex_peer_patch_port', default='patch-int', help=_("Peer patch port in external bridge for integration " "bridge.")), - cfg.IntOpt('send_arp_interval', default=5, - help=_("Polling interval for arp request in seconds")) ] FIP_GW_RESOLVING_STATUS = 'resolving' @@ -55,50 +51,31 @@ class DNATApp(df_base_app.DFlowApp): cfg.CONF.register_opts(DF_DNAT_APP_OPTS, group='df_dnat_app') self.external_network_bridge = \ cfg.CONF.df_dnat_app.external_network_bridge + self.external_bridge_mac = "" self.integration_bridge = cfg.CONF.df.integration_bridge self.int_peer_patch_port = cfg.CONF.df_dnat_app.int_peer_patch_port self.ex_peer_patch_port = cfg.CONF.df_dnat_app.ex_peer_patch_port - self.send_arp_interval = cfg.CONF.df_dnat_app.send_arp_interval self.external_networks = collections.defaultdict(int) self.local_floatingips = collections.defaultdict(str) def switch_features_handler(self, ev): self._init_external_bridge() - self._init_external_network_bridge_check() self._install_output_to_physical_patch(self.external_ofport) - def _check_for_external_network_bridge_mac(self): - idl = self.vswitch_api.idl - if not idl: + def ovs_port_updated(self, ovs_port): + if ovs_port.get_name() != self.external_network_bridge: return - interface = idlutils.row_by_value( - idl, - 'Interface', - 'name', - self.external_network_bridge, - None, - ) - if not interface: - return - if not interface.mac_in_use[0]: - return - if interface.mac_in_use[0] == '00:00:00:00:00:00': - return - return interface.mac_in_use[0] - def _wait_for_external_network_bridge_mac(self): - mac = self._check_for_external_network_bridge_mac() - if not mac: + mac = ovs_port.get_mac_in_use() + if (self.external_bridge_mac == mac + or not mac + or mac == '00:00:00:00:00:00'): return + for key, floatingip in six.iteritems(self.local_floatingips): self._install_dnat_egress_rules(floatingip, mac) - raise loopingcall.LoopingCallDone() - def _init_external_network_bridge_check(self): - """Spawn a thread to check that br-ex is ready.""" - periodic = loopingcall.FixedIntervalLoopingCall( - self._wait_for_external_network_bridge_mac) - periodic.start(interval=self.send_arp_interval) + self.external_bridge_mac = mac def _init_external_bridge(self): self.external_ofport = self.vswitch_api.create_patch_port( @@ -272,9 +249,9 @@ class DNATApp(df_base_app.DFlowApp): const.PRIORITY_MEDIUM, const.EGRESS_NAT_TABLE, match=match) - mac = self._check_for_external_network_bridge_mac() - if mac: - self._install_dnat_egress_rules(floatingip, mac) + if self.external_bridge_mac: + self._install_dnat_egress_rules(floatingip, + self.external_bridge_mac) def _remove_egress_nat_rules(self, floatingip): net = netaddr.IPNetwork(floatingip.get_external_cidr()) @@ -366,16 +343,6 @@ class DNATApp(df_base_app.DFlowApp): self._install_ingress_nat_rules(floatingip) self._install_egress_nat_rules(floatingip) - def update_bridge_port(self, lport): - port_name = lport.get_name() - if port_name != self.external_network_bridge: - return - mac = self._check_for_external_network_bridge_mac() - if not mac: - return - for key, floatingip in six.iteritems(self.local_floatingips): - self._install_dnat_egress_rules(floatingip, mac) - def delete_floatingip(self, floatingip): self._remove_ingress_nat_rules(floatingip) self._remove_egress_nat_rules(floatingip) diff --git a/dragonflow/controller/metadata_service_app.py b/dragonflow/controller/metadata_service_app.py index 62b256cb4..8927b706f 100644 --- a/dragonflow/controller/metadata_service_app.py +++ b/dragonflow/controller/metadata_service_app.py @@ -25,8 +25,6 @@ from oslo_config import cfg from oslo_log import log from oslo_utils import encodeutils -from neutron.agent.ovsdb.native import idlutils - from dragonflow._i18n import _, _LW, _LE from dragonflow.common import exceptions from dragonflow.common import utils as df_utils @@ -76,6 +74,7 @@ class MetadataServiceApp(df_base_app.DFlowApp): ) self._arp_responder = None self._ofport = None + self._interface_mac = "" cfg.CONF.register_opts(options, group='df_metadata') self._ip = cfg.CONF.df_metadata.ip self._port = cfg.CONF.df_metadata.port @@ -85,7 +84,20 @@ class MetadataServiceApp(df_base_app.DFlowApp): if ovs_port.get_name() != cfg.CONF.df.metadata_interface: return - self._add_metadata_interface_flows() + ofport = ovs_port.get_ofport() + mac = ovs_port.get_mac_in_use() + if not ofport or not mac: + return + + if ofport <= 0: + return + + if ofport == self._ofport and mac == self._interface_mac: + return + + self._add_tap_metadata_port(ofport, mac) + self._ofport = ofport + self._interface_mac = mac def ovs_port_deleted(self, ovs_port): if ovs_port.get_name() != cfg.CONF.df.metadata_interface: @@ -110,28 +122,7 @@ class MetadataServiceApp(df_base_app.DFlowApp): match=parser.OFPMatch(in_port=self._ofport)) self._ofport = None - - def _add_metadata_interface_flows(self): - idl = self.vswitch_api.idl - if not idl: - return - interface = idlutils.row_by_value( - idl, - 'Interface', - 'name', - self._interface, - None, - ) - if not interface: - return - ofport = interface.ofport - if not ofport: - return - if isinstance(ofport, list): - ofport = ofport[0] - if ofport <= 0: - return - self._add_tap_metadata_port(ofport, interface.mac_in_use[0]) + self._interface_mac = "" def _add_tap_metadata_port(self, ofport, mac): """ diff --git a/dragonflow/db/api_nb.py b/dragonflow/db/api_nb.py index f976a36f8..c2eafb0bc 100644 --- a/dragonflow/db/api_nb.py +++ b/dragonflow/db/api_nb.py @@ -1155,6 +1155,9 @@ class OvsPort(DbStoreObject): def get_attached_mac(self): return self.ovs_port.get_attached_mac() + def get_mac_in_use(self): + return self.ovs_port.get_mac_in_use() + def get_remote_ip(self): return self.ovs_port.get_remote_ip() diff --git a/dragonflow/db/api_vswitch.py b/dragonflow/db/api_vswitch.py index 5155e2631..c3d9c3ed9 100644 --- a/dragonflow/db/api_vswitch.py +++ b/dragonflow/db/api_vswitch.py @@ -74,6 +74,7 @@ class LocalInterface(object): self.iface_id = "" self.peer = "" self.attached_mac = "" + self.mac_in_use = "" self.remote_ip = "" self.tunnel_type = "" @@ -103,6 +104,8 @@ class LocalInterface(object): result.uuid = row.uuid if row.ofport: result.ofport = int(row.ofport[0]) + if row.mac_in_use: + result.mac_in_use = row.mac_in_use[0] result.name = row.name if row.admin_state: result.admin_state = row.admin_state[0] @@ -141,6 +144,9 @@ class LocalInterface(object): def get_attached_mac(self): return self.attached_mac + def get_mac_in_use(self): + return self.mac_in_use + def get_remote_ip(self): return self.remote_ip @@ -150,11 +156,11 @@ class LocalInterface(object): def __str__(self): if self.ofport is None: self.ofport = -1 - return "uuid:%s, ofport:%d, name:%s, " \ - "admin_state:%s, type:%s, " \ - "iface_id:%s, peer:%s, " \ - "attached_mac:%s, remote_ip:%s, " \ - "tunnel_type:%s" % (self.uuid, + return ("uuid:%s, ofport:%d, name:%s, " + "admin_state:%s, type:%s, " + "iface_id:%s, peer:%s, " + "attached_mac:%s, mac_in_use:%s, remote_ip:%s, " + "tunnel_type:%s" % (self.uuid, self.ofport, self.name, self.admin_state, @@ -162,5 +168,6 @@ class LocalInterface(object): self.iface_id, self.peer, self.attached_mac, + self.mac_in_use, self.remote_ip, - self.tunnel_type) + self.tunnel_type)) diff --git a/dragonflow/tests/unit/test_app_base.py b/dragonflow/tests/unit/test_app_base.py index 2df3f7e5d..7cc5d998a 100644 --- a/dragonflow/tests/unit/test_app_base.py +++ b/dragonflow/tests/unit/test_app_base.py @@ -18,6 +18,7 @@ from oslo_config import cfg from dragonflow.controller import df_local_controller from dragonflow.controller import ryu_base_app +from dragonflow.controller import topology from dragonflow.db import api_nb from dragonflow.tests import base as tests_base @@ -28,6 +29,7 @@ class DFAppTestBase(tests_base.BaseTestCase): def setUp(self): cfg.CONF.set_override('apps_list', self.apps_list, group='df') super(DFAppTestBase, self).setUp() + mock.patch('ryu.base.app_manager.AppManager.get_instance').start() self.controller = df_local_controller.DfLocalController('fake_host') self.nb_api = self.controller.nb_api = mock.MagicMock() self.vswitch_api = self.controller.vswitch_api = mock.MagicMock() @@ -40,6 +42,7 @@ class DFAppTestBase(tests_base.BaseTestCase): self.open_flow_app = self.controller.open_flow_app self.datapath = self.open_flow_app._datapath = mock.Mock() self.open_flow_app.load(self.controller.open_flow_app, **kwargs) + self.controller.topology = topology.Topology(self.controller, False) self.vswitch_api.get_local_ports_to_ofport_mapping.return_value = ( {}, {fake_local_port1.get_id(): 2}) diff --git a/dragonflow/tests/unit/test_dnat_app.py b/dragonflow/tests/unit/test_dnat_app.py index eb27f8edc..8be9a0f9c 100644 --- a/dragonflow/tests/unit/test_dnat_app.py +++ b/dragonflow/tests/unit/test_dnat_app.py @@ -53,3 +53,40 @@ class TestDNATApp(test_app_base.DFAppTestBase): self.dnat_app.mod_flow.assert_called_once_with( self.datapath, inst=mock.ANY, table_id=constants.INGRESS_NAT_TABLE, priority=constants.PRIORITY_MEDIUM, match=mock.ANY) + + def test_external_bridge_online(self): + self.dnat_app.local_floatingips[ + test_app_base.fake_floatingip1.get_id()] = ( + test_app_base.fake_floatingip1) + + with mock.patch.object(self.dnat_app, + '_install_dnat_egress_rules') as mock_func: + fake_ovs_port = mock.Mock() + fake_ovs_port.get_ofport = mock.Mock(return_value=-1) + fake_ovs_port.get_name = mock.Mock( + return_value=self.dnat_app.external_network_bridge) + # Device without mac will not trigger update flow + fake_ovs_port.get_mac_in_use = mock.Mock(return_value="") + self.controller.ovs_port_updated(fake_ovs_port) + mock_func.assert_not_called() + mock_func.reset_mock() + + # Other device update will not trigger update flow + fake_ovs_port.get_mac_in_use = mock.Mock( + return_value="aa:bb:cc:dd:ee:ff") + fake_ovs_port.get_name = mock.Mock(return_value="no-bridge") + self.controller.ovs_port_updated(fake_ovs_port) + mock_func.assert_not_called() + mock_func.reset_mock() + + # Device with mac will trigger update flow + fake_ovs_port.get_name = mock.Mock( + return_value=self.dnat_app.external_network_bridge) + self.controller.ovs_port_updated(fake_ovs_port) + mock_func.assert_called_once_with(test_app_base.fake_floatingip1, + "aa:bb:cc:dd:ee:ff") + mock_func.reset_mock() + + # Duplicated updated will not trigger update flow + self.controller.ovs_port_updated(fake_ovs_port) + mock_func.assert_not_called() diff --git a/dragonflow/tests/unit/test_metadata_service_app.py b/dragonflow/tests/unit/test_metadata_service_app.py new file mode 100644 index 000000000..10842c107 --- /dev/null +++ b/dragonflow/tests/unit/test_metadata_service_app.py @@ -0,0 +1,59 @@ +# Copyright (c) 2016 OpenStack Foundation. +# 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 dragonflow.tests.unit import test_app_base + + +class TestMetadataServiceApp(test_app_base.DFAppTestBase): + apps_list = "metadata_service_app.MetadataServiceApp" + + def setUp(self): + super(TestMetadataServiceApp, self).setUp() + self.meta_app = self.open_flow_app.dispatcher.apps[0] + + def test_metadata_interface_online(self): + with mock.patch.object(self.meta_app, + '_add_tap_metadata_port') as mock_func: + fake_ovs_port = mock.Mock() + fake_ovs_port.get_ofport = mock.Mock(return_value=1) + fake_ovs_port.get_name = mock.Mock( + return_value=self.meta_app._interface) + # Device without mac will not trigger update flow + fake_ovs_port.get_mac_in_use = mock.Mock(return_value="") + self.controller.ovs_port_updated(fake_ovs_port) + mock_func.assert_not_called() + mock_func.reset_mock() + + # Other device update will not trigger update flow + fake_ovs_port.get_mac_in_use = mock.Mock( + return_value="aa:bb:cc:dd:ee:ff") + fake_ovs_port.get_name = mock.Mock(return_value="no-interface") + self.controller.ovs_port_updated(fake_ovs_port) + mock_func.assert_not_called() + mock_func.reset_mock() + + # Device with mac will trigger update flow + fake_ovs_port.get_name = mock.Mock( + return_value=self.meta_app._interface) + self.controller.ovs_port_updated(fake_ovs_port) + mock_func.assert_called_once_with(1, + "aa:bb:cc:dd:ee:ff") + mock_func.reset_mock() + + # Duplicated updated will not trigger update flow + self.controller.ovs_port_updated(fake_ovs_port) + mock_func.assert_not_called()