From ec875b42b6e92300093c895668532966de9e1327 Mon Sep 17 00:00:00 2001 From: Yang Youseok Date: Fri, 23 Nov 2018 15:55:02 +0900 Subject: [PATCH] Add router_factory to l3-agent and L3 extension API Currently, most implementations override the L3NatAgent class itself for their own logic since there is no proper interface to extend RouterInfo class. This adds unnecessary complexity for developers who just want to extend router mechanism instead of whole RPC. Add a RouterFactory class that developer can registers RouterInfo class and delegate it for RouterInfo creation. Seperate functions and variables which currently used externally to abstract class from RouterInfo, so that extension can use the basic interface. Provide the router registration function to the l3 extension API so that extension can extend RouterInfo itself which correspond to each features (ha, distribtued, ha + distributed) Depends-On: https://review.openstack.org/#/c/620348/ Closes-Bug: #1804634 Partially-Implements: blueprint openflow-based-dvr Change-Id: I1eff726900a8e67596814ca9a5f392938f154d7b --- neutron/agent/l3/agent.py | 81 ++++++++--- neutron/agent/l3/l3_agent_extension_api.py | 9 +- neutron/agent/l3/router_info.py | 136 +++++++++++------- .../functional/agent/l3/test_dvr_router.py | 4 + .../unit/agent/l3/extensions/qos/test_fip.py | 2 +- .../l3/extensions/qos/test_gateway_ip.py | 2 +- .../l3/extensions/test_port_forwarding.py | 2 +- neutron/tests/unit/agent/l3/test_agent.py | 63 ++++++++ .../agent/l3/test_l3_agent_extension_api.py | 34 +++-- .../services/logapi/agent/l3/test_base.py | 2 +- ...ister-router-factory-46a86f845895f4f6.yaml | 10 ++ 11 files changed, 261 insertions(+), 84 deletions(-) create mode 100644 releasenotes/notes/l3-agent-extensions-register-router-factory-46a86f845895f4f6.yaml diff --git a/neutron/agent/l3/agent.py b/neutron/agent/l3/agent.py index d1ac187e264..698eb5029d0 100644 --- a/neutron/agent/l3/agent.py +++ b/neutron/agent/l3/agent.py @@ -195,6 +195,40 @@ class L3PluginApi(object): return cctxt.call(context, 'get_host_ha_router_count', host=self.host) +class RouterFactory(object): + + def __init__(self): + self._routers = {} + + def register(self, features, router_cls): + """Register router class which implements BaseRouterInfo + + Features which is a list of strings converted to frozenset internally + for key uniqueness. + + :param features: a list of strings of router's features + :param router_cls: a router class which implements BaseRouterInfo + """ + self._routers[frozenset(features)] = router_cls + + def create(self, features, **kwargs): + """Create router instance with registered router class + + :param features: a list of strings of router's features + :param kwargs: arguments for router class + :returns: a router instance which implements BaseRouterInfo + :raises: n_exc.RouterNotFoundInRouterFactory + """ + try: + router = self._routers[frozenset(features)] + return router(**kwargs) + except KeyError: + exc = l3_exc.RouterNotFoundInRouterFactory( + router_id=kwargs['router_id'], features=features) + LOG.exception(exc.msg) + raise exc + + @profiler.trace_cls("l3-agent") class L3NATAgent(ha.AgentMixin, dvr.AgentMixin, @@ -224,6 +258,8 @@ class L3NATAgent(ha.AgentMixin, else: self.conf = cfg.CONF self.router_info = {} + self.router_factory = RouterFactory() + self._register_router_cls(self.router_factory) self._check_config_params() @@ -329,6 +365,21 @@ class L3NATAgent(ha.AgentMixin, except Exception: LOG.exception('update_all_ha_network_port_statuses failed') + def _register_router_cls(self, factory): + factory.register([], legacy_router.LegacyRouter) + factory.register(['ha'], ha_router.HaRouter) + + if self.conf.agent_mode == lib_const.L3_AGENT_MODE_DVR_SNAT: + factory.register(['distributed'], + dvr_router.DvrEdgeRouter) + factory.register(['ha', 'distributed'], + dvr_edge_ha_router.DvrEdgeHaRouter) + else: + factory.register(['distributed'], + dvr_local_router.DvrLocalRouter) + factory.register(['ha', 'distributed'], + dvr_local_router.DvrLocalRouter) + def _check_config_params(self): """Check items in configuration files. @@ -376,7 +427,6 @@ class L3NATAgent(ha.AgentMixin, raise Exception(msg) def _create_router(self, router_id, router): - args = [] kwargs = { 'agent': self, 'router_id': router_id, @@ -386,9 +436,15 @@ class L3NATAgent(ha.AgentMixin, 'interface_driver': self.driver, } + features = [] if router.get('distributed'): + features.append('distributed') kwargs['host'] = self.host + if router.get('ha'): + features.append('ha') + kwargs['state_change_callback'] = self.enqueue_state_change + if router.get('distributed') and router.get('ha'): # Case 1: If the router contains information about the HA interface # and if the requesting agent is a DVR_SNAT agent then go ahead @@ -399,22 +455,12 @@ class L3NATAgent(ha.AgentMixin, # that needs to provision a router namespace because of a DVR # service port (e.g. DHCP). So go ahead and create a regular DVR # edge router. - if (self.conf.agent_mode == lib_const.L3_AGENT_MODE_DVR_SNAT and - router.get(lib_const.HA_INTERFACE_KEY) is not None): - kwargs['state_change_callback'] = self.enqueue_state_change - return dvr_edge_ha_router.DvrEdgeHaRouter(*args, **kwargs) + if (not router.get(lib_const.HA_INTERFACE_KEY) or + self.conf.agent_mode != lib_const.L3_AGENT_MODE_DVR_SNAT): + features.remove('ha') + kwargs.pop('state_change_callback') - if router.get('distributed'): - if self.conf.agent_mode == lib_const.L3_AGENT_MODE_DVR_SNAT: - return dvr_router.DvrEdgeRouter(*args, **kwargs) - else: - return dvr_local_router.DvrLocalRouter(*args, **kwargs) - - if router.get('ha'): - kwargs['state_change_callback'] = self.enqueue_state_change - return ha_router.HaRouter(*args, **kwargs) - - return legacy_router.LegacyRouter(*args, **kwargs) + return self.router_factory.create(features, **kwargs) @lockutils.synchronized('resize_greenpool') def _resize_process_pool(self): @@ -487,7 +533,8 @@ class L3NATAgent(ha.AgentMixin, def init_extension_manager(self, connection): l3_ext_manager.register_opts(self.conf) - self.agent_api = l3_ext_api.L3AgentExtensionAPI(self.router_info) + self.agent_api = l3_ext_api.L3AgentExtensionAPI(self.router_info, + self.router_factory) self.l3_ext_manager = ( l3_ext_manager.L3AgentExtensionsManager(self.conf)) self.l3_ext_manager.initialize( diff --git a/neutron/agent/l3/l3_agent_extension_api.py b/neutron/agent/l3/l3_agent_extension_api.py index 14b45032dac..3a96a37aabc 100644 --- a/neutron/agent/l3/l3_agent_extension_api.py +++ b/neutron/agent/l3/l3_agent_extension_api.py @@ -26,8 +26,9 @@ class L3AgentExtensionAPI(object): agent's RouterInfo object. ''' - def __init__(self, router_info): + def __init__(self, router_info, router_factory): self._router_info = router_info + self._router_factory = router_factory def _local_namespaces(self): local_ns_list = ip_lib.list_network_namespaces() @@ -68,3 +69,9 @@ class L3AgentExtensionAPI(object): def get_router_info(self, router_id): """Return RouterInfo for the given router id.""" return self._router_info.get(router_id) + + def register_router(self, features, router_cls): + """Register router class with the given features. This is for the + plugin to ovrride with their own ``router_info`` class. + """ + self._router_factory.register(features, router_cls) diff --git a/neutron/agent/l3/router_info.py b/neutron/agent/l3/router_info.py index 59a28ed9b06..79d75f1c446 100644 --- a/neutron/agent/l3/router_info.py +++ b/neutron/agent/l3/router_info.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +import abc import collections import netaddr @@ -19,6 +20,7 @@ from neutron_lib import constants as lib_constants from neutron_lib.exceptions import l3 as l3_exc from neutron_lib.utils import helpers from oslo_log import log as logging +import six from neutron._i18n import _ from neutron.agent.l3 import namespaces @@ -41,7 +43,8 @@ ADDRESS_SCOPE_MARK_ID_MAX = 2048 DEFAULT_ADDRESS_SCOPE = "noscope" -class RouterInfo(object): +@six.add_metaclass(abc.ABCMeta) +class BaseRouterInfo(object): def __init__(self, agent, @@ -52,16 +55,88 @@ class RouterInfo(object): use_ipv6=False): self.agent = agent self.router_id = router_id - self.agent_conf = agent_conf - self.ex_gw_port = None + # Invoke the setter for establishing initial SNAT action self._snat_enabled = None - self.fip_map = {} + self.router = router + self.agent_conf = agent_conf + self.driver = interface_driver + self.use_ipv6 = use_ipv6 + self.internal_ports = [] + self.ns_name = None + self.process_monitor = None + + def initialize(self, process_monitor): + """Initialize the router on the system. + + This differs from __init__ in that this method actually affects the + system creating namespaces, starting processes, etc. The other merely + initializes the python object. This separates in-memory object + initialization from methods that actually go do stuff to the system. + + :param process_monitor: The agent's process monitor instance. + """ + self.process_monitor = process_monitor + + @property + def router(self): + return self._router + + @router.setter + def router(self, value): + self._router = value + if not self._router: + return + # enable_snat by default if it wasn't specified by plugin + self._snat_enabled = self._router.get('enable_snat', True) + + @abc.abstractmethod + def delete(self, agent): + pass + + @abc.abstractmethod + def process(self, agent): + """Process updates to this router + + This method is the point where the agent requests that updates be + applied to this router. + + :param agent: Passes the agent in order to send RPC messages. + """ + pass + + def get_ex_gw_port(self): + return self.router.get('gw_port') + + def get_gw_ns_name(self): + return self.ns_name + + def get_internal_device_name(self, port_id): + return (INTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN] + + def get_external_device_name(self, port_id): + return (EXTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN] + + def get_external_device_interface_name(self, ex_gw_port): + return self.get_external_device_name(ex_gw_port['id']) + + +class RouterInfo(BaseRouterInfo): + + def __init__(self, + agent, + router_id, + router, + agent_conf, + interface_driver, + use_ipv6=False): + super(RouterInfo, self).__init__(agent, router_id, router, agent_conf, + interface_driver, use_ipv6) + + self.ex_gw_port = None + self.fip_map = {} self.pd_subnets = {} self.floating_ips = set() - # Invoke the setter for establishing initial SNAT action - self.router = router - self.use_ipv6 = use_ipv6 ns = self.create_router_namespace_object( router_id, agent_conf, interface_driver, use_ipv6) self.router_namespace = ns @@ -76,8 +151,6 @@ class RouterInfo(object): self.initialize_address_scope_iptables() self.initialize_metadata_iptables() self.routes = [] - self.driver = interface_driver - self.process_monitor = None # radvd is a neutron.agent.linux.ra.DaemonMonitor self.radvd = None self.centralized_port_forwarding_fip_set = set() @@ -85,16 +158,7 @@ class RouterInfo(object): self.qos_gateway_ips = set() def initialize(self, process_monitor): - """Initialize the router on the system. - - This differs from __init__ in that this method actually affects the - system creating namespaces, starting processes, etc. The other merely - initializes the python object. This separates in-memory object - initialization from methods that actually go do stuff to the system. - - :param process_monitor: The agent's process monitor instance. - """ - self.process_monitor = process_monitor + super(RouterInfo, self).initialize(process_monitor) self.radvd = ra.DaemonMonitor(self.router_id, self.ns_name, process_monitor, @@ -108,33 +172,9 @@ class RouterInfo(object): return namespaces.RouterNamespace( router_id, agent_conf, iface_driver, use_ipv6) - @property - def router(self): - return self._router - - @router.setter - def router(self, value): - self._router = value - if not self._router: - return - # enable_snat by default if it wasn't specified by plugin - self._snat_enabled = self._router.get('enable_snat', True) - def is_router_master(self): return True - def get_internal_device_name(self, port_id): - return (INTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN] - - def get_external_device_name(self, port_id): - return (EXTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN] - - def get_external_device_interface_name(self, ex_gw_port): - return self.get_external_device_name(ex_gw_port['id']) - - def get_gw_ns_name(self): - return self.ns_name - def _update_routing_table(self, operation, route, namespace): cmd = ['ip', 'route', operation, 'to', route['destination'], 'via', route['nexthop']] @@ -159,9 +199,6 @@ class RouterInfo(object): LOG.debug("Removed route entry is '%s'", route) self.update_routing_table('delete', route) - def get_ex_gw_port(self): - return self.router.get('gw_port') - def get_floating_ips(self): """Filter Floating IPs to be hosted on this agent.""" return self.router.get(lib_constants.FLOATINGIP_KEY, []) @@ -1170,13 +1207,6 @@ class RouterInfo(object): @common_utils.exception_logger() def process(self): - """Process updates to this router - - This method is the point where the agent requests that updates be - applied to this router. - - :param agent: Passes the agent in order to send RPC messages. - """ LOG.debug("Process updates, router %s", self.router['id']) self.centralized_port_forwarding_fip_set = set(self.router.get( 'port_forwardings_fip_set', set())) diff --git a/neutron/tests/functional/agent/l3/test_dvr_router.py b/neutron/tests/functional/agent/l3/test_dvr_router.py index 6a35e065f86..55825947966 100644 --- a/neutron/tests/functional/agent/l3/test_dvr_router.py +++ b/neutron/tests/functional/agent/l3/test_dvr_router.py @@ -579,6 +579,10 @@ class TestDvrRouter(framework.L3AgentTestFramework): self._add_fip_agent_gw_port_info_to_router(router, external_gw_port) + # Router creation is delegated to router_factory. We have to + # re-register here so that factory can find override agent mode + # normally. + self.agent._register_router_cls(self.agent.router_factory) return router def _get_fip_agent_gw_port_for_router( diff --git a/neutron/tests/unit/agent/l3/extensions/qos/test_fip.py b/neutron/tests/unit/agent/l3/extensions/qos/test_fip.py index 14b7867229d..df107b44fef 100644 --- a/neutron/tests/unit/agent/l3/extensions/qos/test_fip.py +++ b/neutron/tests/unit/agent/l3/extensions/qos/test_fip.py @@ -128,7 +128,7 @@ class QosExtensionBaseTestCase(test_agent.BasicRouterOperationsFramework): 'L3AgentExtensionAPI.get_router_info').start() self.get_router_info.side_effect = _mock_get_router_info - self.agent_api = l3_ext_api.L3AgentExtensionAPI(None) + self.agent_api = l3_ext_api.L3AgentExtensionAPI(None, None) self.fip_qos_ext.consume_api(self.agent_api) diff --git a/neutron/tests/unit/agent/l3/extensions/qos/test_gateway_ip.py b/neutron/tests/unit/agent/l3/extensions/qos/test_gateway_ip.py index 33872851338..e53f222a27e 100644 --- a/neutron/tests/unit/agent/l3/extensions/qos/test_gateway_ip.py +++ b/neutron/tests/unit/agent/l3/extensions/qos/test_gateway_ip.py @@ -129,7 +129,7 @@ class QosExtensionBaseTestCase(test_agent.BasicRouterOperationsFramework): 'L3AgentExtensionAPI.get_router_info').start() self.get_router_info.side_effect = _mock_get_router_info - self.agent_api = l3_ext_api.L3AgentExtensionAPI(None) + self.agent_api = l3_ext_api.L3AgentExtensionAPI(None, None) self.gw_ip_qos_ext.consume_api(self.agent_api) diff --git a/neutron/tests/unit/agent/l3/extensions/test_port_forwarding.py b/neutron/tests/unit/agent/l3/extensions/test_port_forwarding.py index 4840b85261e..4c7a9e201e3 100644 --- a/neutron/tests/unit/agent/l3/extensions/test_port_forwarding.py +++ b/neutron/tests/unit/agent/l3/extensions/test_port_forwarding.py @@ -91,7 +91,7 @@ class PortForwardingExtensionBaseTestCase( 'L3AgentExtensionAPI.get_router_info').start() self.get_router_info.return_value = self.router_info - self.agent_api = l3_ext_api.L3AgentExtensionAPI(None) + self.agent_api = l3_ext_api.L3AgentExtensionAPI(None, None) self.fip_pf_ext.consume_api(self.agent_api) self.port_forwardings = [self.portforwarding1] diff --git a/neutron/tests/unit/agent/l3/test_agent.py b/neutron/tests/unit/agent/l3/test_agent.py index 4e23845e647..95483072450 100644 --- a/neutron/tests/unit/agent/l3/test_agent.py +++ b/neutron/tests/unit/agent/l3/test_agent.py @@ -34,7 +34,9 @@ from testtools import matchers from neutron.agent.common import resource_processing_queue from neutron.agent.l3 import agent as l3_agent +from neutron.agent.l3 import dvr_edge_ha_router from neutron.agent.l3 import dvr_edge_router as dvr_router +from neutron.agent.l3 import dvr_local_router from neutron.agent.l3 import dvr_router_base from neutron.agent.l3 import dvr_snat_ns from neutron.agent.l3 import ha_router @@ -428,6 +430,67 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): self.assertEqual(len(stale_router_ids), destroy_proxy.call_count) destroy_proxy.assert_has_calls(expected_calls, any_order=True) + def test__create_router_legacy_agent(self): + router = {'distributed': False, 'ha': False} + + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + router_info = agent._create_router(_uuid(), router) + + self.assertEqual(legacy_router.LegacyRouter, type(router_info)) + + def test__create_router_ha_agent(self): + router = {'distributed': False, 'ha': True} + + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + router_info = agent._create_router(_uuid(), router) + + self.assertEqual(ha_router.HaRouter, type(router_info)) + + def test__create_router_dvr_agent(self): + router = {'distributed': True, 'ha': False} + + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + router_info = agent._create_router(_uuid(), router) + + self.assertEqual(dvr_local_router.DvrLocalRouter, type(router_info)) + + def test__create_router_dvr_agent_with_dvr_snat_mode(self): + router = {'distributed': True, 'ha': False} + + self.conf.set_override('agent_mode', + lib_constants.L3_AGENT_MODE_DVR_SNAT) + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + router_info = agent._create_router(_uuid(), router) + + self.assertEqual(dvr_router.DvrEdgeRouter, type(router_info)) + + def test__create_router_dvr_ha_agent(self): + router = {'distributed': True, 'ha': True} + + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + router_info = agent._create_router(_uuid(), router) + + self.assertEqual(dvr_local_router.DvrLocalRouter, type(router_info)) + + def test__create_router_dvr_ha_agent_with_dvr_snat_mode(self): + router = {'distributed': True, 'ha': True, + lib_constants.HA_INTERFACE_KEY: None} + + self.conf.set_override('agent_mode', + lib_constants.L3_AGENT_MODE_DVR_SNAT) + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + router_info = agent._create_router(_uuid(), router) + + self.assertEqual(dvr_router.DvrEdgeRouter, type(router_info)) + + router = {'distributed': True, 'ha': True, + lib_constants.HA_INTERFACE_KEY: True} + + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + router_info = agent._create_router(_uuid(), router) + + self.assertEqual(dvr_edge_ha_router.DvrEdgeHaRouter, type(router_info)) + def test_router_info_create(self): id = _uuid() agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) diff --git a/neutron/tests/unit/agent/l3/test_l3_agent_extension_api.py b/neutron/tests/unit/agent/l3/test_l3_agent_extension_api.py index 4b943d936c2..58c0486e7ec 100644 --- a/neutron/tests/unit/agent/l3/test_l3_agent_extension_api.py +++ b/neutron/tests/unit/agent/l3/test_l3_agent_extension_api.py @@ -18,8 +18,9 @@ import mock from oslo_utils import uuidutils +from neutron.agent.l3 import agent from neutron.agent.l3 import l3_agent_extension_api as l3_agent_api -from neutron.agent.l3 import router_info +from neutron.agent.l3 import router_info as l3router from neutron.agent.linux import ip_lib from neutron.conf.agent import common as config from neutron.conf.agent.l3 import config as l3_config @@ -38,7 +39,7 @@ class TestL3AgentExtensionApi(base.BaseTestCase): 'agent_conf': self.conf, 'interface_driver': mock.ANY, 'use_ipv6': mock.ANY} - ri = router_info.RouterInfo(mock.Mock(), self.router_id, **ri_kwargs) + ri = l3router.RouterInfo(mock.Mock(), self.router_id, **ri_kwargs) ri.internal_ports = ports return {ri.router_id: ri}, ri @@ -51,7 +52,7 @@ class TestL3AgentExtensionApi(base.BaseTestCase): 'list_network_namespaces') as mock_list_netns: mock_list_netns.return_value = [] - api_object = l3_agent_api.L3AgentExtensionAPI(router_info) + api_object = l3_agent_api.L3AgentExtensionAPI(router_info, None) router = api_object.get_router_hosting_port(port_ids[0]) mock_list_netns.assert_called_once_with() @@ -65,7 +66,7 @@ class TestL3AgentExtensionApi(base.BaseTestCase): with mock.patch.object(ip_lib, 'list_network_namespaces') as mock_list_netns: mock_list_netns.return_value = [ri.ns_name] - api_object = l3_agent_api.L3AgentExtensionAPI(router_info) + api_object = l3_agent_api.L3AgentExtensionAPI(router_info, None) router = api_object.get_router_hosting_port(port_ids[0]) self.assertEqual(ri, router) @@ -75,7 +76,7 @@ class TestL3AgentExtensionApi(base.BaseTestCase): with mock.patch.object(ip_lib, 'list_network_namespaces') as mock_list_netns: mock_list_netns.return_value = [ri.ns_name] - api_object = l3_agent_api.L3AgentExtensionAPI(router_info) + api_object = l3_agent_api.L3AgentExtensionAPI(router_info, None) routers = api_object.get_routers_in_project(self.project_id) self.assertEqual([ri], routers) @@ -85,7 +86,7 @@ class TestL3AgentExtensionApi(base.BaseTestCase): with mock.patch.object(ip_lib, 'list_network_namespaces') as mock_list_netns: mock_list_netns.return_value = [ri.ns_name] - api_object = l3_agent_api.L3AgentExtensionAPI(router_info) + api_object = l3_agent_api.L3AgentExtensionAPI(router_info, None) router_in_ns = api_object.is_router_in_namespace(ri.router_id) self.assertTrue(router_in_ns) @@ -95,17 +96,32 @@ class TestL3AgentExtensionApi(base.BaseTestCase): with mock.patch.object(ip_lib, 'list_network_namespaces') as mock_list_netns: mock_list_netns.return_value = [uuidutils.generate_uuid()] - api_object = l3_agent_api.L3AgentExtensionAPI(router_info) + api_object = l3_agent_api.L3AgentExtensionAPI(router_info, None) router_in_ns = api_object.is_router_in_namespace(ri.router_id) self.assertFalse(router_in_ns) def test_get_router_info(self): router_info, ri = self._prepare_router_data() - api_object = l3_agent_api.L3AgentExtensionAPI(router_info) + api_object = l3_agent_api.L3AgentExtensionAPI(router_info, None) self.assertEqual(ri, api_object.get_router_info(self.router_id)) def test_get_router_info_nonexistent(self): router_info, ri = self._prepare_router_data() - api_object = l3_agent_api.L3AgentExtensionAPI(router_info) + api_object = l3_agent_api.L3AgentExtensionAPI(router_info, None) self.assertIsNone( api_object.get_router_info(uuidutils.generate_uuid())) + + def test_register_router(self): + router_info, ri = self._prepare_router_data() + router_info_cls = l3router.BaseRouterInfo + router_factory = agent.RouterFactory() + api_object = l3_agent_api.L3AgentExtensionAPI(router_info, + router_factory) + self.assertIsNone( + api_object.register_router([], router_info_cls)) + self.assertIsNone( + api_object.register_router(['ha'], router_info_cls)) + self.assertIsNone( + api_object.register_router(['distributed'], router_info_cls)) + self.assertIsNone( + api_object.register_router(['ha', 'distributed'], router_info_cls)) diff --git a/neutron/tests/unit/services/logapi/agent/l3/test_base.py b/neutron/tests/unit/services/logapi/agent/l3/test_base.py index 494b0127927..70e62930aa2 100644 --- a/neutron/tests/unit/services/logapi/agent/l3/test_base.py +++ b/neutron/tests/unit/services/logapi/agent/l3/test_base.py @@ -75,7 +75,7 @@ class L3LoggingExtBaseTestCase(test_agent.BasicRouterOperationsFramework): 'neutron.agent.l3.l3_agent_extension_api.' 'L3AgentExtensionAPI.get_router_info').start() self.get_router_info.side_effect = _mock_get_router_info - self.agent_api = l3_ext_api.L3AgentExtensionAPI(None) + self.agent_api = l3_ext_api.L3AgentExtensionAPI(None, None) mock.patch( 'neutron.manager.NeutronManager.load_class_for_provider').start() diff --git a/releasenotes/notes/l3-agent-extensions-register-router-factory-46a86f845895f4f6.yaml b/releasenotes/notes/l3-agent-extensions-register-router-factory-46a86f845895f4f6.yaml new file mode 100644 index 00000000000..91ed977820d --- /dev/null +++ b/releasenotes/notes/l3-agent-extensions-register-router-factory-46a86f845895f4f6.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + A new parameter ``router_factory`` has been added to + ``neutron.agent.l3.L3AgentExtensionAPI``. Developers can register + ``neutron.agent.l3.agent.RouterInfo`` class and delegate it for + ``RouterInfo`` creation. + + Extensions can extend ``RouterInfo`` itself which correspond to each + features (ha, distribtued, ha + distributed).