Use ovs_port_updated event to update ofport and mac

Currently, df apps use ovs idl to query the mac of ovs Interface.
ovs_port_updated event was introduced in [1]. And there is already an
ovs Interface object in the event. So, df apps can just get mac address
from that event. This can save a query from ovs db, and also stop abuse
of ovs idl.

[1] 19232467d6

Change-Id: I31d86ce224d43d61cb8861907608248647f10015
Related-Bug: #1622879
This commit is contained in:
Hong Hui Xiao 2016-09-21 06:02:56 +08:00
parent 585c38f22b
commit 6c328c7614
7 changed files with 143 additions and 76 deletions

View File

@ -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)

View File

@ -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):
"""

View File

@ -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()

View File

@ -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))

View File

@ -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})

View File

@ -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()

View File

@ -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()