Merge "Add router_factory to l3-agent and L3 extension API"

This commit is contained in:
Zuul 2019-04-27 06:37:15 +00:00 committed by Gerrit Code Review
commit 554b7cd228
11 changed files with 261 additions and 84 deletions

View File

@ -194,6 +194,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,
@ -223,6 +257,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()
@ -328,6 +364,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.
@ -375,7 +426,6 @@ class L3NATAgent(ha.AgentMixin,
raise Exception(msg)
def _create_router(self, router_id, router):
args = []
kwargs = {
'agent': self,
'router_id': router_id,
@ -385,9 +435,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
@ -398,22 +454,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):
@ -486,7 +532,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(

View File

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

View File

@ -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
@ -40,7 +42,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,
@ -51,16 +54,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
@ -75,8 +150,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()
@ -84,16 +157,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,
@ -107,33 +171,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']]
@ -158,9 +198,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, [])
@ -1174,13 +1211,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()))

View File

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

View File

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

View File

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

View File

@ -90,7 +90,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]

View File

@ -35,7 +35,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
@ -450,6 +452,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)

View File

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

View File

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

View File

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