Move controller ovs functionality to a driver

Move all the ovs-specific code from the df_local_controller to a driver
that is loaded dynamically and wire it properly.

Partial-Bug: #1781376
Change-Id: I2e59042286154bd4f1c58e92c1513ddda97ebedb
This commit is contained in:
Shachar Snapiri 2018-07-10 15:56:09 +03:00
parent 41595e7453
commit cbb9993b3d
7 changed files with 128 additions and 67 deletions

View File

@ -21,16 +21,12 @@ import time
from eventlet import queue from eventlet import queue
from oslo_log import log from oslo_log import log
from oslo_service import loopingcall from oslo_service import loopingcall
from ryu.app.ofctl import service as of_service
from ryu.base import app_manager
from ryu import cfg as ryu_cfg
from dragonflow.common import exceptions from dragonflow.common import exceptions
from dragonflow.common import utils as df_utils from dragonflow.common import utils as df_utils
from dragonflow import conf as cfg from dragonflow import conf as cfg
from dragonflow.controller.common import constants as ctrl_const from dragonflow.controller.common import constants as ctrl_const
from dragonflow.controller import df_config from dragonflow.controller import df_config
from dragonflow.controller import ryu_base_app
from dragonflow.controller import service from dragonflow.controller import service
from dragonflow.controller import topology from dragonflow.controller import topology
from dragonflow.db import api_nb from dragonflow.db import api_nb
@ -41,9 +37,7 @@ from dragonflow.db import model_proxy
from dragonflow.db.models import core from dragonflow.db.models import core
from dragonflow.db.models import l2 from dragonflow.db.models import l2
from dragonflow.db.models import mixins from dragonflow.db.models import mixins
from dragonflow.db.models import ovs
from dragonflow.db import sync from dragonflow.db import sync
from dragonflow.ovsdb import vswitch_impl
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -66,24 +60,18 @@ class DfLocalController(object):
self.ip = cfg.CONF.df.local_ip self.ip = cfg.CONF.df.local_ip
# Virtual tunnel port support multiple tunnel types together # Virtual tunnel port support multiple tunnel types together
self.tunnel_types = cfg.CONF.df.tunnel_types self.tunnel_types = cfg.CONF.df.tunnel_types
self.sync_finished = False
self.vswitch_api = vswitch_impl.OvsApi(cfg.CONF.df.management_ip)
self.neutron_notifier = None self.neutron_notifier = None
if cfg.CONF.df.enable_neutron_notifier: if cfg.CONF.df.enable_neutron_notifier:
self.neutron_notifier = df_utils.load_driver( self.neutron_notifier = df_utils.load_driver(
cfg.CONF.df.neutron_notifier, cfg.CONF.df.neutron_notifier,
df_utils.DF_NEUTRON_NOTIFIER_DRIVER_NAMESPACE) df_utils.DF_NEUTRON_NOTIFIER_DRIVER_NAMESPACE)
self.switch_backend = df_utils.load_driver(
cfg.CONF.df.switch_backend,
df_utils.DF_SWITCH_BACKEND_DRIVER_NAMESPACE,
nb_api, cfg.CONF.df.management_ip)
app_mgr = app_manager.AppManager.get_instance() self.switch_backend.initialize(self.db_change_callback,
self.open_flow_app = app_mgr.instantiate( self.neutron_notifier)
ryu_base_app.RyuDFAdapter,
nb_api=self.nb_api,
vswitch_api=self.vswitch_api,
neutron_server_notifier=self.neutron_notifier,
db_change_callback=self.db_change_callback
)
# The OfctlService is needed to support the 'get_flows' method
self.open_flow_service = app_mgr.instantiate(of_service.OfctlService)
self.topology = None self.topology = None
self.enable_selective_topo_dist = \ self.enable_selective_topo_dist = \
cfg.CONF.df.enable_selective_topology_distribution cfg.CONF.df.enable_selective_topology_distribution
@ -113,11 +101,9 @@ class DfLocalController(object):
self._queue.task_done() self._queue.task_done()
def run(self): def run(self):
self.vswitch_api.initialize(self.db_change_callback)
self.nb_api.register_notification_callback(self._handle_update) self.nb_api.register_notification_callback(self._handle_update)
if cfg.CONF.df.enable_neutron_notifier: if self.neutron_notifier:
self.neutron_notifier.initialize(nb_api=self.nb_api, self.neutron_notifier.initialize(nb_api=self.nb_api)
is_neutron_server=False)
self.topology = topology.Topology(self, self.topology = topology.Topology(self,
self.enable_selective_topo_dist) self.enable_selective_topo_dist)
self._sync_pulse.start( self._sync_pulse.start(
@ -125,23 +111,7 @@ class DfLocalController(object):
initial_delay=cfg.CONF.df.db_sync_time, initial_delay=cfg.CONF.df.db_sync_time,
) )
# both set_controller and del_controller will delete flows. self.switch_backend.start()
# for reliability, here we should check if controller is set for OVS,
# if yes, don't set controller and don't delete controller.
# if no, set controller
targets = ('tcp:' + cfg.CONF.df_ryu.of_listen_address + ':' +
str(cfg.CONF.df_ryu.of_listen_port))
is_controller_set = self.vswitch_api.check_controller(targets)
integration_bridge = cfg.CONF.df.integration_bridge
if not is_controller_set:
self.vswitch_api.set_controller(integration_bridge, [targets])
is_fail_mode_set = self.vswitch_api.check_controller_fail_mode(
'secure')
if not is_fail_mode_set:
self.vswitch_api.set_controller_fail_mode(
integration_bridge, 'secure')
self.open_flow_service.start()
self.open_flow_app.start()
self._register_models() self._register_models()
self.register_chassis() self.register_chassis()
self.sync() self.sync()
@ -152,12 +122,12 @@ class DfLocalController(object):
ctrl_const.CONTROLLER_SYNC, None) ctrl_const.CONTROLLER_SYNC, None)
def _register_models(self): def _register_models(self):
ignore_models = self.switch_backend.sync_ignore_models()
for model in model_framework.iter_models_by_dependency_order(): for model in model_framework.iter_models_by_dependency_order():
# FIXME (dimak) generalize sync to support non-northbound models # FIXME (dimak) generalize sync to support non-northbound models
# Adding OvsPort will cause sync to delete all OVS ports # Adding OvsPort will cause sync to delete all OVS ports
# periodically # periodically
if model == ovs.OvsPort: if model not in ignore_models:
continue
self._sync.add_model(model) self._sync.add_model(model)
def sync(self): def sync(self):
@ -237,10 +207,10 @@ class DfLocalController(object):
self.db_store.delete(publisher) self.db_store.delete(publisher)
def switch_sync_finished(self): def switch_sync_finished(self):
self.open_flow_app.notify_switch_sync_finished() self.switch_backend.switch_sync_finished()
def switch_sync_started(self): def switch_sync_started(self):
self.open_flow_app.notify_switch_sync_started() self.switch_backend.switch_sync_started()
def _is_newer(self, obj, cached_obj): def _is_newer(self, obj, cached_obj):
'''Check wether obj is newer than cached_on. '''Check wether obj is newer than cached_on.
@ -290,11 +260,7 @@ class DfLocalController(object):
# TODO(snapiri): We should not have ovs here # TODO(snapiri): We should not have ovs here
# TODO(snapiri): This should be part of the interface # TODO(snapiri): This should be part of the interface
def notify_port_status(self, ovs_port, status): def notify_port_status(self, ovs_port, status):
if self.neutron_notifier: self.switch_backend.notify_port_status(ovs_port, status)
table_name = l2.LogicalPort.table_name
iface_id = ovs_port.lport
self.neutron_notifier.notify_neutron_server(table_name, iface_id,
'update', status)
def _get_delete_handler(self, table): def _get_delete_handler(self, table):
method_name = 'delete_{0}'.format(table) method_name = 'delete_{0}'.format(table)
@ -329,7 +295,7 @@ class DfLocalController(object):
action = update.action action = update.action
if action == ctrl_const.CONTROLLER_REINITIALIZE: if action == ctrl_const.CONTROLLER_REINITIALIZE:
self.db_store.clear() self.db_store.clear()
self.vswitch_api.initialize(self.db_change_callback) self.switch_backend.initialize(self.db_change_callback)
self.sync() self.sync()
elif action == ctrl_const.CONTROLLER_SYNC: elif action == ctrl_const.CONTROLLER_SYNC:
self.sync() self.sync()
@ -461,12 +427,6 @@ def _has_basic_events(obj):
return isinstance(obj, mixins.BasicEvents) return isinstance(obj, mixins.BasicEvents)
def init_ryu_config():
ryu_cfg.CONF(project='ryu', args=[])
ryu_cfg.CONF.ofp_listen_host = cfg.CONF.df_ryu.of_listen_address
ryu_cfg.CONF.ofp_tcp_listen_port = cfg.CONF.df_ryu.of_listen_port
# Run this application like this: # Run this application like this:
# python df_local_controller.py <chassis_unique_name> # python df_local_controller.py <chassis_unique_name>
# <local ip address> <southbound_db_ip_address> # <local ip address> <southbound_db_ip_address>
@ -474,7 +434,6 @@ def main():
chassis_name = cfg.CONF.host chassis_name = cfg.CONF.host
df_config.init(sys.argv) df_config.init(sys.argv)
init_ryu_config()
nb_api = api_nb.NbApi.get_instance() nb_api = api_nb.NbApi.get_instance()
controller = DfLocalController(chassis_name, nb_api) controller = DfLocalController(chassis_name, nb_api)
service.register_service('df-local-controller', nb_api) service.register_service('df-local-controller', nb_api)

View File

@ -0,0 +1,95 @@
# Copyright (c) 2018 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.
from ryu.app.ofctl import service as of_service
from ryu.base import app_manager
from ryu import cfg as ryu_cfg
from dragonflow import conf as cfg
from dragonflow.controller import ryu_base_app
from dragonflow.db.models import l2
from dragonflow.db.models import ovs
from dragonflow.ovsdb import vswitch_impl
from dragonflow.switch.drivers import df_switch_driver
class DfOvsDriver(df_switch_driver.DfSwitchDriver):
def __init__(self, nb_api, ip):
super(DfOvsDriver, self).__init__(nb_api)
init_ryu_config()
self.vswitch_api = vswitch_impl.OvsApi(ip)
self.app_mgr = app_manager.AppManager.get_instance()
self.open_flow_app = None
self.open_flow_service = None
self.neutron_notifier = None
def initialize(self, db_change_callback, neutron_notifier):
super(DfOvsDriver, self).initialize(db_change_callback,
neutron_notifier)
self.open_flow_app = self.app_mgr.instantiate(
ryu_base_app.RyuDFAdapter,
nb_api=self.nb_api,
vswitch_api=self.vswitch_api,
neutron_server_notifier=self.neutron_notifier,
db_change_callback=self.db_change_callback
)
# The OfctlService is needed to support the 'get_flows' method
self.open_flow_service = self.app_mgr.instantiate(
of_service.OfctlService)
def start(self):
self.vswitch_api.initialize(self.db_change_callback)
# both set_controller and del_controller will delete flows.
# for reliability, here we should check if controller is set for OVS,
# if yes, don't set controller and don't delete controller.
# if no, set controller
targets = ('tcp:' + cfg.CONF.df_ryu.of_listen_address + ':' +
str(cfg.CONF.df_ryu.of_listen_port))
is_controller_set = self.vswitch_api.check_controller(targets)
integration_bridge = cfg.CONF.df.integration_bridge
if not is_controller_set:
self.vswitch_api.set_controller(integration_bridge, [targets])
is_fail_mode_set = self.vswitch_api.check_controller_fail_mode(
'secure')
if not is_fail_mode_set:
self.vswitch_api.set_controller_fail_mode(integration_bridge,
'secure')
self.open_flow_service.start()
self.open_flow_app.start()
def stop(self):
pass
def switch_sync_started(self):
self.open_flow_app.notify_switch_sync_started()
def switch_sync_finished(self):
self.open_flow_app.notify_switch_sync_finished()
def sync_ignore_models(self):
return [ovs.OvsPort, ]
def notify_port_status(self, ovs_port, status):
if self.neutron_notifier:
table_name = l2.LogicalPort.table_name
iface_id = ovs_port.lport
self.neutron_notifier.notify_neutron_server(table_name, iface_id,
'update', status)
def init_ryu_config():
ryu_cfg.CONF(project='ryu', args=[])
ryu_cfg.CONF.ofp_listen_host = cfg.CONF.df_ryu.of_listen_address
ryu_cfg.CONF.ofp_tcp_listen_port = cfg.CONF.df_ryu.of_listen_port

View File

@ -25,8 +25,9 @@ class DfSwitchDriver(object):
self.db_change_callback = None self.db_change_callback = None
self.nb_api = nb_api self.nb_api = nb_api
def initialize(self, db_change_callback): def initialize(self, db_change_callback, neutron_notifier):
self.db_change_callback = db_change_callback self.db_change_callback = db_change_callback
self.neutron_notifier = neutron_notifier
@abc.abstractmethod @abc.abstractmethod
def start(self): def start(self):

View File

@ -83,16 +83,18 @@ class DFAppTestBase(tests_base.BaseTestCase):
self.nb_api = api_nb.NbApi.get_instance() self.nb_api = api_nb.NbApi.get_instance()
self.controller = df_local_controller.DfLocalController( self.controller = df_local_controller.DfLocalController(
fake_chassis1.id, self.nb_api) fake_chassis1.id, self.nb_api)
self.vswitch_api = self.controller.vswitch_api = mock.MagicMock() switch_backend = self.controller.switch_backend
self.vswitch_api = switch_backend.vswitch_api = mock.MagicMock()
kwargs = dict( kwargs = dict(
nb_api=self.controller.nb_api, nb_api=self.controller.nb_api,
vswitch_api=self.controller.vswitch_api vswitch_api=self.vswitch_api
) )
self.controller.open_flow_app = ryu_base_app.RyuDFAdapter( switch_backend.open_flow_app = ryu_base_app.RyuDFAdapter(
db_change_callback=self.controller.db_change_callback, **kwargs) db_change_callback=self.controller.db_change_callback,
self.open_flow_app = self.controller.open_flow_app **kwargs)
self.open_flow_app = switch_backend.open_flow_app
self.datapath = self.open_flow_app._datapath = mock.Mock() self.datapath = self.open_flow_app._datapath = mock.Mock()
self.open_flow_app.load(self.controller.open_flow_app, **kwargs) self.open_flow_app.load(self.open_flow_app, **kwargs)
self.topology = self.controller.topology = topology.Topology( self.topology = self.controller.topology = topology.Topology(
self.controller, enable_selective_topo_dist) self.controller, enable_selective_topo_dist)
cfg.CONF.set_override( cfg.CONF.set_override(

View File

@ -35,7 +35,8 @@ class TestChassisSNATApp(test_app_base.DFAppTestBase):
def test_switch_features_handler(self): def test_switch_features_handler(self):
ev = mock.Mock() ev = mock.Mock()
ev.msg.datapath.ofproto.OFP_VERSION = 0x04 ev.msg.datapath.ofproto.OFP_VERSION = 0x04
self.controller.open_flow_app.switch_features_handler(ev) open_flow_app = self.controller.switch_backend.open_flow_app
open_flow_app.switch_features_handler(ev)
self.SNAT_app.add_flow_go_to_table.assert_has_calls( self.SNAT_app.add_flow_go_to_table.assert_has_calls(
[mock.call( [mock.call(

View File

@ -32,8 +32,9 @@ class TestMigrationApp(test_app_base.DFAppTestBase):
self.controller.db_store.update(fake_lswitch) self.controller.db_store.update(fake_lswitch)
self.controller.db_store.update(lport) self.controller.db_store.update(lport)
self.controller.vswitch_api.get_chassis_ofport.return_value = 3 vswitch_api = self.controller.switch_backend.vswitch_api
self.controller.vswitch_api.get_port_ofport_by_id.return_value = 2 vswitch_api.get_chassis_ofport.return_value = 3
vswitch_api.get_port_ofport_by_id.return_value = 2
mock_update_patch = mock.patch.object( mock_update_patch = mock.patch.object(
self.controller.db_store, self.controller.db_store,

View File

@ -74,6 +74,8 @@ dragonflow.nb_db_driver =
_dummy_nb_db_driver = dragonflow.tests.database._dummy_db_driver:_DummyDbDriver _dummy_nb_db_driver = dragonflow.tests.database._dummy_db_driver:_DummyDbDriver
dragonflow.neutron_notifier_driver = dragonflow.neutron_notifier_driver =
nb_api_neutron_notifier_driver = dragonflow.db.pubsub_drivers.nb_api_neutron_notifier:NbApiNeutronNotifier nb_api_neutron_notifier_driver = dragonflow.db.pubsub_drivers.nb_api_neutron_notifier:NbApiNeutronNotifier
dragonflow.switch_backend_driver =
vswitch_backend_driver = dragonflow.switch.drivers.df_ovs_driver:DfOvsDriver
neutron.service_plugins = neutron.service_plugins =
df-l3-agentless = dragonflow.neutron.services.l3_router_plugin:DFL3AgentlessRouterPlugin df-l3-agentless = dragonflow.neutron.services.l3_router_plugin:DFL3AgentlessRouterPlugin
df-l3 = dragonflow.neutron.services.l3_router_plugin:DFL3RouterPlugin df-l3 = dragonflow.neutron.services.l3_router_plugin:DFL3RouterPlugin