From 7367754398eef00e9db63e0b97b3bf105e38321f Mon Sep 17 00:00:00 2001 From: Dima Kuznetsov Date: Wed, 5 Jul 2017 21:12:04 +0300 Subject: [PATCH] Use stevedore to load apps We used to load apps by listing module and class names in a configuration entry. This patch defines entry points for all present apps, so we don't have to import them by module path, additionally, other packages can register new apps, allowing further extensibility. Closes-Bug: #1702462 Change-Id: Iffa0fea3db36df2f59ef19a621b41838d66cb8d7 --- devstack/ovs_dpdk_setup.sh | 2 +- devstack/ovs_setup.sh | 2 +- devstack/plugin.sh | 17 ++++++------- doc/source/manual_deployment.rst | 2 +- dragonflow/conf/df_common_params.py | 4 +-- dragonflow/controller/dispatcher.py | 25 +++++++++---------- dragonflow/controller/ryu_base_app.py | 3 +-- dragonflow/db/api_nb.py | 2 +- dragonflow/tests/fullstack/test_snat_flows.py | 2 +- .../tests/unit/test_chassis_snat_app.py | 2 +- dragonflow/tests/unit/test_classifier_app.py | 2 +- .../tests/unit/test_df_local_controller.py | 2 +- dragonflow/tests/unit/test_dhcp_app.py | 2 +- dragonflow/tests/unit/test_dispatcher.py | 2 +- dragonflow/tests/unit/test_dnat_app.py | 2 +- dragonflow/tests/unit/test_l2_app.py | 2 +- dragonflow/tests/unit/test_l3_app.py | 2 +- .../tests/unit/test_l3_proactive_app.py | 2 +- dragonflow/tests/unit/test_legacy_snat.py | 2 +- .../tests/unit/test_metadata_service_app.py | 2 +- dragonflow/tests/unit/test_migration_app.py | 2 +- .../tests/unit/test_provider_net_app.py | 2 +- dragonflow/tests/unit/test_sg_app.py | 2 +- dragonflow/tests/unit/test_topology.py | 2 +- dragonflow/tests/unit/test_trunk_app.py | 2 +- dragonflow/tests/unit/test_tunneling_app.py | 2 +- setup.cfg | 19 ++++++++++++++ 27 files changed, 62 insertions(+), 50 deletions(-) diff --git a/devstack/ovs_dpdk_setup.sh b/devstack/ovs_dpdk_setup.sh index 810f62f4e..2fc5671a1 100644 --- a/devstack/ovs_dpdk_setup.sh +++ b/devstack/ovs_dpdk_setup.sh @@ -138,7 +138,7 @@ function configure_ovs { if is_service_enabled df-controller ; then # setup external bridge if necessary - check_dnat=$(echo $DF_APPS_LIST | grep "DNATApp") + check_dnat=$(echo $DF_APPS_LIST | grep "dnat") if [[ "$check_dnat" != "" ]]; then echo "Setup external bridge for DNAT" sudo ovs-vsctl add-br $PUBLIC_BRIDGE || true diff --git a/devstack/ovs_setup.sh b/devstack/ovs_setup.sh index 4051395bd..537aa9700 100644 --- a/devstack/ovs_setup.sh +++ b/devstack/ovs_setup.sh @@ -158,7 +158,7 @@ function start_ovs { function configure_ovs { if is_service_enabled df-controller ; then # setup external bridge if necessary - check_dnat=$(echo $DF_APPS_LIST | grep "DNATApp") + check_dnat=$(echo $DF_APPS_LIST | grep "dnat") if [[ "$check_dnat" != "" ]]; then echo "Setup external bridge for DNAT" sudo ovs-vsctl add-br $PUBLIC_BRIDGE || true diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 7f1077632..f5d238c41 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -17,36 +17,33 @@ OVS_BRANCH=${OVS_BRANCH:-branch-2.6} EXTERNAL_HOST_IP=${EXTERNAL_HOST_IP:-} DEFAULT_TUNNEL_TYPES="vxlan,geneve,gre" -DEFAULT_APPS_LIST="l2_app.L2App,l3_proactive_app.L3ProactiveApp,"\ -"dhcp_app.DHCPApp,dnat_app.DNATApp,sg_app.SGApp,portsec_app.PortSecApp,"\ -"portqos_app.PortQosApp,classifier_app.ClassifierApp,tunneling_app.TunnelingApp,"\ -"provider_networks_app.ProviderNetworksApp" +DEFAULT_APPS_LIST="l2,l3_proactive,dhcp,dnat,sg,portsec,portqos,classifier,tunneling,provider" if is_service_enabled df-metadata ; then - DEFAULT_APPS_LIST="$DEFAULT_APPS_LIST,metadata_service_app.MetadataServiceApp" + DEFAULT_APPS_LIST="$DEFAULT_APPS_LIST,metadata_service" fi if is_service_enabled q-trunk ; then - DEFAULT_APPS_LIST="$DEFAULT_APPS_LIST,trunk_app.TrunkApp" + DEFAULT_APPS_LIST="$DEFAULT_APPS_LIST,trunk" fi ENABLE_ACTIVE_DETECTION=${ENABLE_ACTIVE_DETECTION:-True} if [[ "$ENABLE_ACTIVE_DETECTION" == "True" ]]; then - DEFAULT_APPS_LIST="$DEFAULT_APPS_LIST,active_port_detection_app.ActivePortDetectionApp" + DEFAULT_APPS_LIST="$DEFAULT_APPS_LIST,active_port_detection" fi ENABLE_LIVE_MIGRATION=${ENABLE_LIVE_MIGRATION:-True} if [[ "$ENABLE_LIVE_MIGRATION" == "True" ]]; then - DEFAULT_APPS_LIST="$DEFAULT_APPS_LIST,migration_app.MigrationApp" + DEFAULT_APPS_LIST="$DEFAULT_APPS_LIST,migration" fi if [[ ! -z ${EXTERNAL_HOST_IP} ]]; then - DEFAULT_APPS_LIST="$DEFAULT_APPS_LIST,chassis_snat_app.ChassisSNATApp" + DEFAULT_APPS_LIST="$DEFAULT_APPS_LIST,chassis_snat" fi ENABLED_AGING_APP=${ENABLE_AGING_APP:-True} if [[ "$ENABLE_AGING_APP" == "True" ]]; then - DEFAULT_APPS_LIST="aging_app.Aging,$DEFAULT_APPS_LIST" + DEFAULT_APPS_LIST="aging,$DEFAULT_APPS_LIST" fi DF_APPS_LIST=${DF_APPS_LIST:-$DEFAULT_APPS_LIST} diff --git a/doc/source/manual_deployment.rst b/doc/source/manual_deployment.rst index c7bb3f343..2a8e79821 100644 --- a/doc/source/manual_deployment.rst +++ b/doc/source/manual_deployment.rst @@ -95,7 +95,7 @@ Basic Configurations [df] metadata_interface = tap-metadata enable_selective_topology_distribution = True - apps_list = l2_app.L2App,l3_proactive_app.L3ProactiveApp,dhcp_app.DHCPApp,dnat_app.DNATApp,sg_app.SGApp,portsec_app.PortSecApp,portqos_app.PortQosApp + apps_list = l2,l3_proactive,dhcp,dnat,sg,portsec,portqos integration_bridge = br-int tunnel_type = geneve diff --git a/dragonflow/conf/df_common_params.py b/dragonflow/conf/df_common_params.py index 07e1a42c4..62700af44 100644 --- a/dragonflow/conf/df_common_params.py +++ b/dragonflow/conf/df_common_params.py @@ -41,9 +41,7 @@ df_opts = [ default=False, help=_("Enable dpdk")), cfg.StrOpt('apps_list', - default='l2_app.L2App,' - 'l3_proactive_app.L3ProactiveApp,' - 'dhcp_app.DHCPApp', + default='l2,l3_proactive,dhcp', help=_('List of openflow applications classes to load')), cfg.StrOpt('integration_bridge', default='br-int', help=_("Integration bridge to use. " diff --git a/dragonflow/controller/dispatcher.py b/dragonflow/controller/dispatcher.py index 355317026..3963a1e71 100644 --- a/dragonflow/controller/dispatcher.py +++ b/dragonflow/controller/dispatcher.py @@ -11,9 +11,8 @@ # under the License. from oslo_log import log -from oslo_utils import importutils +import stevedore -from dragonflow._i18n import _ from dragonflow.common import exceptions LOG = log.getLogger(__name__) @@ -21,21 +20,21 @@ LOG = log.getLogger(__name__) class AppDispatcher(object): - def __init__(self, apps_location_prefix, app_list): - self.apps_location_prefix = apps_location_prefix + def __init__(self, app_list): self.apps_list = app_list.split(',') self.apps = [] def load(self, *args, **kwargs): - for app in self.apps_list: - app_class_name = self.apps_location_prefix + "." + app - try: - app_class = importutils.import_class(app_class_name) - app = app_class(*args, **kwargs) - self.apps.append(app) - except ImportError as e: - LOG.exception("Error loading application by class, %s", e) - raise ImportError(_("Application class not found.")) + mgr = stevedore.NamedExtensionManager( + 'dragonflow.controller.apps', + self.apps_list, + invoke_on_load=True, + invoke_args=args, + invoke_kwds=kwargs, + ) + + for ext in mgr: + self.apps.append(ext.obj) def dispatch(self, method, *args, **kwargs): errors = [] diff --git a/dragonflow/controller/ryu_base_app.py b/dragonflow/controller/ryu_base_app.py index 80c399873..42ef1fb38 100644 --- a/dragonflow/controller/ryu_base_app.py +++ b/dragonflow/controller/ryu_base_app.py @@ -39,8 +39,7 @@ class RyuDFAdapter(ofp_handler.OFPHandler): def __init__(self, vswitch_api=None, nb_api=None, neutron_server_notifier=None): super(RyuDFAdapter, self).__init__() - self.dispatcher = dispatcher.AppDispatcher('dragonflow.controller', - cfg.CONF.df.apps_list) + self.dispatcher = dispatcher.AppDispatcher(cfg.CONF.df.apps_list) self.vswitch_api = vswitch_api self.nb_api = nb_api self.neutron_server_notifier = neutron_server_notifier diff --git a/dragonflow/db/api_nb.py b/dragonflow/db/api_nb.py index edbb88c0a..686bb005e 100644 --- a/dragonflow/db/api_nb.py +++ b/dragonflow/db/api_nb.py @@ -91,7 +91,7 @@ class NbApi(object): # FIXME(nick-ma-z): if active-detection is enabled, # we initialize the publisher here. Make sure it # only supports redis-based pub/sub driver. - if "ActivePortDetectionApp" in cfg.CONF.df.apps_list: + if "active_port_detection" in cfg.CONF.df.apps_list: self.publisher.initialize() # NOTE(gampel) we want to start queuing event as soon diff --git a/dragonflow/tests/fullstack/test_snat_flows.py b/dragonflow/tests/fullstack/test_snat_flows.py index f352cbe37..c9c2304e9 100644 --- a/dragonflow/tests/fullstack/test_snat_flows.py +++ b/dragonflow/tests/fullstack/test_snat_flows.py @@ -16,7 +16,7 @@ from dragonflow.tests.common import utils from dragonflow.tests.fullstack import test_base from dragonflow.tests.fullstack import test_objects as objects -SNAT_APP_NAME = 'chassis_snat_app.ChassisSNATApp' +SNAT_APP_NAME = 'chassis_snat' class TestSnatFlows(test_base.DFTestBase): diff --git a/dragonflow/tests/unit/test_chassis_snat_app.py b/dragonflow/tests/unit/test_chassis_snat_app.py index a760f1c69..4c3a453be 100644 --- a/dragonflow/tests/unit/test_chassis_snat_app.py +++ b/dragonflow/tests/unit/test_chassis_snat_app.py @@ -21,7 +21,7 @@ from dragonflow.tests.unit import test_app_base class TestChassisSNATApp(test_app_base.DFAppTestBase): - apps_list = "chassis_snat_app.ChassisSNATApp" + apps_list = "chassis_snat" external_host_ip = '172.24.4.100' def setUp(self): diff --git a/dragonflow/tests/unit/test_classifier_app.py b/dragonflow/tests/unit/test_classifier_app.py index ceda035b7..ed3e3ca0a 100644 --- a/dragonflow/tests/unit/test_classifier_app.py +++ b/dragonflow/tests/unit/test_classifier_app.py @@ -22,7 +22,7 @@ make_fake_local_port = test_app_base.make_fake_local_port class TestClassifierApp(test_app_base.DFAppTestBase): - apps_list = "classifier_app.ClassifierApp" + apps_list = "classifier" def setUp(self): super(TestClassifierApp, self).setUp() diff --git a/dragonflow/tests/unit/test_df_local_controller.py b/dragonflow/tests/unit/test_df_local_controller.py index c5fef46ab..dab20cf3c 100644 --- a/dragonflow/tests/unit/test_df_local_controller.py +++ b/dragonflow/tests/unit/test_df_local_controller.py @@ -36,7 +36,7 @@ class _ModelNoEvents(model_framework.ModelBase, mixins.Version): class DfLocalControllerTestCase(test_app_base.DFAppTestBase): - apps_list = "l2_app.L2App" + apps_list = "l2" @mock.patch.object(ryu_base_app.RyuDFAdapter, 'notify_ovs_sync_finished') def test_ovs_sync_finished(self, mock_notify): diff --git a/dragonflow/tests/unit/test_dhcp_app.py b/dragonflow/tests/unit/test_dhcp_app.py index 752252a15..76225b645 100644 --- a/dragonflow/tests/unit/test_dhcp_app.py +++ b/dragonflow/tests/unit/test_dhcp_app.py @@ -32,7 +32,7 @@ class Option(object): class TestDHCPApp(test_app_base.DFAppTestBase): - apps_list = "dhcp_app.DHCPApp" + apps_list = "dhcp" def setUp(self): super(TestDHCPApp, self).setUp() diff --git a/dragonflow/tests/unit/test_dispatcher.py b/dragonflow/tests/unit/test_dispatcher.py index 5576d78fd..2f5080bae 100644 --- a/dragonflow/tests/unit/test_dispatcher.py +++ b/dragonflow/tests/unit/test_dispatcher.py @@ -30,7 +30,7 @@ class TestAppDispatcher(tests_base.BaseTestCase): def setUp(self): super(TestAppDispatcher, self).setUp() - self.dispatcher = dispatcher.AppDispatcher("", "") + self.dispatcher = dispatcher.AppDispatcher("") def test_dispatch_with_exception(self): fake_app = mock.MagicMock() diff --git a/dragonflow/tests/unit/test_dnat_app.py b/dragonflow/tests/unit/test_dnat_app.py index 66114bb77..f508ab834 100644 --- a/dragonflow/tests/unit/test_dnat_app.py +++ b/dragonflow/tests/unit/test_dnat_app.py @@ -46,7 +46,7 @@ remote_lport.is_local = False class TestDNATApp(test_app_base.DFAppTestBase): - apps_list = "dnat_app.DNATApp" + apps_list = "dnat" def setUp(self): super(TestDNATApp, self).setUp(enable_selective_topo_dist=True) diff --git a/dragonflow/tests/unit/test_l2_app.py b/dragonflow/tests/unit/test_l2_app.py index 54ca0cc9f..848d81d8f 100644 --- a/dragonflow/tests/unit/test_l2_app.py +++ b/dragonflow/tests/unit/test_l2_app.py @@ -21,7 +21,7 @@ from dragonflow.tests.unit import test_app_base class TestL2App(test_app_base.DFAppTestBase): - apps_list = "l2_app.L2App" + apps_list = "l2" def setUp(self): super(TestL2App, self).setUp() diff --git a/dragonflow/tests/unit/test_l3_app.py b/dragonflow/tests/unit/test_l3_app.py index 5a794a72d..0c4b93759 100644 --- a/dragonflow/tests/unit/test_l3_app.py +++ b/dragonflow/tests/unit/test_l3_app.py @@ -23,7 +23,7 @@ from dragonflow.tests.unit import test_app_base class TestL3App(test_app_base.DFAppTestBase, _test_l3.L3AppTestCaseMixin): - apps_list = "l3_app.L3App" + apps_list = "l3_reactive" def setUp(self): super(TestL3App, self).setUp() diff --git a/dragonflow/tests/unit/test_l3_proactive_app.py b/dragonflow/tests/unit/test_l3_proactive_app.py index 8e5813122..e77cdd590 100644 --- a/dragonflow/tests/unit/test_l3_proactive_app.py +++ b/dragonflow/tests/unit/test_l3_proactive_app.py @@ -24,7 +24,7 @@ from dragonflow.tests.unit import test_app_base class TestL3ProactiveApp(test_app_base.DFAppTestBase, _test_l3.L3AppTestCaseMixin): - apps_list = "l3_proactive_app.L3ProactiveApp" + apps_list = "l3_proactive" def setUp(self): super(TestL3ProactiveApp, self).setUp() diff --git a/dragonflow/tests/unit/test_legacy_snat.py b/dragonflow/tests/unit/test_legacy_snat.py index 8882af01f..f99482f52 100644 --- a/dragonflow/tests/unit/test_legacy_snat.py +++ b/dragonflow/tests/unit/test_legacy_snat.py @@ -20,7 +20,7 @@ from dragonflow.tests.unit import test_app_base class TestLegacySNatApp(test_app_base.DFAppTestBase): - apps_list = "legacy_snat_app.LegacySNatApp" + apps_list = "legacy_snat" def setUp(self): super(TestLegacySNatApp, self).setUp() diff --git a/dragonflow/tests/unit/test_metadata_service_app.py b/dragonflow/tests/unit/test_metadata_service_app.py index f64529ca6..a5ac4309a 100644 --- a/dragonflow/tests/unit/test_metadata_service_app.py +++ b/dragonflow/tests/unit/test_metadata_service_app.py @@ -25,7 +25,7 @@ from dragonflow.tests.unit import test_app_base class TestMetadataServiceApp(test_app_base.DFAppTestBase): - apps_list = "metadata_service_app.MetadataServiceApp" + apps_list = "metadata_service" def setUp(self): super(TestMetadataServiceApp, self).setUp() diff --git a/dragonflow/tests/unit/test_migration_app.py b/dragonflow/tests/unit/test_migration_app.py index da100d338..e2a9b356f 100644 --- a/dragonflow/tests/unit/test_migration_app.py +++ b/dragonflow/tests/unit/test_migration_app.py @@ -18,7 +18,7 @@ from dragonflow.tests.unit import test_app_base class TestMigrationApp(test_app_base.DFAppTestBase): - apps_list = "migration_app.MigrationApp" + apps_list = "migration" def test_update_migration_flows(self): cfg.CONF.set_override('host', 'fake-local-host') diff --git a/dragonflow/tests/unit/test_provider_net_app.py b/dragonflow/tests/unit/test_provider_net_app.py index 151189d91..07515202e 100644 --- a/dragonflow/tests/unit/test_provider_net_app.py +++ b/dragonflow/tests/unit/test_provider_net_app.py @@ -23,7 +23,7 @@ make_fake_remote_port = test_app_base.make_fake_remote_port class TestProviderNetsApp(test_app_base.DFAppTestBase): - apps_list = "provider_networks_app.ProviderNetworksApp" + apps_list = "provider" def setUp(self): super(TestProviderNetsApp, self).setUp() diff --git a/dragonflow/tests/unit/test_sg_app.py b/dragonflow/tests/unit/test_sg_app.py index 1535bd2d8..bbcfaa314 100644 --- a/dragonflow/tests/unit/test_sg_app.py +++ b/dragonflow/tests/unit/test_sg_app.py @@ -27,7 +27,7 @@ COMMAND_DELETE = 2 class TestSGApp(test_app_base.DFAppTestBase): - apps_list = "sg_app.SGApp" + apps_list = "sg" def setUp(self): super(TestSGApp, self).setUp() diff --git a/dragonflow/tests/unit/test_topology.py b/dragonflow/tests/unit/test_topology.py index c6dad5e75..c7848549f 100644 --- a/dragonflow/tests/unit/test_topology.py +++ b/dragonflow/tests/unit/test_topology.py @@ -50,7 +50,7 @@ def nb_api_get_all_func(*instances): class TestTopology(test_app_base.DFAppTestBase): # This is to comply the current code, as the app_list can't be empty. # But we don't need any app in this test, acutally. - apps_list = "l2_app.L2App" + apps_list = "l2" def setUp(self): cfg.CONF.set_override('enable_selective_topology_distribution', diff --git a/dragonflow/tests/unit/test_trunk_app.py b/dragonflow/tests/unit/test_trunk_app.py index 441b66aa2..a27f9ebc4 100644 --- a/dragonflow/tests/unit/test_trunk_app.py +++ b/dragonflow/tests/unit/test_trunk_app.py @@ -41,7 +41,7 @@ class SettingMock(object): class TestTrunkApp(test_app_base.DFAppTestBase): - apps_list = "trunk_app.TrunkApp" + apps_list = "trunk" def setUp(self): super(TestTrunkApp, self).setUp() diff --git a/dragonflow/tests/unit/test_tunneling_app.py b/dragonflow/tests/unit/test_tunneling_app.py index 1d242fcf0..6928d435a 100644 --- a/dragonflow/tests/unit/test_tunneling_app.py +++ b/dragonflow/tests/unit/test_tunneling_app.py @@ -23,7 +23,7 @@ make_fake_remote_port = test_app_base.make_fake_remote_port class TestTunnelingApp(test_app_base.DFAppTestBase): - apps_list = "tunneling_app.TunnelingApp" + apps_list = "tunneling" def setUp(self): super(TestTunnelingApp, self).setUp() diff --git a/setup.cfg b/setup.cfg index d677b5b67..463268489 100644 --- a/setup.cfg +++ b/setup.cfg @@ -75,3 +75,22 @@ neutron.service_plugins = df-bgp = dragonflow.neutron.services.bgp.bgp_plugin:DFBgpPlugin oslo.config.opts = dragonflow.conf = dragonflow.conf.opts:list_opts +dragonflow.controller.apps = + active_port_detection = dragonflow.controller.active_port_detection_app:ActivePortDetectionApp + aging = dragonflow.controller.aging_app:Aging + chassis_snat = dragonflow.controller.chassis_snat_app:ChassisSNATApp + classifier = dragonflow.controller.classifier_app:ClassifierApp + dhcp = dragonflow.controller.dhcp_app:DHCPApp + dnat = dragonflow.controller.dnat_app:DNATApp + l2 = dragonflow.controller.l2_app:L2App + legacy_snat = dragonflow.controller.legacy_snat_app:LegacySNatApp + l3_proactive = dragonflow.controller.l3_proactive_app:L3ProactiveApp + l3_reactive = dragonflow.controller.l3_app:L3App + metadata_service = dragonflow.controller.metadata_service_app:MetadataServiceApp + migration = dragonflow.controller.migration_app:MigrationApp + portqos = dragonflow.controller.portqos_app:PortQosApp + portsec = dragonflow.controller.portsec_app:PortSecApp + provider = dragonflow.controller.provider_networks_app:ProviderNetworksApp + sg = dragonflow.controller.sg_app:SGApp + trunk = dragonflow.controller.trunk_app:TrunkApp + tunneling = dragonflow.controller.tunneling_app:TunnelingApp