From 6718fd85603c77f7f36a1b74954696af47f13c94 Mon Sep 17 00:00:00 2001 From: Margaret Frances Date: Mon, 15 Aug 2016 13:26:30 -0400 Subject: [PATCH] FWaaS v2 utilize L3 Agent Extension framework This updates the FWaaS v2 L3 code to move away from an inheritance-based model and use the new L3 agent extension framework. This change rolls back [1] which is the inheritance-based model. [1] https://review.openstack.org/315826 Partial-Implements: blueprint fwaas-api-2.0 Co-Authored-By: Nate Johnston Co-Authored-By: Chandan Dutta Chowdhury Depends-On: I85f89accbeefd820130335674fd56cb54f1449de Change-Id: Ib29b96e73d09530cbf627a98180fb1a591e42e3f --- devstack/plugin.sh | 1 + neutron_fwaas/cmd/__init__.py | 0 neutron_fwaas/cmd/eventlet/agents/__init__.py | 0 neutron_fwaas/cmd/eventlet/agents/fw.py | 62 --------- neutron_fwaas/common/fwaas_constants.py | 1 + .../__init__.py => common/resources.py} | 6 +- neutron_fwaas/extensions/firewall_v2.py | 4 + .../agents/l3reference/firewall_l3_agent.py | 115 ++++++++++------ .../l3reference/firewall_l3_agent_v2.py | 128 ++++++++++++------ .../services/firewall/drivers/fwaas_base.py | 30 +++- .../drivers/linux/iptables_fwaas_v2.py | 39 +++--- .../services/firewall/fwaas_plugin.py | 2 +- .../services/firewall/fwaas_plugin_v2.py | 8 +- .../l3reference/test_firewall_l3_agent.py | 38 ++++-- .../l3reference/test_firewall_l3_agent_v2.py | 48 +++++-- .../agents/test_firewall_agent_api.py | 6 +- .../drivers/linux/test_iptables_fwaas_v2.py | 13 +- setup.cfg | 6 +- 18 files changed, 301 insertions(+), 206 deletions(-) delete mode 100644 neutron_fwaas/cmd/__init__.py delete mode 100644 neutron_fwaas/cmd/eventlet/agents/__init__.py delete mode 100644 neutron_fwaas/cmd/eventlet/agents/fw.py rename neutron_fwaas/{cmd/eventlet/__init__.py => common/resources.py} (74%) diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 4ded72571..5cab7800e 100755 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -35,6 +35,7 @@ function install_fwaas() { function configure_fwaas() { neutron_fwaas_configure_driver + iniset_multiline $Q_L3_CONF_FILE AGENT extensions fwaas } function init_fwaas() { diff --git a/neutron_fwaas/cmd/__init__.py b/neutron_fwaas/cmd/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/neutron_fwaas/cmd/eventlet/agents/__init__.py b/neutron_fwaas/cmd/eventlet/agents/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/neutron_fwaas/cmd/eventlet/agents/fw.py b/neutron_fwaas/cmd/eventlet/agents/fw.py deleted file mode 100644 index e110858ab..000000000 --- a/neutron_fwaas/cmd/eventlet/agents/fw.py +++ /dev/null @@ -1,62 +0,0 @@ -# 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 sys - -from oslo_config import cfg -from oslo_service import service - -from neutron.agent.common import config -from neutron.agent.l3 import ha -from neutron.agent.linux import external_process -from neutron.agent.linux import interface -from neutron.agent.linux import pd -from neutron.agent.linux import ra -from neutron.agent.metadata import config as metadata_config -from neutron.common import config as common_config -from neutron.common import topics -from neutron.conf.agent.l3 import config as l3_config -from neutron import service as neutron_service -from neutron_fwaas.services.firewall.agents import firewall_agent_api as fwa - - -FWAAS_AGENTS = {fwa.FWAAS_V1: ('neutron_fwaas.services.firewall.agents.' - 'l3reference.firewall_l3_agent.L3WithFWaaS'), - fwa.FWAAS_V2: ('neutron_fwaas.services.firewall.agents.' - 'l3reference.firewall_l3_agent_v2.L3WithFWaaS')} - - -def register_opts(conf): - conf.register_opts(l3_config.OPTS) - conf.register_opts(metadata_config.DRIVER_OPTS) - conf.register_opts(metadata_config.SHARED_OPTS) - conf.register_opts(ha.OPTS) - config.register_interface_driver_opts_helper(conf) - config.register_agent_state_opts_helper(conf) - conf.register_opts(interface.OPTS) - conf.register_opts(external_process.OPTS) - conf.register_opts(pd.OPTS) - conf.register_opts(ra.OPTS) - config.register_availability_zone_opts_helper(conf) - - -def main(): - register_opts(cfg.CONF) - common_config.init(sys.argv[1:]) - config.setup_logging() - manager = FWAAS_AGENTS[cfg.CONF.fwaas.agent_version] - server = neutron_service.Service.create( - binary='neutron-l3-agent', - topic=topics.L3_AGENT, - report_interval=cfg.CONF.AGENT.report_interval, - manager=manager) - service.launch(cfg.CONF, server).wait() diff --git a/neutron_fwaas/common/fwaas_constants.py b/neutron_fwaas/common/fwaas_constants.py index 8a2144bfe..07626ee2d 100644 --- a/neutron_fwaas/common/fwaas_constants.py +++ b/neutron_fwaas/common/fwaas_constants.py @@ -16,4 +16,5 @@ # Constants for "topics" FIREWALL_PLUGIN = 'q-firewall-plugin' L3_AGENT = 'l3_agent' +FW_AGENT = 'firewall_agent' FIREWALL_RULE_LIST = 'firewall_rule_list' diff --git a/neutron_fwaas/cmd/eventlet/__init__.py b/neutron_fwaas/common/resources.py similarity index 74% rename from neutron_fwaas/cmd/eventlet/__init__.py rename to neutron_fwaas/common/resources.py index 01f9f6933..7f6fee431 100644 --- a/neutron_fwaas/cmd/eventlet/__init__.py +++ b/neutron_fwaas/common/resources.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. -from neutron.common import eventlet_utils +from neutron_fwaas.db.firewall.v2 import firewall_db_v2 -eventlet_utils.monkey_patch() +FIREWALL_GROUP = firewall_db_v2.FirewallGroup +FIREWALL_POLICY = firewall_db_v2.FirewallPolicy +FIREWALL_RULE = firewall_db_v2.FirewallRuleV2 diff --git a/neutron_fwaas/extensions/firewall_v2.py b/neutron_fwaas/extensions/firewall_v2.py index 379d41702..1d99a37b3 100644 --- a/neutron_fwaas/extensions/firewall_v2.py +++ b/neutron_fwaas/extensions/firewall_v2.py @@ -46,6 +46,10 @@ class FirewallGroupInPendingState(nexception.Conflict): "%(firewall_id)s is in %(pending_state)s.") +class FirewallGroupPortInvalid(nexception.Conflict): + message = _("Firewall Group Port %(port_id)s is invalid") + + class FirewallGroupPortInvalidProject(nexception.Conflict): message = _("Firewall Group %(port_id)s in invalid Project") diff --git a/neutron_fwaas/services/firewall/agents/l3reference/firewall_l3_agent.py b/neutron_fwaas/services/firewall/agents/l3reference/firewall_l3_agent.py index 8befd153b..6ca15bb51 100644 --- a/neutron_fwaas/services/firewall/agents/l3reference/firewall_l3_agent.py +++ b/neutron_fwaas/services/firewall/agents/l3reference/firewall_l3_agent.py @@ -13,8 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. -from neutron.agent.l3 import agent -from neutron.agent.linux import ip_lib +from neutron.agent.l3 import l3_agent_extension +from neutron.common import rpc as n_rpc from neutron import context from neutron.plugins.common import constants as n_const from oslo_config import cfg @@ -23,12 +23,16 @@ from oslo_log import log as logging from neutron_fwaas._i18n import _, _LE from neutron_fwaas.common import fwaas_constants as f_const +from neutron_fwaas.common import resources as f_resources from neutron_fwaas.extensions import firewall as fw_ext from neutron_fwaas.services.firewall.agents import firewall_agent_api as api from neutron_fwaas.services.firewall.agents import firewall_service LOG = logging.getLogger(__name__) +#TODO(njohnston): There needs to be some extrapolation of the common code +# between this module and firewall_l3_agent_v2.py. + class FWaaSL3PluginApi(api.FWaaSPluginApiMixin): """Agent side of the FWaaS agent to FWaaS Plugin RPC API.""" @@ -49,14 +53,39 @@ class FWaaSL3PluginApi(api.FWaaSPluginApiMixin): 'get_tenants_with_firewalls', host=self.host) -class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): +class FWaaSL3AgentExtension(l3_agent_extension.L3AgentCoreResourceExtension): """FWaaS Agent support to be used by Neutron L3 agent.""" + SUPPORTED_RESOURCE_TYPES = [f_resources.FIREWALL_GROUP, + f_resources.FIREWALL_POLICY, + f_resources.FIREWALL_RULE] + + def initialize(self, connection, driver_type): + self._register_rpc_consumers(connection) + + def consume_api(self, agent_api): + LOG.debug("FWaaS consume_api call occurred with %s" % agent_api) + self.agent_api = agent_api + + def _register_rpc_consumers(self, connection): + #TODO(njohnston): Add RPC consumer connection loading here. + pass + + def start_rpc_listeners(self, conf): + self.endpoints = [self] + + self.conn = n_rpc.create_connection() + self.conn.create_consumer( + f_const.FW_AGENT, self.endpoints, fanout=False) + return self.conn.consume_in_threads() + def __init__(self, host, conf): LOG.debug("Initializing firewall agent") + self.agent_api = None self.neutron_service_plugins = None self.conf = conf self.fwaas_enabled = cfg.CONF.fwaas.enabled + self.start_rpc_listeners(conf) # None means l3-agent has no information on the server # configuration due to the lack of RPC support. @@ -77,8 +106,7 @@ class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): self.services_sync_needed = False # setup RPC to msg fwaas plugin self.fwplugin_rpc = FWaaSL3PluginApi(f_const.FIREWALL_PLUGIN, - conf.host) - super(FWaaSL3AgentRpcCallback, self).__init__(host=conf.host) + host) def _has_router_insertion_fields(self, fw): return 'add-router-ids' in fw @@ -90,36 +118,24 @@ class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): return (fw['del-router-ids'] if to_delete else fw['add-router-ids']) else: - # we are in a upgrade and msg from older version of plugin - try: - routers = self.plugin_rpc.get_routers(context) - except Exception: - LOG.exception( - _LE("FWaaS RPC failure in _get_router_ids_for_fw " - "for firewall: %(fwid)s"), - {'fwid': fw['id']}) - self.services_sync_needed = True - return [ - router['id'] - for router in routers - if router['tenant_id'] == fw['tenant_id']] + return [router['id'] for router in + self.agent_api.get_routers_in_project(fw['tenant_id'])] + + def _get_routers_in_project(self, project_id): + if self.agent_api is None: + LOG.exception(_LE("FWaaS RPC call failed; L3 agent_api failure")) + router_info = self.agent_api._router_info + if project_id: + return [ri for ri in router_info.values() + if ri.router['tenant_id'] == project_id] + else: + return [] def _get_router_info_list_for_tenant(self, router_ids, tenant_id): """Returns the list of router info objects on which to apply the fw.""" - root_ip = ip_lib.IPWrapper() - local_ns_list = root_ip.get_namespaces() - - router_info_list = [] - # Pick up namespaces for Tenant Routers - for rid in router_ids: - # for routers without an interface - get_routers returns - # the router - but this is not yet populated in router_info - if rid not in self.router_info: - continue - router_ns = self.router_info[rid].ns_name - if router_ns in local_ns_list: - router_info_list.append(self.router_info[rid]) - return router_info_list + return [ri for ri in self._get_routers_in_project(tenant_id) + if ri.router_id in router_ids and + self.agent_api.is_router_in_namespace(ri.router_id)] def _invoke_driver_for_sync_from_plugin(self, ctx, router_info_list, fw): """Invoke the delete driver method for status of PENDING_DELETE and @@ -165,17 +181,17 @@ class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): fw['id'], status) - def _process_router_add(self, ri): + def _process_router_add(self, router): """On router add, get fw with rules from plugin and update driver.""" - LOG.debug("Process router add, router_id: '%s'", ri.router['id']) - router_ids = ri.router['id'] + LOG.debug("Process router add, router_id: '%s'", router['id']) + router_ids = router['id'] router_info_list = self._get_router_info_list_for_tenant( [router_ids], - ri.router['tenant_id']) + router['tenant_id']) if router_info_list: # Get the firewall with rules # for the tenant the router is on. - ctx = context.Context('', ri.router['tenant_id']) + ctx = context.Context('', router['tenant_id']) fw_list = self.fwplugin_rpc.get_firewalls_for_tenant(ctx) for fw in fw_list: if self._has_router_insertion_fields(fw): @@ -190,7 +206,7 @@ class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): # router can be present only on one fw return - def process_router_add(self, ri): + def add_router(self, context, new_router): """On router add, get fw with rules from plugin and update driver. Handles agent restart, when a router is added, query the plugin to @@ -201,15 +217,26 @@ class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): if not self.fwaas_enabled: return try: - # TODO(sridar): as per discussion with pc_m, we may want to hook - # this up to the l3 agent observer notification - self._process_router_add(ri) + self._process_router_add(new_router) except Exception: LOG.exception( _LE("FWaaS RPC info call failed for '%s'."), - ri.router['id']) + new_router['id']) self.services_sync_needed = True + def update_router(self, context, updated_router): + """The update_router method is just a synonym for add_router""" + self.add_router(context, updated_router) + + def delete_router(self, context, new_router): + """Handles router deletion. There is basically nothing to do for this + in the context of FWaaS with an IPTables driver; the namespace will + already have been deleted, taking the IPTables rules with it. + """ + #TODO(njohnston): When another firewall driver is implemented, look at + # expanding this out so that the driver can handle deletion calls. + pass + def process_services_sync(self, ctx): if not self.services_sync_needed: return @@ -403,9 +430,9 @@ class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): self.services_sync_needed = True -class L3WithFWaaS(FWaaSL3AgentRpcCallback, agent.L3NATAgentWithStateReport): +class L3WithFWaaS(FWaaSL3AgentExtension): - def __init__(self, host, conf=None): + def __init__(self, conf=None): if conf: self.conf = conf else: diff --git a/neutron_fwaas/services/firewall/agents/l3reference/firewall_l3_agent_v2.py b/neutron_fwaas/services/firewall/agents/l3reference/firewall_l3_agent_v2.py index 6345c0eb6..97407275c 100644 --- a/neutron_fwaas/services/firewall/agents/l3reference/firewall_l3_agent_v2.py +++ b/neutron_fwaas/services/firewall/agents/l3reference/firewall_l3_agent_v2.py @@ -13,8 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. -from neutron.agent.l3 import agent +from neutron.agent.l3 import l3_agent_extension from neutron.agent.linux import ip_lib +from neutron.common import rpc as n_rpc from neutron import context from neutron.plugins.common import constants as n_const from neutron_fwaas.common import fwaas_constants as f_const @@ -23,6 +24,7 @@ from oslo_log import helpers as log_helpers from oslo_log import log as logging from neutron_fwaas._i18n import _, _LE +from neutron_fwaas.common import resources as f_resources from neutron_fwaas.extensions import firewall as fw_ext from neutron_fwaas.services.firewall.agents import firewall_agent_api as api from neutron_fwaas.services.firewall.agents import firewall_service @@ -67,15 +69,40 @@ class FWaaSL3PluginApi(api.FWaaSPluginApiMixin): fwg_id=fwg_id, status=status, host=self.host) -class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): - """FWaaS agent support to be used by neutron's L3 agent.""" +class FWaaSL3AgentExtension(l3_agent_extension.L3AgentCoreResourceExtension): + """FWaaS agent extension.""" + + SUPPORTED_RESOURCE_TYPES = [f_resources.FIREWALL_GROUP, + f_resources.FIREWALL_POLICY, + f_resources.FIREWALL_RULE] + + def initialize(self, connection, driver_type): + self._register_rpc_consumers(connection) + + def consume_api(self, agent_api): + LOG.debug("FWaaS consume_api call occurred with %s" % agent_api) + self.agent_api = agent_api + + def _register_rpc_consumers(self, connection): + #TODO(njohnston): Add RPC consumer connection loading here. + pass + + def start_rpc_listeners(self, host, conf): + self.endpoints = [self] + + self.conn = n_rpc.create_connection() + self.conn.create_consumer( + f_const.FW_AGENT, self.endpoints, fanout=False) + return self.conn.consume_in_threads() def __init__(self, host, conf): LOG.debug("Initializing firewall group agent") + self.agent_api = None self.neutron_service_plugins = None self.conf = conf self.fwaas_enabled = cfg.CONF.fwaas.enabled + self.start_rpc_listeners(host, conf) # None means l3-agent has no information on the server # configuration due to the lack of RPC support. if self.neutron_service_plugins is not None: @@ -96,7 +123,7 @@ class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): self.services_sync_needed = False self.fwplugin_rpc = FWaaSL3PluginApi(f_const.FIREWALL_PLUGIN, host) - super(FWaaSL3AgentRpcCallback, self).__init__(host=host) + super(FWaaSL3AgentExtension, self).__init__() @property def _local_namespaces(self): @@ -127,7 +154,8 @@ class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): else: fwg_port_ids = firewall_group['add-port-ids'] elif not require_new_plugin: - routers = [self.router_info[rid] for rid in self.router_info] + routers = self.agent_api.get_routers_in_project( + firewall_group['tenant_id']) for router in routers: if router.router['tenant_id'] == firewall_group['tenant_id']: fwg_port_ids.extend([p['id'] for p in @@ -140,21 +168,17 @@ class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): """Returns port objects in the local namespace, along with their router_info. """ - in_ns_ports = [] - if port_ids: - for router_id in self.router_info: - # For routers without an interface - get_routers returns - # the router - but this is not yet populated in router_info - router_info = self.router_info[router_id] - if router_info.ns_name not in self._local_namespaces: - continue - in_ns_router_port_ids = [] - for port in router_info.internal_ports: - if port['id'] in port_ids: - in_ns_router_port_ids.append(port['id']) - if in_ns_router_port_ids: - in_ns_ports.append((router_info, in_ns_router_port_ids)) - return in_ns_ports + in_ns_ports = {} # This will be converted to a list later. + if port_ids and self.agent_api: + for port_id in port_ids: + # This fetched router_info is guaranteed to be in_namespace. + router_info = self.agent_api.get_router_hosting_port(port_id) + if router_info: + if router_info in in_ns_ports: + in_ns_ports[router_info].append(port_id) + else: + in_ns_ports[router_info] = [port_id] + return list(in_ns_ports.items()) def _invoke_driver_for_sync_from_plugin(self, ctx, port, firewall_group): """Calls the FWaaS driver's delete_firewall_group method if firewall @@ -200,29 +224,27 @@ class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): self.fwplugin_rpc.set_firewall_group_status( ctx, firewall_group['id'], status) - def _process_router_add(self, new_router): - """If the new router is in the local namespace, queries the plugin to - get the firewall groups for the project in question and then sees if - the router has any ports for any firewall group that is configured - for that project. If so, installs firewall group rules on the - requested ports on this router. + def _process_router_update(self, updated_router): + """If a new or existing router in the local namespace is updated, + queries the plugin to get the firewall groups for the project in + question and then sees if the router has any ports for any firewall + group that is configured for that project. If so, installs firewall + group rules on the requested ports on this router. """ - LOG.debug("Process router add, router_id: %s.", - new_router.router['id']) - router_id = new_router.router['id'] - if router_id not in self.router_info or \ - self.router_info[router_id].ns_name not in \ - self._local_namespaces: + LOG.debug("Process router update, router_id: %s tenant: %s.", + updated_router['id'], updated_router['tenant_id']) + router_id = updated_router['id'] + if not self.agent_api.is_router_in_namespace(router_id): return # Get the firewall groups for the new router's project. # NOTE: Vernacular move from "tenant" to "project" doesn't yet appear # as a key in router or firewall group objects. - ctx = context.Context('', new_router.router['tenant_id']) + ctx = context.Context('', updated_router['tenant_id']) fwg_list = self.fwplugin_rpc.get_firewall_groups_for_project(ctx) # Apply a firewall group, as requested, to ports on the new router. - for port in new_router.router.internal_ports: + for port in updated_router['_interfaces']: for firewall_group in fwg_list: if (self._has_port_insertion_fields(firewall_group) and (port['id'] in firewall_group['add-port-ids'] or @@ -232,7 +254,7 @@ class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): # A port can have at most one firewall group. break - def process_router_add(self, new_router): + def add_router(self, context, new_router): """Handles agent restart and router add. Fetches firewall groups from plugin and updates driver. """ @@ -240,12 +262,37 @@ class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): return try: - self._process_router_add(new_router) + self._process_router_update(new_router) except Exception: - LOG.exception(_LE("FWaaS RPC info call failed for %s"), - new_router.router['id']) + LOG.exception(_LE("FWaaS router add RPC info call failed for %s"), + new_router['id']) self.services_sync_needed = True + def update_router(self, context, updated_router): + """Handles agent restart and router add. Fetches firewall groups from + plugin and updates driver. + """ + if not self.fwaas_enabled: + return + + try: + self._process_router_update(updated_router) + except Exception: + #TODO(njohnston): This repr should be replaced. + LOG.exception( + _LE("FWaaS router update RPC info call failed for %s"), + repr(updated_router)) + self.services_sync_needed = True + + def delete_router(self, context, new_router): + """Handles router deletion. There is basically nothing to do for this + in the context of FWaaS with an IPTables driver; the namespace will + already have been deleted, taking the IPTables rules with it. + """ + #TODO(njohnston): When another firewall driver is implemented, look at + # expanding this out so that the driver can handle deletion calls. + pass + def process_services_sync(self, ctx): """Syncs with plugin and applies the sync data. """ @@ -283,7 +330,6 @@ class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): # Get the in-namespace ports to which to add the firewall group. ports_for_fwg = self._get_firewall_group_ports(context, firewall_group) - if not ports_for_fwg: return @@ -451,9 +497,9 @@ class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): self.services_sync_needed = True -class L3WithFWaaS(FWaaSL3AgentRpcCallback, agent.L3NATAgentWithStateReport): +class L3WithFWaaS(FWaaSL3AgentExtension): - def __init__(self, host, conf=None): + def __init__(self, conf=None): if conf: self.conf = conf else: diff --git a/neutron_fwaas/services/firewall/drivers/fwaas_base.py b/neutron_fwaas/services/firewall/drivers/fwaas_base.py index acae94055..3ec22ecf0 100644 --- a/neutron_fwaas/services/firewall/drivers/fwaas_base.py +++ b/neutron_fwaas/services/firewall/drivers/fwaas_base.py @@ -59,8 +59,8 @@ class FwaasDriverBase(object): application of rules. Argument agent_mode indicates the l3 agent in DVR or DVR_SNAT or LEGACY mode. """ - - @abc.abstractmethod + # TODO(Margaret): Remove the first 3 methods and make the second three + # @abc.abstractmethod def create_firewall(self, agent_mode, apply_list, firewall): """Create the Firewall with default (drop all) policy. @@ -69,7 +69,6 @@ class FwaasDriverBase(object): """ pass - @abc.abstractmethod def delete_firewall(self, agent_mode, apply_list, firewall): """Delete firewall. @@ -78,7 +77,6 @@ class FwaasDriverBase(object): """ pass - @abc.abstractmethod def update_firewall(self, agent_mode, apply_list, firewall): """Apply the policy on all trusted interfaces. @@ -87,6 +85,30 @@ class FwaasDriverBase(object): """ pass + def create_firewall_group(self, agent_mode, apply_list, firewall): + """Create the Firewall with default (drop all) policy. + + The default policy will be applied on all the interfaces of + trusted zone. + """ + pass + + def delete_firewall_group(self, agent_mode, apply_list, firewall): + """Delete firewall. + + Removes all policies created by this instance and frees up + all the resources. + """ + pass + + def update_firewall_group(self, agent_mode, apply_list, firewall): + """Apply the policy on all trusted interfaces. + + Remove previous policy and apply the new policy on all trusted + interfaces. + """ + pass + @abc.abstractmethod def apply_default_policy(self, agent_mode, apply_list, firewall): """Apply the default policy on all trusted interfaces. diff --git a/neutron_fwaas/services/firewall/drivers/linux/iptables_fwaas_v2.py b/neutron_fwaas/services/firewall/drivers/linux/iptables_fwaas_v2.py index cf6f7680e..44b83ad02 100644 --- a/neutron_fwaas/services/firewall/drivers/linux/iptables_fwaas_v2.py +++ b/neutron_fwaas/services/firewall/drivers/linux/iptables_fwaas_v2.py @@ -58,6 +58,9 @@ class IptablesFwaasDriver(fwaas_base_v2.FwaasDriverBase): LOG.debug("Initializing fwaas iptables driver") self.pre_firewall = None + def initialize(self): + pass + def _get_intf_name(self, if_prefix, port_id): _name = "%s%s" % (if_prefix, port_id) return _name[:MAX_INTF_NAME_LEN] @@ -78,7 +81,7 @@ class IptablesFwaasDriver(fwaas_base_v2.FwaasDriverBase): LOG.exception(_LE("Failed to create firewall: %s"), firewall['id']) raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME) - def _get_ipt_mgrs_with_if_prefix(self, agent_mode, router_info): + def _get_ipt_mgrs_with_if_prefix(self, agent_mode, ri): """Gets the iptables manager along with the if prefix to apply rules. With DVR we can have differing namespaces depending on which agent @@ -88,18 +91,18 @@ class IptablesFwaasDriver(fwaas_base_v2.FwaasDriverBase): namespace and a fip so this is provided back as a list - so in that scenario rules can be applied on both. """ - if not router_info.router.get('distributed'): - return [{'ipt': router_info.iptables_manager, + if not ri.router.get('distributed'): + return [{'ipt': ri.iptables_manager, 'if_prefix': INTERNAL_DEV_PREFIX}] ipt_mgrs = [] # TODO(sridar): refactor to get strings to a common location. if agent_mode == 'dvr_snat': - if router_info.snat_iptables_manager: - ipt_mgrs.append({'ipt': router_info.snat_iptables_manager, + if ri.snat_iptables_manager: + ipt_mgrs.append({'ipt': ri.snat_iptables_manager, 'if_prefix': SNAT_INT_DEV_PREFIX}) - if router_info.dist_fip_count: + if ri.dist_fip_count: # handle the fip case on n/w or compute node. - ipt_mgrs.append({'ipt': router_info.iptables_manager, + ipt_mgrs.append({'ipt': ri.iptables_manager, 'if_prefix': ROUTER_2_FIP_DEV_PREFIX}) return ipt_mgrs @@ -108,9 +111,9 @@ class IptablesFwaasDriver(fwaas_base_v2.FwaasDriverBase): {'fw_id': firewall['id'], 'tid': firewall['tenant_id']}) fwid = firewall['id'] try: - for router_info, router_fw_ports in apply_list: + for ri, router_fw_ports in apply_list: ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix( - agent_mode, router_info) + agent_mode, ri) for ipt_if_prefix in ipt_if_prefix_list: ipt_mgr = ipt_if_prefix['ipt'] self._remove_chains(fwid, ipt_mgr) @@ -148,9 +151,9 @@ class IptablesFwaasDriver(fwaas_base_v2.FwaasDriverBase): {'fw_id': firewall['id'], 'tid': firewall['tenant_id']}) fwid = firewall['id'] try: - for router_info, router_fw_ports in apply_list: + for ri, router_fw_ports in apply_list: ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix( - agent_mode, router_info) + agent_mode, ri) for ipt_if_prefix in ipt_if_prefix_list: # the following only updates local memory; no hole in FW ipt_mgr = ipt_if_prefix['ipt'] @@ -172,9 +175,9 @@ class IptablesFwaasDriver(fwaas_base_v2.FwaasDriverBase): def _setup_firewall(self, agent_mode, apply_list, firewall): fwid = firewall['id'] - for router_info, router_fw_ports in apply_list: + for ri, router_fw_ports in apply_list: ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix( - agent_mode, router_info) + agent_mode, ri) for ipt_if_prefix in ipt_if_prefix_list: ipt_mgr = ipt_if_prefix['ipt'] # the following only updates local memory; no hole in FW @@ -298,9 +301,9 @@ class IptablesFwaasDriver(fwaas_base_v2.FwaasDriverBase): def _remove_conntrack_new_firewall(self, agent_mode, apply_list, firewall): """Remove conntrack when create new firewall""" routers_list = list(set([apply_info[0] for apply_info in apply_list])) - for router_info in routers_list: + for ri in routers_list: ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix( - agent_mode, router_info) + agent_mode, ri) for ipt_if_prefix in ipt_if_prefix_list: ipt_mgr = ipt_if_prefix['ipt'] cmd = self._get_conntrack_cmd_from_rule(ipt_mgr) @@ -310,9 +313,9 @@ class IptablesFwaasDriver(fwaas_base_v2.FwaasDriverBase): apply_list, pre_firewall, firewall): """Remove conntrack when updated firewall""" routers_list = list(set([apply_info[0] for apply_info in apply_list])) - for router_info in routers_list: + for ri in routers_list: ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix( - agent_mode, router_info) + agent_mode, ri) for ipt_if_prefix in ipt_if_prefix_list: ipt_mgr = ipt_if_prefix['ipt'] ch_rules = self._find_changed_rules(pre_firewall, @@ -391,7 +394,7 @@ class IptablesFwaasDriver(fwaas_base_v2.FwaasDriverBase): jump_rule = ['%s %s -j %s-%s' % ( IPTABLES_DIR[direction], intf_name, bname, chain_name)] - self._add_rules_to_chain(ipt_mgr, ver, + self._add_rules_to_chain(ipt_mgr, ver, 'FORWARD', jump_rule) # jump to DROP_ALL policy diff --git a/neutron_fwaas/services/firewall/fwaas_plugin.py b/neutron_fwaas/services/firewall/fwaas_plugin.py index f6821f7b5..50f4e6f2d 100644 --- a/neutron_fwaas/services/firewall/fwaas_plugin.py +++ b/neutron_fwaas/services/firewall/fwaas_plugin.py @@ -148,7 +148,7 @@ class FirewallPlugin( self.start_rpc_listeners() self.agent_rpc = FirewallAgentApi( - f_const.L3_AGENT, + f_const.FW_AGENT, cfg.CONF.host ) firewall_db.subscribe() diff --git a/neutron_fwaas/services/firewall/fwaas_plugin_v2.py b/neutron_fwaas/services/firewall/fwaas_plugin_v2.py index b666a0f8a..d9a4ab5c5 100644 --- a/neutron_fwaas/services/firewall/fwaas_plugin_v2.py +++ b/neutron_fwaas/services/firewall/fwaas_plugin_v2.py @@ -107,11 +107,11 @@ class FirewallCallbacks(object): if fwg['status'] == n_const.PENDING_DELETE: fwg_with_rules['add-port-ids'] = [] fwg_with_rules['del-port-ids'] = ( - self.plugin._get_ports_for_firewall_group(context, + self.plugin._get_ports_in_firewall_group(context, fwg['id'])) else: fwg_with_rules['add-port-ids'] = ( - self.plugin._get_ports_for_firewall_group(context, + self.plugin._get_ports_in_firewall_group(context, fwg['id'])) fwg_with_rules['del-port-ids'] = [] fwg_list.append(fwg_with_rules) @@ -142,7 +142,7 @@ class FirewallPluginV2( self.start_rpc_listeners() self.agent_rpc = FirewallAgentApi( - f_const.L3_AGENT, + f_const.FW_AGENT, cfg.CONF.host ) @@ -205,6 +205,8 @@ class FirewallPluginV2( # TODO(sridar): elevated context and do we want to use public ? for port_id in fwg_ports: port_db = self._core_plugin._get_port(context, port_id) + if port_db['device_owner'] != "network:router_interface": + raise fw_ext.FirewallGroupPortInvalid(port_id=port_id) if port_db['tenant_id'] != tenant_id: raise fw_ext.FirewallGroupPortInvalidProject(port_id=port_id) return diff --git a/neutron_fwaas/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent.py b/neutron_fwaas/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent.py index 50e2fa350..89754ee76 100644 --- a/neutron_fwaas/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent.py +++ b/neutron_fwaas/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent.py @@ -12,13 +12,15 @@ # License for the specific language governing permissions and limitations # under the License. +import mock +import testtools import uuid -import mock from oslo_config import cfg from neutron.agent.l3 import config as l3_config from neutron.agent.l3 import ha +from neutron.agent.l3 import l3_agent_extension_api as l3_agent_api from neutron.agent.l3 import router_info from neutron.agent.linux import ip_lib from neutron.conf import common as base_config @@ -38,12 +40,12 @@ class FWaasHelper(object): pass -class FWaasAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, FWaasHelper): +class FWaasAgent(firewall_l3_agent.FWaaSL3AgentExtension, FWaasHelper): neutron_service_plugins = [] def _setup_test_agent_class(service_plugins): - class FWaasTestAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, + class FWaasTestAgent(firewall_l3_agent.FWaaSL3AgentExtension, FWaasHelper): neutron_service_plugins = service_plugins @@ -64,13 +66,15 @@ class TestFwaasL3AgentRpcCallback(base.BaseTestCase): self.conf.register_opts(l3_config.OPTS) self.conf.register_opts(ha.OPTS) self.conf.register_opts(firewall_agent_api.FWaaSOpts, 'fwaas') - self.api = FWaasAgent("myhost", self.conf) + self.api = FWaasAgent(host=None, conf=self.conf) self.api.fwaas_driver = test_firewall_agent_api.NoopFwaasDriver() self.adminContext = context.get_admin_context() self.router_id = str(uuid.uuid4()) self.agent_conf = mock.Mock() + project_id = str(uuid.uuid4()) # For 'tenant_id' and 'project_id' keys self.ri_kwargs = {'router': {'id': self.router_id, - 'tenant_id': str(uuid.uuid4())}, + 'tenant_id': project_id, + 'project_id': project_id}, 'agent_conf': self.agent_conf, 'interface_driver': mock.ANY, 'use_ipv6': mock.ANY, @@ -82,8 +86,8 @@ class TestFwaasL3AgentRpcCallback(base.BaseTestCase): with mock.patch('oslo_utils.importutils.import_object'): test_agent_class(cfg.CONF) + @testtools.skip('needs to be refactored for fwaas v2') def test_fw_config_mismatch_plugin_enabled_agent_disabled(self): - self.skipTest('this is broken') test_agent_class = _setup_test_agent_class([constants.FIREWALL]) cfg.CONF.set_override('enabled', False, 'fwaas') self.assertRaises(SystemExit, test_agent_class, cfg.CONF) @@ -300,9 +304,15 @@ class TestFwaasL3AgentRpcCallback(base.BaseTestCase): def test_get_router_info_list_for_tenant(self): ri = self._prepare_router_data() + router_info = {ri.router_id: ri} + self.api.router_info = router_info + + api_object = l3_agent_api.L3AgentExtensionAPI(router_info) + self.api.consume_api(api_object) + routers = [ri.router] router_ids = [router['id'] for router in routers] - self.api.router_info = {ri.router_id: ri} + with mock.patch.object(ip_lib.IPWrapper, 'get_namespaces') as mock_get_namespaces: mock_get_namespaces.return_value = [] @@ -316,19 +326,27 @@ class TestFwaasL3AgentRpcCallback(base.BaseTestCase): rtr_with_ri): # ri.router with associated router_info (ri) # rtr2 has no router_info + ri = self._prepare_router_data() rtr2 = {'id': str(uuid.uuid4()), 'tenant_id': ri.router['tenant_id']} + routers = [rtr2] - self.api.router_info = {} + router_info = {} ri_expected = [] + if rtr_with_ri: - self.api.router_info[ri.router_id] = ri + router_info[ri.router_id] = ri routers.append(ri.router) ri_expected.append(ri) + + self.api.router_info = router_info router_ids = [router['id'] for router in routers] + with mock.patch.object(ip_lib.IPWrapper, 'get_namespaces') as mock_get_namespaces: - mock_get_namespaces.return_value = ri.ns_name + mock_get_namespaces.return_value = [ri.ns_name] + api_object = l3_agent_api.L3AgentExtensionAPI(router_info) + self.api.consume_api(api_object) router_info_list = self.api._get_router_info_list_for_tenant( router_ids, ri.router['tenant_id']) diff --git a/neutron_fwaas/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent_v2.py b/neutron_fwaas/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent_v2.py index 8b11b3271..a851d057e 100644 --- a/neutron_fwaas/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent_v2.py +++ b/neutron_fwaas/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent_v2.py @@ -19,6 +19,7 @@ import mock from oslo_config import cfg from neutron.agent.l3 import config as l3_config +from neutron.agent.l3 import l3_agent_extension_api as l3_agent_api from neutron.agent.l3 import router_info from neutron.agent.linux import ip_lib from neutron import context @@ -33,35 +34,54 @@ from neutron_fwaas.tests.unit.services.firewall.agents \ class FWaasHelper(object): - def __init__(self, host): + def __init__(self): pass -class FWaasAgent(firewall_l3_agent_v2.FWaaSL3AgentRpcCallback, FWaasHelper): +class FWaasAgent(firewall_l3_agent_v2.L3WithFWaaS, FWaasHelper): neutron_service_plugins = [] + def add_router(self, context, data): + pass + + def delete_router(self, context, data): + pass + + def update_router(self, context, data): + pass + def _setup_test_agent_class(service_plugins): - class FWaasTestAgent(firewall_l3_agent_v2.FWaaSL3AgentRpcCallback, + class FWaasTestAgent(firewall_l3_agent_v2.L3WithFWaaS, FWaasHelper): neutron_service_plugins = service_plugins def __init__(self, conf): self.event_observers = mock.Mock() self.conf = conf - super(FWaasTestAgent, self).__init__('myhost', conf) + super(FWaasTestAgent, self).__init__(conf) + + def add_router(self, context, data): + pass + + def delete_router(self, context, data): + pass + + def update_router(self, context, data): + pass return FWaasTestAgent -class TestFwaasL3AgentRpcCallback(base.BaseTestCase): +class TestFWaaSL3AgentExtension(base.BaseTestCase): def setUp(self): - super(TestFwaasL3AgentRpcCallback, self).setUp() + super(TestFWaaSL3AgentExtension, self).setUp() self.conf = cfg.ConfigOpts() self.conf.register_opts(l3_config.OPTS) self.conf.register_opts(firewall_agent_api.FWaaSOpts, 'fwaas') - self.api = FWaasAgent('myhost', self.conf) + self.conf.host = 'myhost' + self.api = FWaasAgent(self.conf) self.api.fwaas_driver = test_firewall_agent_api.NoopFwaasDriverV2() self.adminContext = context.get_admin_context() self.context = mock.sentinel.context @@ -279,7 +299,9 @@ class TestFwaasL3AgentRpcCallback(base.BaseTestCase): ports = [{'id': pid} for pid in port_ids] ri = self._prepare_router_data() ri.internal_ports = ports - self.api.router_info = {ri.router_id: ri} + router_info = {ri.router_id: ri} + api_object = l3_agent_api.L3AgentExtensionAPI(router_info) + self.api.consume_api(api_object) fw_port_ids = port_ids with mock.patch.object(ip_lib.IPWrapper, @@ -288,7 +310,7 @@ class TestFwaasL3AgentRpcCallback(base.BaseTestCase): mock_get_namespaces.return_value = [] ports_for_fw_list = self.api._get_in_ns_ports(fw_port_ids) - mock_get_namespaces.assert_called_once_with() + mock_get_namespaces.assert_called_with() self.assertFalse(ports_for_fw_list) def test_get_in_ns_ports_for_fw(self): @@ -296,8 +318,10 @@ class TestFwaasL3AgentRpcCallback(base.BaseTestCase): ports = [{'id': pid} for pid in port_ids] ri = self._prepare_router_data() ri.internal_ports = ports - self.api.router_info = {} - self.api.router_info[ri.router_id] = ri + router_info = {} + router_info[ri.router_id] = ri + api_object = l3_agent_api.L3AgentExtensionAPI(router_info) + self.api.consume_api(api_object) fw_port_ids = port_ids ports_for_fw_expected = [(ri, port_ids)] @@ -306,3 +330,5 @@ class TestFwaasL3AgentRpcCallback(base.BaseTestCase): mock_get_namespaces.return_value = [ri.ns_name] ports_for_fw_actual = self.api._get_in_ns_ports(fw_port_ids) self.assertEqual(ports_for_fw_expected, ports_for_fw_actual) + + #TODO(Margaret) Add test for add_router method. diff --git a/neutron_fwaas/tests/unit/services/firewall/agents/test_firewall_agent_api.py b/neutron_fwaas/tests/unit/services/firewall/agents/test_firewall_agent_api.py index fff45de9d..b82f48c15 100644 --- a/neutron_fwaas/tests/unit/services/firewall/agents/test_firewall_agent_api.py +++ b/neutron_fwaas/tests/unit/services/firewall/agents/test_firewall_agent_api.py @@ -28,13 +28,13 @@ class NoopFwaasDriver(fwaas_base.FwaasDriverBase): This driver is for disabling Fwaas functionality. """ - def create_firewall(self, agent_mode, apply_list, firewall): + def create_firewall_group(self, agent_mode, apply_list, firewall): pass - def delete_firewall(self, agent_mode, apply_list, firewall): + def delete_firewall_group(self, agent_mode, apply_list, firewall): pass - def update_firewall(self, agent_mode, apply_list, firewall): + def update_firewall_group(self, agent_mode, apply_list, firewall): pass def apply_default_policy(self, agent_mode, apply_list, firewall): diff --git a/neutron_fwaas/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas_v2.py b/neutron_fwaas/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas_v2.py index 5f0e8e23f..42097e45c 100644 --- a/neutron_fwaas/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas_v2.py +++ b/neutron_fwaas/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas_v2.py @@ -181,11 +181,14 @@ class IptablesFwaasTestCase(base.BaseTestCase): mock.call.add_rule(egress_chain, rule2), mock.call.add_rule(egress_chain, rule3)] - intf_name = self._get_intf_name(if_prefix, FAKE_PORT_IDS[-1]) - calls.append(mock.call.add_rule('FORWARD', - '-o %s -j %s' % (intf_name, ipt_mgr_ichain))) - calls.append(mock.call.add_rule('FORWARD', - '-i %s -j %s' % (intf_name, ipt_mgr_echain))) + for port in FAKE_PORT_IDS: + intf_name = self._get_intf_name(if_prefix, port) + calls.append(mock.call.add_rule('FORWARD', + '-o %s -j %s' % (intf_name, ipt_mgr_ichain))) + for port in FAKE_PORT_IDS: + intf_name = self._get_intf_name(if_prefix, port) + calls.append(mock.call.add_rule('FORWARD', + '-i %s -j %s' % (intf_name, ipt_mgr_echain))) for direction in ['o', 'i']: for port_id in FAKE_PORT_IDS: diff --git a/setup.cfg b/setup.cfg index b1e7c76c9..b40496b42 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,13 +27,12 @@ setup-hooks = pbr.hooks.setup_hook [entry_points] -console_scripts = - neutron-l3-agent = neutron_fwaas.cmd.eventlet.agents.fw:main firewall_drivers = # These are for backwards compat with Juno firewall service provider # configuration values neutron.services.firewall.drivers.linux.iptables_fwaas.IptablesFwaasDriver = neutron_fwaas.services.firewall.drivers.linux.iptables_fwaas:IptablesFwaasDriver iptables = neutron_fwaas.services.firewall.drivers.linux.iptables_fwaas:IptablesFwaasDriver + iptables_v2 = neutron_fwaas.services.firewall.drivers.linux.iptables_fwaas_v2:IptablesFwaasDriver neutron.service_plugins = firewall = neutron_fwaas.services.firewall.fwaas_plugin:FirewallPlugin firewall_v2 = neutron_fwaas.services.firewall.fwaas_plugin_v2:FirewallPluginV2 @@ -45,6 +44,9 @@ tempest.test_plugins = neutron-fwaas = neutron_fwaas.tests.tempest_plugin.plugin:NeutronFWaaSPlugin oslo.config.opts = firewall.agent = neutron_fwaas.opts:list_agent_opts +neutron.agent.l3.extensions = + fwaas = neutron_fwaas.services.firewall.agents.l3reference.firewall_l3_agent:L3WithFWaaS + fwaas_v2 = neutron_fwaas.services.firewall.agents.l3reference.firewall_l3_agent_v2:L3WithFWaaS [build_sphinx] all_files = 1