FWaaS v2 extension for L2 agent

This patch adds L2 agent extension for FWaaS v2 to handle
create/update/delete firewall groups on ports. It also
handles applying firewall group on port, when a port is
added/created/deleted.

DocImpact

Depends-On:  Ifd6758617ab8fd49e69ad1a0483fefa479d7b8e7
Co-Authored-By: Paddu Krishnan <kprad1@yahoo.com>
Co-Authored-By: Chandan Dutta Chowdhury <chandanc@juniper.net>
Co-Authored-By: Nguyen Phuong An <AnNP@vn.fujitsu.com>¬
Co-Authored-By: Inessa Vasilevskaya <ivasilevskaya@mirantis.com>
Partial-Implements: blueprint fwaas-api-2.0
Change-Id: I9f172be46ee590b99313106fa262019a2583774a
This commit is contained in:
Yushiro FURUKAWA 2016-05-31 18:20:54 +09:00 committed by Nguyen Phuong An
parent 74eac2ca29
commit dbac4b8922
18 changed files with 1485 additions and 44 deletions

View File

@ -46,6 +46,8 @@ function configure_fwaas_v2() {
neutron_fwaas_configure_driver fwaas_v2
iniset_multiline $Q_L3_CONF_FILE fwaas agent_version v2
iniset_multiline $Q_L3_CONF_FILE fwaas driver $FWAAS_DRIVER_V2
iniset $NEUTRON_CORE_PLUGIN_CONF fwaas firewall_l2_driver $FW_L2_DRIVER
iniset $NEUTRON_CORE_PLUGIN_CONF agent extensions fwaas_v2
}
function neutron_fwaas_generate_config_files {

View File

@ -1,5 +1,6 @@
FWAAS_DRIVER_V1=${FWAAS_DRIVER_V1:-iptables}
FWAAS_DRIVER_V2=${FWAAS_DRIVER_V2:-iptables_v2}
FW_L2_DRIVER=${FW_L2_DRIVER:-noop}
FWAAS_PLUGIN_V1=${FWAAS_PLUGIN:-firewall}
FWAAS_PLUGIN_V2=${FWAAS_PLUGIN:-firewall_v2}

View File

@ -25,3 +25,12 @@ FIREWALL_RULE_LIST = 'firewall_rule_list'
DEFAULT_FWG = 'default'
DEFAULT_FWP_INGRESS = 'default ingress'
DEFAULT_FWP_EGRESS = 'default egress'
# Firewall group events for agent-side
DELETE_FWG = 'delete_firewall_group'
UPDATE_FWG = 'update_firewall_group'
CREATE_FWG = 'create_firewall_group'
# Port events for L2 agent extension
HANDLE_PORT = 'handle_port'
DELETE_PORT = 'delete_port'

View File

@ -1079,3 +1079,16 @@ class Firewall_db_mixin_v2(fw_ext.Firewallv2PluginBase, base_db.CommonDbMixin):
return self._get_collection(context, FirewallGroup,
self._make_firewall_group_dict,
filters=filters, fields=fields)
def get_firewall_group_for_port(self, context, port_id):
"""Get firewall group is associated with a port
:param context: context object
:param port_id: Port ID.
"""
filters = {'port_id': [port_id]}
fwg_port_binding = self._get_collection_query(
context, FirewallGroupPortAssociation, filters=filters).first()
if fwg_port_binding:
fwg_id = fwg_port_binding['firewall_group_id']
return self._make_firewall_group_dict_with_rules(context, fwg_id)

View File

@ -22,7 +22,7 @@ from neutron_fwaas._i18n import _
FWAAS_V1 = "v1"
FWAAS_V2 = "v2"
FW_L2_NOOP_DRIVER = 'noop'
FWaaSOpts = [
cfg.StrOpt(
@ -35,12 +35,17 @@ FWaaSOpts = [
help=_("Enable FWaaS")),
cfg.StrOpt(
'agent_version',
default=FWAAS_V1,
default=FWAAS_V2,
help=_("Firewall agent class")),
cfg.StrOpt(
'conntrack_driver',
default='conntrack',
help=_("Name of the FWaaS Conntrack Driver")),
cfg.StrOpt(
'firewall_l2_driver',
default=FW_L2_NOOP_DRIVER,
help=_("Name of the firewall l2 driver")
)
]
cfg.CONF.register_opts(FWaaSOpts, 'fwaas')

View File

@ -0,0 +1,449 @@
# Copyright 2017 FUJITSU LIMITED
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_concurrency import lockutils
from oslo_config import cfg
from oslo_log import log as logging
import six
from neutron.agent import securitygroups_rpc
from neutron.common import rpc as n_rpc
from neutron import manager
from neutron.plugins.ml2.drivers.openvswitch.agent import vlanmanager
from neutron_lib.agent import l2_extension
from neutron_lib import constants as nl_const
from neutron_lib.exceptions import firewall_v2 as f_exc
from neutron_fwaas._i18n import _
from neutron_fwaas.common import fwaas_constants as consts
from neutron_fwaas.services.firewall.agents import firewall_agent_api as api
LOG = logging.getLogger(__name__)
FWAAS_L2_DRIVER = 'neutron.agent.l2.firewall_drivers'
class FWaaSL2PluginApi(api.FWaaSPluginApiMixin):
"""L2 agent side of FWaaS agent-to-plugin RPC API"""
def get_firewall_group_for_port(self, context, port_id):
"""Get firewall group is associated with a port"""
LOG.debug("Get firewall group is associated with port %s", port_id)
cctxt = self.client.prepare()
return cctxt.call(context, 'get_firewall_group_for_port',
port_id=port_id)
def set_firewall_group_status(self, context, fwg_id, status, host):
"""Set the status of a group operation."""
LOG.debug("Fetch firewall group changing status")
cctxt = self.client.prepare()
return cctxt.call(context, 'set_firewall_group_status',
fwg_id=fwg_id, status=status, host=host)
def firewall_group_deleted(self, context, fwg_id, host):
"""Notifies the plugin that a firewall group has been deleted."""
LOG.debug("Notify to the plugin that firewall group has been deleted")
cctxt = self.client.prepare()
return cctxt.call(context, 'firewall_group_deleted',
fwg_id=fwg_id, host=host)
class FWaaSV2AgentExtension(l2_extension.L2AgentExtension):
def initialize(self, connection, driver_type):
"""Perform Agent Extension initialization"""
self.conf = cfg.CONF
int_br = self.agent_api.request_int_br()
self.vlan_manager = vlanmanager.LocalVlanManager()
fw_l2_driver_cls = self._load_l2_driver_class(driver_type)
sg_enabled = securitygroups_rpc.is_firewall_enabled()
self.driver = manager.NeutronManager.load_class_for_provider(
FWAAS_L2_DRIVER, fw_l2_driver_cls)(int_br, sg_enabled)
self.plugin_rpc = FWaaSL2PluginApi(
consts.FIREWALL_PLUGIN, self.conf.host)
self.start_rpc_listeners()
self.fwg_map = PortFirewallGroupMap()
def consume_api(self, agent_api):
self.agent_api = agent_api
def start_rpc_listeners(self):
self.conn = n_rpc.create_connection()
endpoints = [self]
self.conn.create_consumer(consts.FW_AGENT, endpoints, fanout=False)
return self.conn.consume_in_threads()
def _load_l2_driver_class(self, driver_type):
driver = self.conf.fwaas.firewall_l2_driver or 'noop'
if driver == api.FW_L2_NOOP_DRIVER:
return driver
if driver != driver_type:
raise Exception(
_("Firewall l2 driver: %s is not compatible"), driver_type)
return driver
def _is_port_layer2(self, port):
"""This function checks if a port belongs to a L2 case.
Currently both DHCP and router ports are eliminated.
"""
return port and port.get('device_owner', '').startswith(
nl_const.DEVICE_OWNER_COMPUTE_PREFIX)
def _get_firewall_group_ports(self, fwg, host, to_delete=False):
port_list = []
port_ids = fwg['del-port-ids'] if to_delete else fwg['add-port-ids']
LOG.debug("_get_fwg fwg=%(fwg)s ports=%(port)s to_delete=%(delete)s",
{'fwg': fwg, 'port': port_ids, 'delete': to_delete})
for fw_port in port_ids:
port_detail = fwg['port_details'].get(fw_port)
if (self._is_port_layer2(port_detail) and
port_detail.get('host') == host):
port_list.append(port_detail)
return port_list
@staticmethod
def _has_ports(fwg, event):
"""Verifying fwg has ports or not
This function verify applying firewall group on ports
:param fwg: a fwg object
:param event: create/update firewall group or
create/update/delete port
:return: True if applying firewall group is fine. Otherwise is False
"""
if event == consts.UPDATE_FWG and 'last-port' in fwg:
return not fwg['last-port']
else:
return bool(fwg['ports'])
@staticmethod
def _has_policy(fwg):
"""Verifying fwg has policy or not"""
return bool(fwg['ingress_firewall_policy_id'] or
fwg['egress_firewall_policy_id'])
def _compute_status(self, fwg, result, event=consts.CREATE_FWG):
"""Compute a status of specified firewall group for update
Validates 'ACTIVE', 'DOWN', 'INACTIVE', 'ERROR' and None as follows:
- "ERROR" : if result is not True
- "ACTIVE" : admin_state_up is True and exists ports
- "INACTIVE" : admin_state_up is True and with no ports
- "DOWN" : admin_state_up is False
- None : In case of 'delete_firewall_group'
"""
if not result:
return nl_const.ERROR
if not fwg['admin_state_up']:
return nl_const.DOWN
if event == consts.DELETE_FWG:
# This firewall_group will be deleted. No need to update status.
return
if (self._has_ports(fwg, event) and self._has_policy(fwg)):
return nl_const.ACTIVE
return nl_const.INACTIVE
def _get_network_id(self, fwg_port):
port_id = fwg_port.get('port_id', fwg_port.get('id'))
port_details = fwg_port.get('port_details')
if port_details:
target = port_details.get(port_id)
if target:
return target.get('network_id')
return
return fwg_port.get('network_id')
def _add_local_vlan_to_ports(self, fwg_ports):
"""Add local VLAN to ports if found
This function tries to add local VLAN related to ports.
"""
ports_with_lvlan = []
for fwg_port in fwg_ports:
try:
network_id = self._get_network_id(fwg_port)
l_vlan = self.vlan_manager.get(network_id).vlan
fwg_port['lvlan'] = int(l_vlan)
except vlanmanager.MappingNotFound:
LOG.warning("No Local VLAN found in network %s", network_id)
# NOTE(yushiro): We ignore this exception because we should send
# all selected ports to driver layer. It depends on driver's
# behavior whether it occurs an error with no local VLAN or not.
ports_with_lvlan.append(fwg_port)
return ports_with_lvlan
def _apply_fwg_rules(self, fwg, ports, event=consts.UPDATE_FWG):
"""This function invokes the driver create/update routine. """
# Set firewall group status; will be overwritten if call to driver
# fails.
if event in [consts.CREATE_FWG, consts.UPDATE_FWG]:
ports_for_driver = self._add_local_vlan_to_ports(ports)
else:
ports_for_driver = ports
# apply firewall group to driver
try:
if event == consts.UPDATE_FWG:
self.driver.update_firewall_group(ports_for_driver, fwg)
elif event == consts.DELETE_FWG:
self.driver.delete_firewall_group(ports_for_driver, fwg)
elif event == consts.CREATE_FWG:
self.driver.create_firewall_group(ports_for_driver, fwg)
except f_exc.FirewallInternalDriverError:
msg = _("FWaaS driver error in %(event)s_firewall_group "
"for firewall group: %(fwg_id)s")
LOG.exception(msg, {'event': event, 'fwg_id': fwg['id']})
return False
return True
def _send_fwg_status(self, context, fwg_id, status, host):
"""Send firewall group's status to plugin.
:returns: True if no exception occurred otherwise False
:rtype: boolean
"""
try:
self.plugin_rpc.set_firewall_group_status(
context, fwg_id, status, host)
LOG.debug("Successfully sent status(%s) for firewall_group(%s)",
status, fwg_id)
except Exception:
msg = _("Failed to send status for firewall_group(%s)")
LOG.exception(msg, fwg_id)
def _create_firewall_group(self, context, fwg, host,
event=consts.CREATE_FWG):
"""Handles RPC from plugin to create a firewall group. """
add_ports = self._get_firewall_group_ports(fwg, host)
if not add_ports:
status = nl_const.INACTIVE
else:
ret = self._apply_fwg_rules(fwg, add_ports, event)
# cleanup port_map
for port in add_ports:
self.fwg_map.remove_port(port)
status = self._compute_status(fwg, ret, event)
for port in add_ports:
self.fwg_map.set_port_fwg(port, fwg)
# Update status of firewall group which is associated with ports
# after updating.
self._send_fwg_status(context, fwg['id'], status, host)
def _delete_firewall_group(self, context, fwg, host,
event=consts.DELETE_FWG):
"""Handles RPC from plugin to delete a firewall group. """
del_ports = self._get_firewall_group_ports(fwg, host, to_delete=True)
if not del_ports:
return
# cleanup all flows of del_ports
ret = self._apply_fwg_rules(fwg, del_ports, event=consts.DELETE_FWG)
del_port_ids = []
for port in del_ports:
del_port_ids.append(port['id'])
self.fwg_map.remove_port(port)
if event == consts.DELETE_FWG:
self.fwg_map.remove_fwg(fwg)
self.plugin_rpc.firewall_group_deleted(
context, fwg['id'], host=self.conf.host)
else:
status = self._compute_status(fwg, ret, event)
self._send_fwg_status(context, fwg['id'], status, self.conf.host)
@lockutils.synchronized('fwg')
def create_firewall_group(self, context, firewall_group, host):
"""Handles create firewall group event"""
with self.driver.defer_apply():
try:
self._create_firewall_group(context, firewall_group, host)
except Exception as exc:
LOG.exception(
"Exception caught in create_firewall_group %s", exc)
self._send_fwg_status(context, firewall_group['id'],
status=nl_const.ERROR, host=host)
@lockutils.synchronized('fwg')
def delete_firewall_group(self, context, firewall_group, host):
"""Handles delete firewall group event"""
with self.driver.defer_apply():
try:
self._delete_firewall_group(context, firewall_group, host)
except Exception as exc:
LOG.exception(
"Exception caught in delete_firewall_group %s", exc)
self._send_fwg_status(context, firewall_group['id'],
status=nl_const.ERROR, host=host)
@lockutils.synchronized('fwg')
def update_firewall_group(self, context, firewall_group, host):
"""Handles update firewall group event"""
with self.driver.defer_apply():
try:
self._delete_firewall_group(
context, firewall_group, host, event=consts.UPDATE_FWG)
self._create_firewall_group(
context, firewall_group, host, event=consts.UPDATE_FWG)
except Exception as exc:
LOG.exception(
"Exception caught in update_firewall_group %s", exc)
self._send_fwg_status(context, firewall_group['id'],
status=nl_const.ERROR, host=host)
@lockutils.synchronized('fwg-port')
def handle_port(self, context, port):
"""Handle port update event"""
if not self._is_port_layer2(port):
return
# check if port is already assigned to a fwg
if self.fwg_map.get_port_fwg(port):
return
fwg = self.plugin_rpc.get_firewall_group_for_port(
context, port.get('port_id'))
if not fwg:
LOG.info("Firewall group applied to port %s is "
"not available on server.", port['port_id'])
return
ret = self._apply_fwg_rules(fwg, [port])
status = self._compute_status(fwg, ret, event=consts.HANDLE_PORT)
self.fwg_map.set_port_fwg(port, fwg)
self._send_fwg_status(
context, fwg_id=fwg['id'], status=status, host=self.conf.host)
def delete_port(self, context, port):
"""This is being called when a port is deleted by the agent. """
# delete_port should be handled only unbound timing for a port.
# If 'vif_port' is included in the port dict, this is called after
# deleted the port and should be ignored.
if 'vif_port' in port:
return
port = self.fwg_map.get_port(port)
if not self._is_port_layer2(port):
return
fwg = self.fwg_map.get_port_fwg(port)
if not fwg:
LOG.info("Firewall group associated to port %(port_id)s is "
"not available on server.", {'port_id': port['port_id']})
return
ret = self._apply_fwg_rules(fwg, [port], event=consts.DELETE_FWG)
port_id = self.fwg_map.port_id(port)
if port_id in fwg['ports']:
fwg['ports'].remove(port_id)
# update the fwg dict to known_fwgs
self.fwg_map.set_fwg(fwg)
self.fwg_map.remove_port(port)
status = self._compute_status(fwg, ret, event=consts.DELETE_PORT)
self._send_fwg_status(context, fwg['id'], status, self.conf.host)
class PortFirewallGroupMap(object):
"""Store relations between Port and FirewallGroup
This map is used in deleting firewall_group because the firewall_group has
been deleted at that time. Therefore, it is impossible to refer 'ports'.
This map enables to refer 'ports' for specified firewall_group.
"""
def __init__(self):
self.known_fwgs = {}
self.port_fwg = {}
self.port_detail = {}
# TODO(yushiro): If agent is restarted, this map doesn't have any
# information. Need to consider map initialization in __init__()
def port_id(self, port):
return (port if isinstance(port, six.string_types)
else port.get('port_id', port.get('id')))
def get_fwg(self, fwg_id):
return self.known_fwgs.get(fwg_id)
def set_fwg(self, fwg):
self.known_fwgs[fwg['id']] = fwg
def get_port(self, port):
return self.port_detail.get(self.port_id(port))
def get_port_fwg(self, port):
fwg_id = self.port_fwg.get(self.port_id(port))
if fwg_id:
return self.get_fwg(fwg_id)
def set_port_fwg(self, port, fwg):
"""Add a new port into fwg['ports']"""
port_id = self.port_id(port)
# Update fwg['ports'] data
fwg['ports'] = list(set(fwg['ports'] + [port_id]))
# Update fwg_id -> firewall_group data
self.known_fwgs[fwg['id']] = fwg
# Update port_id -> port data
self.port_detail[port_id] = port
# Update port_id -> firewall_group_id relation
self.port_fwg[port_id] = fwg['id']
def remove_port(self, port):
"""Remove port from fwg['ports'] and port_fwg dictionary
When removing 'port' from several cases, the port should be removed
from this map.
"""
port_id = self.port_id(port)
# Check if 'port_id' has registered in port_fwg dictionary.
# Update firewall_group
if port_id in self.port_fwg:
fwg_id = self.port_fwg.get(port_id)
if not fwg_id:
return
new_fwg = self.known_fwgs[fwg_id]
new_fwg['ports'] = [p for p in new_fwg['ports'] if p != port_id]
self.known_fwgs[fwg_id] = new_fwg
del self.port_fwg[port_id]
del self.port_detail[port_id]
def remove_fwg(self, fwg):
"""Remove firewall_group from known_fwgs dictionary
When removing firewall_group, it should be removed from this map
"""
if fwg['id'] in self.known_fwgs:
del self.known_fwgs[fwg['id']]

View File

@ -402,12 +402,21 @@ class FWaaSL3AgentExtension(l3_extension.L3AgentExtension):
# Get the list of in-namespace ports from which to delete the firewall
# group.
ports_for_fwg = self._get_firewall_group_ports(context, firewall_group,
to_delete=True, require_new_plugin=True)
del_fwg_ports = self._get_firewall_group_ports(
context, firewall_group, to_delete=True, require_new_plugin=True)
add_fwg_ports = self._get_firewall_group_ports(context, firewall_group)
port_ids = (firewall_group.get('del-port-ids') +
firewall_group.get('add-port-ids'))
if port_ids and not (del_fwg_ports or add_fwg_ports):
LOG.debug("All ports are not router port."
"No need to update firewall driver.")
return
# Remove firewall group from ports if requested.
if ports_for_fwg:
fw_ports = [p for ri_ports in ports_for_fwg for p in ri_ports[1]]
if del_fwg_ports:
fw_ports = [p for ri_port in del_fwg_ports for p in ri_port[1]]
LOG.debug("Update (delete) firewall group %(fwg_id)s on ports: "
"%(ports)s",
{'fwg_id': firewall_group['id'],
@ -426,7 +435,7 @@ class FWaaSL3AgentExtension(l3_extension.L3AgentExtension):
# Call the driver.
try:
self.fwaas_driver.delete_firewall_group(self.conf.agent_mode,
ports_for_fwg,
del_fwg_ports,
firewall_group)
except fw_ext.FirewallInternalDriverError:
msg = ("FWaaS driver error in update_firewall_group "
@ -437,11 +446,9 @@ class FWaaSL3AgentExtension(l3_extension.L3AgentExtension):
# Handle the add router and/or rule, policy, firewall group attribute
# updates.
if status not in (nl_constants.ERROR, nl_constants.INACTIVE):
ports_for_fwg = self._get_firewall_group_ports(context,
firewall_group)
if ports_for_fwg:
fw_ports = [p for ri_ports in ports_for_fwg
for p in ri_ports[1]]
if add_fwg_ports:
fw_ports = [p for ri_port in add_fwg_ports
for p in ri_port[1]]
LOG.debug("Update (create) firewall group %(fwg_id)s on "
"ports: %(ports)s",
{'fwg_id': firewall_group['id'],
@ -457,7 +464,7 @@ class FWaaSL3AgentExtension(l3_extension.L3AgentExtension):
# Call the driver.
try:
self.fwaas_driver.update_firewall_group(
self.conf.agent_mode, ports_for_fwg,
self.conf.agent_mode, add_fwg_ports,
firewall_group)
except fw_ext.FirewallInternalDriverError:
msg = ("FWaaS driver error in update_firewall_group "

View File

@ -22,7 +22,7 @@ import six
class FirewallL2DriverBase(object):
"""Abstract firewall L2 driver base"""
def __init__(self, integration_bridge):
def __init__(self, integration_bridge, sg_enabled=False):
pass
def filter_defer_apply_on(self):

View File

@ -16,6 +16,7 @@ from neutron.common import rpc as n_rpc
from neutron.db import servicetype_db as st_db
from neutron.services import provider_configuration as provider_conf
from neutron_lib.api.definitions import firewall_v2
from neutron_lib.api.definitions import portbindings as pb_def
from neutron_lib import constants as nl_constants
from neutron_lib import context as neutron_context
from neutron_lib.exceptions import firewall_v2 as f_exc
@ -28,7 +29,6 @@ import oslo_messaging
from neutron_fwaas.common import fwaas_constants
from neutron_fwaas.db.firewall.v2 import firewall_db_v2
LOG = logging.getLogger(__name__)
@ -142,6 +142,13 @@ class FirewallCallbacks(object):
fwg_project_list = list(set(fwg['tenant_id'] for fwg in fwg_list))
return fwg_project_list
def get_firewall_group_for_port(self, context, **kwargs):
"""Get firewall_group is associated with a port."""
LOG.debug("get_firewall_group_for_port() called")
ctx = context.elevated()
return self.plugin.get_firewall_group_for_port(
ctx, kwargs.get('port_id'))
class FirewallPluginV2(
firewall_db_v2.Firewall_db_mixin_v2):
@ -190,6 +197,8 @@ class FirewallPluginV2(
fwg_with_rules['add-port-ids'] = self._get_ports_in_firewall_group(
context, fwg_id)
fwg_with_rules['del-port-ids'] = []
fwg_with_rules['port_details'] = self._get_fwg_port_details(
context, fwg_with_rules['add-port-ids'])
self.agent_rpc.update_firewall_group(context, fwg_with_rules)
def _rpc_update_firewall_policy(self, context, firewall_policy_id):
@ -226,12 +235,14 @@ 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 f_exc.FirewallGroupPortInvalid(port_id=port_id)
if port_db['tenant_id'] != tenant_id:
raise f_exc.FirewallGroupPortInvalidProject(
port_id=port_id, project_id=port_db['tenant_id'])
return
device_owner = port_db.get('device_owner', '')
if (device_owner not in [nl_constants.DEVICE_OWNER_ROUTER_INTF]
and not device_owner.startswith(
nl_constants.DEVICE_OWNER_COMPUTE_PREFIX)):
raise f_exc.FirewallGroupPortInvalid(port_id=port_id)
def _check_no_need_pending(self, context, fwg_id, fwg_body):
fwg_db = self._get_firewall_group(context, fwg_id)
@ -245,6 +256,34 @@ class FirewallPluginV2(
return True
return False
def _get_fwg_port_details(self, context, fwg_ports):
"""Returns a dictionary list of port details. """
port_details = {}
for port_id in fwg_ports:
port_db = self._core_plugin.get_port(context, port_id)
# Add more parameters below based on requirement.
device_owner = port_db['device_owner']
port_details[port_id] = {
'device_owner': device_owner,
'device': port_db['id'],
'network_id': port_db['network_id'],
'fixed_ips': port_db['fixed_ips'],
'allowed_address_pairs':
port_db.get('allowed_address_pairs', []),
'port_security_enabled':
port_db.get('port_security_enabled', True),
'id': port_db['id']
}
if device_owner.startswith(
nl_constants.DEVICE_OWNER_COMPUTE_PREFIX):
port_details[port_id].update(
{'host': port_db[pb_def.HOST_ID]})
return port_details
def get_project_id_from_port_id(self, context, port_id):
"""Returns an ID of project for specified port_id. """
return self._core_plugin.get_port(context, port_id)['project_id']
def create_firewall_group(self, context, firewall_group):
LOG.debug("create_firewall_group() called")
fwgrp = firewall_group['firewall_group']
@ -280,6 +319,8 @@ class FirewallPluginV2(
fwg_with_rules['add-port-ids'] = fwg_ports
fwg_with_rules['del-ports-ids'] = []
fwg_with_rules['port_details'] = self._get_fwg_port_details(
context, fwg_ports)
self.agent_rpc.create_firewall_group(context, fwg_with_rules)
@ -337,6 +378,10 @@ class FirewallPluginV2(
fwg_with_rules['add-port-ids'],
fwg_with_rules['del-port-ids'])
fwg_with_rules['port_details'] = self._get_fwg_port_details(
context, fwg_with_rules['del-port-ids'])
fwg_with_rules['port_details'].update(self._get_fwg_port_details(
context, fwg_with_rules['add-port-ids']))
self.agent_rpc.update_firewall_group(context, fwg_with_rules)
return fwg
@ -346,12 +391,18 @@ class FirewallPluginV2(
def delete_firewall_group(self, context, id):
LOG.debug("delete_firewall_group() called on firewall_group %s", id)
fw_with_rules = (
fwg_db = self._get_firewall_group(context, id)
if fwg_db['status'] == nl_constants.ACTIVE:
raise f_exc.FirewallGroupInUse(firewall_id=id)
fwg_with_rules = (
self._make_firewall_group_dict_with_rules(context, id))
fw_with_rules['del-port-ids'] = self._get_ports_in_firewall_group(
fwg_with_rules['del-port-ids'] = self._get_ports_in_firewall_group(
context, id)
fw_with_rules['add-port-ids'] = []
if not fw_with_rules['del-port-ids']:
fwg_with_rules['add-port-ids'] = []
if not fwg_with_rules['del-port-ids']:
# no ports, no need to talk to the agent
self.delete_db_firewall_group_object(context, id)
else:
@ -359,9 +410,11 @@ class FirewallPluginV2(
nl_constants.PENDING_DELETE}}
super(FirewallPluginV2, self).update_firewall_group(
context, id, status)
# Reflect state change in fw_with_rules
fw_with_rules['status'] = status['firewall_group']['status']
self.agent_rpc.delete_firewall_group(context, fw_with_rules)
# Reflect state change in fwg_with_rules
fwg_with_rules['status'] = status['firewall_group']['status']
fwg_with_rules['port_details'] = self._get_fwg_port_details(
context, fwg_with_rules['del-port-ids'])
self.agent_rpc.delete_firewall_group(context, fwg_with_rules)
def update_firewall_policy(self, context, id, firewall_policy):
LOG.debug("update_firewall_policy() called")
@ -381,18 +434,16 @@ class FirewallPluginV2(
self._rpc_update_firewall_policy(context, fwp_id)
return fwr
def insert_rule(self, context, policy_id, rule_info):
LOG.debug("insert_rule() called for policy %s ", policy_id)
self._ensure_update_firewall_policy(context, policy_id)
fwp = super(FirewallPluginV2, self).insert_rule(context, policy_id,
rule_info)
self._rpc_update_firewall_policy(context, policy_id)
def insert_rule(self, context, id, rule_info):
LOG.debug("insert_rule() called")
self._ensure_update_firewall_policy(context, id)
fwp = super(FirewallPluginV2, self).insert_rule(context, id, rule_info)
self._rpc_update_firewall_policy(context, id)
return fwp
def remove_rule(self, context, policy_id, rule_info):
LOG.debug("remove_rule() called for policy %s ", policy_id)
self._ensure_update_firewall_policy(context, policy_id)
fwp = super(FirewallPluginV2, self).remove_rule(context, policy_id,
rule_info)
self._rpc_update_firewall_policy(context, policy_id)
def remove_rule(self, context, id, rule_info):
LOG.debug("remove_rule() called")
self._ensure_update_firewall_policy(context, id)
fwp = super(FirewallPluginV2, self).remove_rule(context, id, rule_info)
self._rpc_update_firewall_policy(context, id)
return fwp

View File

@ -272,7 +272,6 @@ class FWaaSv2ExtensionTestJSON(v2_base.BaseFWaaSTest):
ports=[intf_1['port_id'], intf_2['port_id']])
created_firewall_group = body['firewall_group']
fwg_id = created_firewall_group['id']
self.addCleanup(self._try_delete_firewall_group, fwg_id)
# Wait for the firewall resource to become ready
self._wait_until_ready(fwg_id)
@ -299,6 +298,8 @@ class FWaaSv2ExtensionTestJSON(v2_base.BaseFWaaSTest):
m['ingress_firewall_policy_id'],
m['egress_firewall_policy_id']) for m in fwgs])
# Disassociate all port with this firewall group
self.firewall_groups_client.update_firewall_group(fwg_id, ports=[])
# Delete firewall_group
self.firewall_groups_client.delete_firewall_group(fwg_id)

View File

@ -294,3 +294,7 @@ class TestFWaaS_v2(base.FWaaSScenarioTest_V2):
topology['private_key2'],
address_list=[topology['server_fixed_ip_1']],
should_connect=False)
# Disassociate ports of this firewall group for cleanup resources
self.firewall_groups_client.update_firewall_group(
fw_group['id'], ports=[])

View File

@ -0,0 +1,153 @@
# Copyright 2017 FUJITSU LIMITED
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import copy
import mock
from neutron_lib import constants as nl_consts
from oslo_utils import uuidutils
TENANT_UUID = uuidutils.generate_uuid()
TENANT_ID = TENANT_UUID
PROJECT_ID = TENANT_UUID
NETWORK_ID = uuidutils.generate_uuid()
SUBNET_ID = uuidutils.generate_uuid()
DEVICE_ID = uuidutils.generate_uuid()
PORT1 = uuidutils.generate_uuid()
PORT2 = uuidutils.generate_uuid()
PORT3 = uuidutils.generate_uuid()
PORT4 = uuidutils.generate_uuid()
HOST = 'fake_host'
class FakeFWaaSL2Agent(object):
def __init__(self):
super(FakeFWaaSL2Agent, self).__init__()
def create(self, resource, attrs=None, minimal=False):
"""Create a fake fwaas v2 resources
:param resource: A dictionary with all attributes
:type resource: string
:param attrs: A dictionary of each attribute you need to modify
:type attrs: dictionary
:param minimal: True if minimal port_detail is necessary
otherwise False
:type minimal: boolean
:return:
A OrderedDict faking the fwaas v2 resource
"""
target = getattr(self, "_" + resource)
return copy.deepcopy(target(attrs=attrs, minimal=minimal))
def _fwg(self, **kwargs):
fwg = {
'id': uuidutils.generate_uuid(),
'name': 'my-group-' + uuidutils.generate_uuid(),
'ingress_firewall_policy_id': uuidutils.generate_uuid(),
'egress_firewall_policy_id': uuidutils.generate_uuid(),
'description': 'my firewall group',
'status': nl_consts.PENDING_CREATE,
'ports': [PORT3, PORT4],
'admin_state_up': True,
'shared': False,
'tenant_id': TENANT_ID,
'project_id': PROJECT_ID
}
attrs = kwargs.get('attrs', None)
if attrs:
fwg.update(attrs)
return fwg
def _fwg_with_rule(self, **kwargs):
fwg_with_rule = self.create('fwg', attrs={'ports': [PORT1, PORT2]})
rules = {
'ingress_rule_list': [mock.Mock()],
'egress_rule_list': [mock.Mock()],
'add-port-ids': [PORT1],
'del-port-ids': [PORT2],
'port_details': {
PORT1: {
'device': uuidutils.generate_uuid(),
'device_owner': 'compute:nova',
'host': HOST,
'network_id': NETWORK_ID,
'fixed_ips': [
{'subnet_id': SUBNET_ID, 'ip_address': '172.24.4.5'}],
'allowed_address_pairs': [],
'port_security_enabled': True,
'id': PORT1
},
PORT2: {
'device': uuidutils.generate_uuid(),
'device_owner': 'compute:nova',
'host': HOST,
'network_id': NETWORK_ID,
'fixed_ips': [
{'subnet_id': SUBNET_ID, 'ip_address': '172.24.4.6'}],
'allowed_address_pairs': [],
'port_security_enabled': True,
'id': PORT2
}
},
}
fwg_with_rule.update(rules)
if kwargs.get('minimal', None):
fwg_with_rule.update({'ports': []})
fwg_with_rule.update({'add-port-ids': []})
fwg_with_rule.update({'del-port-ids': []})
fwg_with_rule.update({'port_details': {}})
attrs = kwargs.get('attrs', None)
if attrs:
fwg_with_rule.update(attrs)
return fwg_with_rule
def _port(self, **kwargs):
if kwargs.get('minimal', None):
return {'port_id': uuidutils.generate_uuid()}
port_detail = {
'profile': {},
'network_qos_policy_id': None,
'qos_policy_id': None,
'allowed_address_pairs': [],
'admin_state_up': True,
'network_id': NETWORK_ID,
'segmentation_id': None,
'fixed_ips': [
{'subnet_id': SUBNET_ID, 'ip_address': '172.24.4.5'}],
'vif_port': mock.Mock(),
'device_owner': 'compute:node',
'physical_network': 'physnet',
'mac_address': 'fa:16:3e:8a:80:2b',
'device': DEVICE_ID,
'port_security_enabled': True,
'port_id': uuidutils.generate_uuid(),
'network_type': 'flat',
'security_groups': []
}
attrs = kwargs.get('attrs', None)
if attrs:
port_detail.update(attrs)
return port_detail

View File

@ -0,0 +1,732 @@
# Copyright 2017 Cisco Systems
#
# 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 copy
import mock
from neutron_lib import constants as nl_consts
from neutron_lib import context
from neutron_lib.exceptions import firewall_v2 as f_exc
from oslo_config import cfg
from neutron_fwaas.common import fwaas_constants as consts
from neutron_fwaas.services.firewall.agents.l2 import fwaas_v2
from neutron_fwaas.tests import base
from neutron_fwaas.tests.unit.services.firewall.agents.l2 import fake_data
class TestFWaasV2AgentExtensionBase(base.BaseTestCase):
def setUp(self):
super(TestFWaasV2AgentExtensionBase, self).setUp()
self.fake = fake_data.FakeFWaaSL2Agent()
self.port = self.fake.create('port')
self.port_minimal = self.fake.create('port', minimal=True)
self.fwg = self.fake.create('fwg')
self.fwg_with_rule = self.fake.create('fwg_with_rule')
self.port_id = self.port['port_id']
self.fwg_id = self.fwg['id']
self.host = fake_data.HOST
self.ctx = context.get_admin_context()
self.l2 = fwaas_v2.FWaaSV2AgentExtension()
self.l2.consume_api(mock.Mock())
self.driver = mock.patch(
'neutron.manager.NeutronManager.load_class_for_provider').start()
self.l2.initialize(None, 'ovs')
self.l2.vlan_manager = mock.Mock()
self.conf = cfg.ConfigOpts()
self.l2.fwg_map = mock.Mock()
self.l2.conf.host = self.host
self.rpc = self.l2.plugin_rpc
class TestFWaasV2AgentExtension(TestFWaasV2AgentExtensionBase):
def setUp(self):
super(TestFWaasV2AgentExtension, self).setUp()
cfg.CONF.set_override('firewall_l2_driver', 'ovs', group='fwaas')
def test_initialize(self):
with mock.patch('neutron.common.rpc.create_connection') as conn:
self.l2.initialize(None, 'ovs')
self.driver.assert_called_with('neutron.agent.l2.firewall_drivers',
'ovs')
conn.assert_called_with()
self.l2.conn.create_consumer.assert_called_with(
consts.FW_AGENT, [self.l2], fanout=False)
self.l2.conn.consume_in_threads.assert_called_with()
class TestHandlePort(TestFWaasV2AgentExtensionBase):
def setUp(self):
super(TestHandlePort, self).setUp()
self.rpc.get_firewall_group_for_port = mock.Mock(
return_value=self.fwg)
self.l2._compute_status = mock.Mock(return_value=nl_consts.ACTIVE)
self.l2._apply_fwg_rules = mock.Mock(return_value=True)
self.l2._send_fwg_status = mock.Mock()
self.ctx = context.get_admin_context()
def test_normal(self):
self.l2.fwg_map.get_port_fwg.return_value = None
self.l2.handle_port(self.ctx, self.port)
self.rpc.get_firewall_group_for_port.assert_called_once_with(
self.ctx, self.port['port_id'])
self.l2._apply_fwg_rules.assert_called_once_with(self.fwg, [self.port])
self.l2._compute_status.assert_called_once_with(
self.fwg, True, event=consts.HANDLE_PORT)
self.l2.fwg_map.set_port_fwg.assert_called_once_with(self.port,
self.fwg)
self.l2._send_fwg_status.assert_called_once_with(
self.ctx, fwg_id=self.fwg['id'],
status=nl_consts.ACTIVE, host=self.l2.conf.host)
def test_non_layer2_port(self):
self.port['device_owner'] = 'network:router_gateway'
self.l2.handle_port(self.ctx, self.port)
self.rpc.get_firewall_group_for_port.assert_not_called()
self.l2._apply_fwg_rules.assert_not_called()
self.l2._compute_status.assert_not_called()
self.l2.fwg_map.set_port_fwg.assert_not_called()
self.l2._send_fwg_status.assert_not_called()
def test_no_fwg_is_asossicate_to_port(self):
self.l2.fwg_map.get_port_fwg.return_value = None
self.rpc.get_firewall_group_for_port.return_value = None
self.l2.handle_port(self.ctx, self.port)
self.rpc.get_firewall_group_for_port.assert_called_once_with(
self.ctx, self.port['port_id'])
self.l2._apply_fwg_rules.assert_not_called()
self.l2._compute_status.assert_not_called()
self.l2.fwg_map.set_port_fwg.assert_not_called()
self.l2._send_fwg_status.assert_not_called()
def test_port_already_apply_fwg(self):
self.l2.fwg_map.get_port_fwg.return_value = self.fwg
self.l2.handle_port(self.ctx, self.port)
self.rpc.get_firewall_group_for_port.assert_not_called()
self.l2._apply_fwg_rules.assert_not_called()
self.l2._compute_status.assert_not_called()
self.l2.fwg_map.set_port_fwg.assert_not_called()
self.l2._send_fwg_status.assert_not_called()
class TestDeletePort(TestFWaasV2AgentExtensionBase):
def setUp(self):
super(TestDeletePort, self).setUp()
self.l2._compute_status = mock.Mock(return_value=nl_consts.ACTIVE)
self.l2._apply_fwg_rules = mock.Mock(return_value=True)
self.l2._send_fwg_status = mock.Mock()
self.l2.fwg_map.get_port_fwg = mock.Mock(return_value=self.fwg)
self.l2.fwg_map.set_fwg = mock.Mock()
self.l2.fwg_map.get_port = mock.Mock(return_value=self.port)
self.l2.fwg_map.remove_port = mock.Mock()
def test_include_vif_port_attribute(self):
self.port_minimal.update({'vif_port': None})
self.l2.fwg_map.get_port_fwg.return_value = None
self.l2.delete_port(self.ctx, self.port_minimal)
self.l2.fwg_map.get_port_fwg.assert_not_called()
self.l2._apply_fwg_rules.assert_not_called()
def test_port_belongs_to_fwg(self):
expected_ports = self.fwg['ports']
self.fwg['ports'].append(self.port['port_id'])
self.l2.delete_port(self.ctx, self.port_minimal)
self.l2.fwg_map.get_port_fwg.assert_called_once_with(self.port)
self.l2._apply_fwg_rules.assert_called_once_with(
self.fwg, [self.port], event=consts.DELETE_FWG)
# 'port_id' has been removed from 'ports'
self.assertEqual(expected_ports, self.fwg['ports'])
self.l2.fwg_map.set_fwg.assert_called_once_with(self.fwg)
def test_port_belongs_to_no_fwg(self):
expected_ports = self.fwg['ports']
self.l2.delete_port(self.ctx, self.port_minimal)
self.l2.fwg_map.get_port_fwg.assert_called_once_with(self.port)
self.l2._apply_fwg_rules.assert_called_once_with(
self.fwg, [self.port], event=consts.DELETE_FWG)
# 'ports' not changed during delete_port()
self.assertEqual(expected_ports, self.fwg['ports'])
self.l2.fwg_map.set_fwg.assert_called_once_with(self.fwg)
def test_non_layer2_port(self):
self.port['device_owner'] = 'network:router_gateway'
self.l2.delete_port(self.ctx, self.port_minimal)
self.l2.fwg_map.get_port_fwg.assert_not_called()
def test_cannot_get_fwg_from_port(self):
self.l2.fwg_map.get_port_fwg.return_value = None
self.l2.delete_port(self.ctx, self.port_minimal)
self.l2.fwg_map.get_port_fwg.assert_called_once_with(self.port)
self.l2._apply_fwg_rules.assert_not_called()
class TestCreateFirewallGroup(TestFWaasV2AgentExtensionBase):
def setUp(self):
super(TestCreateFirewallGroup, self).setUp()
self.l2._apply_fwg_rules = mock.Mock(return_value=True)
self.l2._compute_status = mock.Mock(return_value='ACTIVE')
self.l2._send_fwg_status = mock.Mock()
def test_create_event_is_create(self):
fwg = self.fwg_with_rule
fwg['ports'] = [fake_data.PORT1]
ports = [fwg['port_details'][fake_data.PORT1]]
self.l2._create_firewall_group(
self.ctx, fwg, self.host, event=consts.CREATE_FWG)
self.l2._apply_fwg_rules.assert_called_once_with(
fwg, ports, consts.CREATE_FWG)
self.l2._compute_status.assert_called_once_with(
fwg, True, consts.CREATE_FWG)
def test_create_event_is_not_create(self):
fwg = self.fwg_with_rule
fwg['ports'] = [fake_data.PORT1]
ports = [fwg['port_details'][fake_data.PORT1]]
self.l2._create_firewall_group(
self.ctx, fwg, self.host, event=consts.UPDATE_FWG)
self.l2._apply_fwg_rules.assert_called_once_with(
fwg, ports, consts.UPDATE_FWG)
def test_create_with_port(self):
fwg = self.fwg_with_rule
ports = [fwg['port_details'][fake_data.PORT1]]
self.l2.create_firewall_group(self.ctx, fwg, self.host)
self.l2._apply_fwg_rules.assert_called_once_with(
fwg, ports, consts.CREATE_FWG)
for idx, args in enumerate(self.l2._compute_status.call_args_list):
self.assertEqual(fwg, args[0][0])
self.assertEqual(True, args[0][1])
self.assertEqual(consts.CREATE_FWG, args[0][2])
for idx, args in enumerate(self.l2._send_fwg_status.call_args_list):
self.assertEqual(self.ctx, args[0][0])
self.assertEqual(fwg['id'], args[0][1])
self.assertEqual('ACTIVE', args[0][2])
self.assertEqual(self.host, args[0][3])
def test_create_with_no_ports(self):
self.fwg_with_rule['add-port-ids'] = []
self.assertIsNone(self.l2.create_firewall_group(
self.ctx, self.fwg_with_rule, self.host))
self.l2._apply_fwg_rules.assert_not_called()
self.l2.fwg_map.set_port_fwg.assert_not_called()
self.l2._send_fwg_status.assert_called_once_with(
self.ctx, self.fwg_with_rule['id'], 'INACTIVE', self.host)
def test_create_with_invalid_host(self):
self.fwg_with_rule['port_details'][fake_data.PORT1]['host'] = 'invalid'
self.l2.create_firewall_group(self.ctx, self.fwg_with_rule, self.host)
self.l2._apply_fwg_rules.assert_not_called()
self.l2._send_fwg_status.assert_called_once_with(
self.ctx, self.fwg_with_rule['id'], 'INACTIVE', self.host)
def test_illegal_create_with_no_l2_ports(self):
fwg = {
'name': 'non-default',
'id': self.fwg_id,
'ports': [],
'add-port-ids': [self.port_id],
'admin_state_up': True,
'port_details': {
self.port_id: {
'device_owner': 'network:router_interface'
}
}
}
self.l2.create_firewall_group(self.ctx, fwg, self.host)
self.l2._apply_fwg_rules.assert_not_called()
self.l2.fwg_map.set_port_fwg.assert_not_called()
self.l2._send_fwg_status.assert_called_once_with(
self.ctx, fwg['id'], 'INACTIVE', self.host)
class TestDeleteFirewallGroup(TestFWaasV2AgentExtensionBase):
def setUp(self):
super(TestDeleteFirewallGroup, self).setUp()
self.l2._apply_fwg_rules = mock.Mock(return_value=True)
self.l2._compute_status = mock.Mock(return_value='ACTIVE')
self.l2._send_fwg_status = mock.Mock()
self.rpc.firewall_group_deleted = mock.Mock()
def test_delete_with_port(self):
fwg = self.fwg_with_rule
ports = [fwg['port_details'][fake_data.PORT2]]
self.assertIsNone(self.l2.delete_firewall_group(
self.ctx, self.fwg_with_rule, self.host))
self.l2._apply_fwg_rules.assert_called_once_with(
fwg, ports, event=consts.DELETE_FWG)
self.l2.fwg_map.remove_fwg.assert_called_once_with(fwg)
for idx, args in enumerate(self.l2._compute_status.call_args_list):
self.assertEqual(fwg, args[0][0])
self.assertEqual(True, args[0][2])
self.assertEqual({'event': consts.CREATE_FWG}, args[1])
for idx, args in enumerate(self.l2._send_fwg_status.call_args_list):
self.assertEqual(self.ctx, args[0][0])
self.assertEqual(fwg['id'], args[0][1])
self.assertEqual('ACTIVE', args[0][2])
self.assertEqual(self.host, args[0][3])
def test_delete_with_no_ports(self):
self.fwg_with_rule['del-port-ids'] = []
self.l2.delete_firewall_group(self.ctx, self.fwg_with_rule, self.host)
self.l2._apply_fwg_rules.assert_not_called()
def test_delete_with_no_l2_ports(self):
self.fwg_with_rule['port_details'][fake_data.PORT2][
'device_owner'] = 'network:router_interface'
self.l2.delete_firewall_group(self.ctx, self.fwg_with_rule, self.host)
self.l2._apply_fwg_rules.assert_not_called()
def test_delete_with_exception(self):
self.l2._delete_firewall_group = mock.Mock(side_effect=Exception)
self.assertIsNone(self.l2.delete_firewall_group(
self.ctx, self.fwg_with_rule, self.host))
def test_delete_event_is_update(self):
self.l2._delete_firewall_group(
self.ctx, self.fwg_with_rule, self.host, event=consts.UPDATE_FWG)
self.l2.fwg_map.remove_fwg.assert_not_called()
self.rpc.firewall_group_deleted.assert_not_called()
self.l2._compute_status.assert_called_once_with(
self.fwg_with_rule, True, consts.UPDATE_FWG)
self.l2._send_fwg_status.assert_called_once_with(
self.ctx, self.fwg_with_rule['id'], 'ACTIVE', self.host)
class TestUpdateFirewallGroup(TestFWaasV2AgentExtensionBase):
def setUp(self):
super(TestUpdateFirewallGroup, self).setUp()
self.l2._delete_firewall_group = mock.Mock()
self.l2._create_firewall_group = mock.Mock()
self.l2._send_fwg_status = mock.Mock()
def test_update(self):
self.assertIsNone(self.l2.update_firewall_group(
self.ctx, mock.ANY, self.host))
self.l2._delete_firewall_group.assert_called_once_with(
self.ctx, mock.ANY, self.host, event=consts.UPDATE_FWG)
self.l2._create_firewall_group.assert_called_once_with(
self.ctx, mock.ANY, self.host, event=consts.UPDATE_FWG)
def test_update_raised_in_delete_firewall_group(self):
self.l2._delete_firewall_group.side_effect = Exception
fwg = self.fwg_with_rule
self.assertIsNone(self.l2.update_firewall_group(
self.ctx, fwg, self.host))
self.l2._send_fwg_status.assert_called_once_with(
self.ctx, fwg['id'], status='ERROR', host=self.host)
def test_update_raised_in_create_firewall_group(self):
self.l2._create_firewall_group.side_effect = Exception
fwg = self.fwg_with_rule
self.assertIsNone(self.l2.update_firewall_group(
self.ctx, fwg, self.host))
self.l2._send_fwg_status.assert_called_once_with(
self.ctx, fwg['id'], status='ERROR', host=self.host)
class TestIsPortLayer2(TestFWaasV2AgentExtensionBase):
def setUp(self):
super(TestIsPortLayer2, self).setUp()
def test_vm_port(self):
self.assertTrue(self.l2._is_port_layer2(self.port))
def test_not_vm_port(self):
for device_owner in [nl_consts.DEVICE_OWNER_ROUTER_INTF,
nl_consts.DEVICE_OWNER_ROUTER_GW,
nl_consts.DEVICE_OWNER_DHCP,
nl_consts.DEVICE_OWNER_DVR_INTERFACE,
nl_consts.DEVICE_OWNER_AGENT_GW,
nl_consts.DEVICE_OWNER_ROUTER_SNAT,
nl_consts.DEVICE_OWNER_LOADBALANCER,
nl_consts.DEVICE_OWNER_LOADBALANCERV2,
'unknown device_owner',
'']:
self.port['device_owner'] = device_owner
self.assertFalse(self.l2._is_port_layer2(self.port))
def test_illegal_no_device_owner(self):
del self.port['device_owner']
self.assertFalse(self.l2._is_port_layer2(self.port))
class TestComputeStatus(TestFWaasV2AgentExtensionBase):
def setUp(self):
super(TestComputeStatus, self).setUp()
self.ports = list(self.fwg_with_rule['port_details'].values())
def test_normal(self):
result = True
fwg = self.fwg_with_rule
self.assertEqual('ACTIVE', self.l2._compute_status(fwg, result))
def test_event_is_delete(self):
result = True
fwg = self.fwg_with_rule
self.assertIsNone(self.l2._compute_status(
fwg, result, consts.DELETE_FWG))
def test_event_is_update(self):
result = True
fwg = self.fwg_with_rule
self.assertEqual('ACTIVE', self.l2._compute_status(
fwg, result, consts.UPDATE_FWG))
def test_event_is_update_and_has_last_port(self):
result = True
fwg = self.fake.create('fwg_with_rule', attrs={'last-port': False})
self.assertEqual('ACTIVE', self.l2._compute_status(
fwg, result, consts.UPDATE_FWG))
fwg = self.fake.create('fwg_with_rule', attrs={'last-port': True})
self.assertEqual('INACTIVE', self.l2._compute_status(
fwg, result, consts.UPDATE_FWG))
def test_event_is_update_and_has_no_last_port_but_has_ports(self):
result = True
fwg = self.fwg_with_rule
self.assertEqual('ACTIVE', self.l2._compute_status(
fwg, result, consts.UPDATE_FWG))
def test_event_is_update_and_has_no_last_port_and_ports(self):
result = True
fwg = self.fwg_with_rule
fwg['ports'] = []
self.assertEqual('INACTIVE', self.l2._compute_status(
fwg, result, consts.UPDATE_FWG))
def test_event_is_create(self):
result = True
fwg = self.fwg_with_rule
self.assertEqual('ACTIVE', self.l2._compute_status(
fwg, result, consts.CREATE_FWG))
def test_event_is_create_and_no_fwg_ports(self):
result = True
fwg = self.fwg_with_rule
fwg['ports'] = []
self.assertEqual('INACTIVE', self.l2._compute_status(
fwg, result, consts.CREATE_FWG))
def test_event_is_handle_port(self):
result = True
fwg = self.fwg_with_rule
self.assertEqual('ACTIVE', self.l2._compute_status(
fwg, result, consts.HANDLE_PORT))
def test_event_is_delete_port(self):
result = True
fwg = self.fwg_with_rule
self.assertEqual('ACTIVE', self.l2._compute_status(
fwg, result, consts.DELETE_PORT))
def test_event_is_delete_port_and_no_fwg_ports(self):
result = True
fwg = self.fwg_with_rule
fwg['ports'] = []
self.assertEqual('INACTIVE', self.l2._compute_status(
fwg, result, consts.DELETE_PORT))
def test_driver_result_is_false(self):
result = False
fwg = self.fwg_with_rule
self.assertEqual('ERROR', self.l2._compute_status(
fwg, result))
def test_admin_state_up_is_false(self):
result = True
self.fwg_with_rule['admin_state_up'] = False
self.assertEqual('DOWN', self.l2._compute_status(
self.fwg_with_rule, self.ports, result))
def test_active_inactive_patterns(self):
result = True
fwg = self.fwg_with_rule
# Case1: ingress/egress_firewall_policy_id
# Case2: ports --> already tested at above cases
expect_and_attrs = [
('INACTIVE', ('ingress_firewall_policy_id',
'egress_firewall_policy_id')),
('ACTIVE', ('ingress_firewall_policy_id',)),
('ACTIVE', ('egress_firewall_policy_id',)),
]
for attr in expect_and_attrs:
fwg = self.fake.create('fwg_with_rule')
expect = attr[0]
for p in attr[1]:
fwg[p] = None
self.assertEqual(expect, self.l2._compute_status(fwg, result))
class TestApplyFwgRules(TestFWaasV2AgentExtensionBase):
def setUp(self):
super(TestApplyFwgRules, self).setUp()
class DummyVlan(object):
def __init__(self, vlan=None):
self.vlan = vlan
self.l2.vlan_manager.get.return_value = DummyVlan(vlan='999')
def test_event_is_create(self):
fwg_ports = [self.fwg_with_rule['port_details'][fake_data.PORT1]]
driver_ports = copy.deepcopy(fwg_ports)
driver_ports[0].update({'lvlan': 999})
self.assertTrue(self.l2._apply_fwg_rules(
self.fwg_with_rule, fwg_ports, event=consts.CREATE_FWG))
self.l2.driver.create_firewall_group.assert_called_once_with(
driver_ports, self.fwg_with_rule)
self.l2.driver.delete_firewall_group.assert_not_called()
self.l2.driver.update_firewall_group.assert_not_called()
def test_event_is_update(self):
fwg_ports = [self.fwg_with_rule['port_details'][fake_data.PORT1]]
driver_ports = copy.deepcopy(fwg_ports)
driver_ports[0].update({'lvlan': 999})
self.assertTrue(self.l2._apply_fwg_rules(
self.fwg_with_rule, fwg_ports, event=consts.UPDATE_FWG))
self.l2.driver.update_firewall_group.assert_called_once_with(
driver_ports, self.fwg_with_rule)
def test_event_is_delete(self):
fwg_ports = [self.fwg_with_rule['port_details'][fake_data.PORT1]]
driver_ports = copy.deepcopy(fwg_ports)
driver_ports[0].update({'lvlan': 999})
self.assertTrue(self.l2._apply_fwg_rules(
self.fwg_with_rule, fwg_ports, event=consts.DELETE_FWG))
self.l2.driver.delete_firewall_group.assert_called_once_with(
fwg_ports, self.fwg_with_rule)
def test_raised_in_driver(self):
self.l2.driver.delete_firewall_group.side_effect = \
f_exc.FirewallInternalDriverError(driver='ovs firewall')
fwg_ports = [self.fwg_with_rule['port_details'][fake_data.PORT1]]
driver_ports = copy.deepcopy(fwg_ports)
driver_ports[0].update({'lvlan': 999})
self.assertFalse(self.l2._apply_fwg_rules(
self.fwg_with_rule, fwg_ports, event=consts.DELETE_FWG))
self.l2.driver.delete_firewall_group.assert_called_once_with(
fwg_ports, self.fwg_with_rule)
class TestSendFwgStatus(TestFWaasV2AgentExtensionBase):
def setUp(self):
super(TestSendFwgStatus, self).setUp()
self.rpc.set_firewall_group_status = mock.Mock()
def test_success(self):
self.assertIsNone(self.l2._send_fwg_status(
self.ctx, self.fwg_id, 'ACTIVE', self.host))
def test_failure(self):
self.rpc.set_firewall_group_status.side_effect = Exception
self.assertIsNone(self.l2._send_fwg_status(
self.ctx, self.fwg_id, 'ACTIVE', self.host))
class TestAddLocalVlanToPorts(TestFWaasV2AgentExtensionBase):
def setUp(self):
super(TestAddLocalVlanToPorts, self).setUp()
class DummyVlan(object):
def __init__(self, vlan=None):
self.vlan = vlan
self.l2.vlan_manager.get.return_value = DummyVlan(vlan='999')
self.port_with_detail = {
'port_id': fake_data.PORT1,
'id': fake_data.PORT1,
'network_id': fake_data.NETWORK_ID,
'port_details': {
fake_data.PORT1: {
'device': 'c12e5c1e-d68e-45bd-a2d3-1f2f32604e41',
'device_owner': 'compute:nova',
'host': self.host,
'network_id': fake_data.NETWORK_ID,
'fixed_ips': [
{'subnet_id': fake_data.SUBNET_ID,
'ip_address': '172.24.4.5'}],
'allowed_address_pairs': [],
'port_security_enabled': True,
'id': fake_data.PORT1
}
}
}
def test_port_has_detail_and_port_id(self):
del self.port_with_detail['id']
expect = [copy.deepcopy(self.port_with_detail)]
expect[0].update({'lvlan': 999})
actual = self.l2._add_local_vlan_to_ports([self.port_with_detail])
self.l2.vlan_manager.get.assert_called_once_with(
self.port_with_detail['network_id'])
self.assertEqual(expect, actual)
def test_port_has_detail_and_id(self):
del self.port_with_detail['port_id']
expect = [copy.deepcopy(self.port_with_detail)]
expect[0].update({'lvlan': 999})
actual = self.l2._add_local_vlan_to_ports([self.port_with_detail])
self.l2.vlan_manager.get.assert_called_once_with(
self.port_with_detail['network_id'])
self.assertEqual(expect, actual)
def test_port_has_no_detail(self):
del self.port_with_detail['port_details']
expect = [copy.deepcopy(self.port_with_detail)]
expect[0].update({'lvlan': 999})
actual = self.l2._add_local_vlan_to_ports([self.port_with_detail])
self.l2.vlan_manager.get.assert_called_once_with(
self.port_with_detail['network_id'])
self.assertEqual(expect, actual)
class TestFWaaSL2PluginApi(TestFWaasV2AgentExtensionBase):
def setUp(self):
super(TestFWaaSL2PluginApi, self).setUp()
self.plugin = fwaas_v2.FWaaSL2PluginApi(
consts.FIREWALL_PLUGIN, self.host)
self.plugin.client = mock.Mock()
self.cctxt = self.plugin.client.prepare()
def test_get_firewall_group_for_port(self):
self.plugin.get_firewall_group_for_port(self.ctx, mock.ANY)
self.cctxt.call.assert_called_once_with(
self.ctx,
'get_firewall_group_for_port',
port_id=mock.ANY
)
def test_set_firewall_group_status(self):
self.plugin.set_firewall_group_status(
self.ctx, self.fwg_id, 'ACTIVE', self.host)
self.cctxt.call.assert_called_once_with(
self.ctx,
'set_firewall_group_status',
fwg_id=self.fwg_id,
status='ACTIVE',
host=self.host,
)
def test_firewall_group_deleted(self):
self.plugin.firewall_group_deleted(self.ctx, self.fwg_id, self.host)
self.cctxt.call.assert_called_once_with(
self.ctx,
'firewall_group_deleted',
fwg_id=self.fwg_id,
host=self.host,
)
class TestPortFirewallGroupMap(base.BaseTestCase):
def setUp(self):
super(TestPortFirewallGroupMap, self).setUp()
self.fake = fake_data.FakeFWaaSL2Agent()
self.map = fwaas_v2.PortFirewallGroupMap()
self.fwg = self.fake.create('fwg')
self.fwg_id = self.fwg['id']
self.port = self.fake.create('port')
self.fwg['ports'] = []
def test_set_and_get(self):
self.map.set_fwg(self.fwg)
self.assertEqual(self.fwg, self.map.get_fwg(self.fwg_id))
def test_set_and_get_port_fwg(self):
port1 = self.port
port2 = self.fake.create('port')
self.map.set_port_fwg(port1, self.fwg)
self.map.set_port_fwg(port2, self.fwg)
self.assertEqual(self.fwg, self.map.get_port_fwg(port1))
self.assertEqual(self.fwg, self.map.get_port_fwg(port2))
self.assertIsNone(self.map.get_port_fwg('unknown'))
def test_remove_port(self):
port1 = self.port
port2 = self.fake.create('port')
self.map.set_port_fwg(port1, self.fwg)
self.map.remove_port(port2)
self.map.set_port_fwg(port2, self.fwg)
self.map.remove_port(port1)
self.assertIsNone(self.map.get_port(port1))
self.assertEqual([port2['port_id']],
self.map.get_fwg(self.fwg_id)['ports'])
self.map.remove_port(port2)
self.assertIsNone(self.map.get_port(port2))
self.assertEqual([], self.map.get_fwg(self.fwg_id)['ports'])
def test_illegal_remove_port_no_relation_with_fwg(self):
port1 = self.port
port1_id = port1['port_id']
self.map.set_port_fwg(port1, self.fwg)
self.map.port_fwg[port1_id] = None
self.map.remove_port(port1)
self.assertEqual(port1, self.map.get_port(port1))
def test_remove_fwg(self):
self.map.set_fwg(self.fwg)
self.assertEqual(self.fwg, self.map.get_fwg(self.fwg_id))
self.map.remove_fwg(self.fwg)
self.assertIsNone(self.map.get_fwg(self.fwg_id))
def test_remove_fwg_non_exist(self):
self.map.remove_fwg(self.fwg)
self.assertIsNone(self.map.get_fwg(self.fwg_id))

View File

@ -59,6 +59,7 @@ def _setup_test_agent_class(service_plugins):
def __init__(self, conf):
self.event_observers = mock.Mock()
self.conf = conf
firewall_agent_api._check_required_agent_extension = mock.Mock()
super(FWaasTestAgent, self).__init__(conf)
def delete_router(self, context, data):
@ -141,6 +142,7 @@ class TestFWaaSL3AgentExtension(base.BaseTestCase):
def test_update_firewall_group_with_ports_added_and_deleted(self):
firewall_group = {'id': 0, 'project_id': 1,
'admin_state_up': True,
'ports': [1, 2, 3, 4],
'add-port-ids': [1, 2],
'del-port-ids': [3, 4],
'router_ids': [],
@ -180,6 +182,7 @@ class TestFWaaSL3AgentExtension(base.BaseTestCase):
def test_update_firewall_group_with_ports_added_and_admin_state_down(self):
firewall_group = {'id': 0, 'project_id': 1,
'admin_state_up': False,
'ports': [1, 2],
'add-port-ids': [1, 2],
'del-port-ids': [],
'router_ids': [],
@ -210,6 +213,7 @@ class TestFWaaSL3AgentExtension(base.BaseTestCase):
def test_update_firewall_group_with_all_ports_deleted(self):
firewall_group = {'id': 0, 'project_id': 1,
'admin_state_up': True,
'ports': [3, 4],
'add-port-ids': [],
'del-port-ids': [3, 4],
'last-port': True}
@ -230,9 +234,14 @@ class TestFWaaSL3AgentExtension(base.BaseTestCase):
self.api.update_firewall_group(self.context, firewall_group,
host='host')
mock_get_firewall_group_ports.assert_called_once_with(self.context,
firewall_group, require_new_plugin=True, to_delete=True)
calls = [
mock.call._get_firewall_group_ports(
self.context, firewall_group, require_new_plugin=True,
to_delete=True),
mock.call._get_firewall_group_ports(
self.context, firewall_group)
]
mock_get_firewall_group_ports.assert_has_calls(calls)
mock_get_in_ns_ports.assert_called
mock_set_firewall_group_status.assert_called_once_with(
self.context, firewall_group['id'], 'INACTIVE')
@ -240,6 +249,7 @@ class TestFWaaSL3AgentExtension(base.BaseTestCase):
def test_update_firewall_group_with_no_ports_added_or_deleted(self):
firewall_group = {'id': 0, 'project_id': 1,
'admin_state_up': True,
'ports': [],
'add-port-ids': [],
'del-port-ids': [],
'router_ids': []}
@ -262,6 +272,7 @@ class TestFWaaSL3AgentExtension(base.BaseTestCase):
# This test is for bug/1634114
firewall_group = {'id': 0, 'project_id': 1,
'admin_state_up': True,
'ports': [1, 2],
'add-port-ids': ['1', '2'],
'del-port-ids': [],
'last-port': False
@ -272,6 +283,7 @@ class TestFWaaSL3AgentExtension(base.BaseTestCase):
def test_delete_firewall_group(self):
firewall_group = {'id': 0, 'project_id': 1,
'admin_state_up': True,
'ports': [3, 4],
'add-port-ids': [],
'del-port-ids': [3, 4],
'last-port': False}
@ -357,6 +369,7 @@ class TestFWaaSL3AgentExtension(base.BaseTestCase):
'status': 'ACTIVE',
'admin_state_up': True,
'tenant_id': 'demo_tenant_id',
'ports': [1],
'del-port-ids': [],
'add-port-ids': ['1'],
'id': '2932b3d9-3a7b-48a1-a16c-bf9f7b2751a5'

View File

@ -122,8 +122,7 @@ class TestFirewallRouterPortBase(
class TestFirewallCallbacks(TestFirewallRouterPortBase):
def setUp(self):
super(TestFirewallCallbacks,
self).setUp(fw_plugin=FW_PLUGIN_KLASS)
super(TestFirewallCallbacks, self).setUp(fw_plugin=FW_PLUGIN_KLASS)
self.callbacks = self.plugin.endpoints[0]
def test_set_firewall_group_status(self):
@ -229,7 +228,7 @@ class TestFirewallCallbacks(TestFirewallRouterPortBase):
class TestFirewallPluginBasev2(TestFirewallRouterPortBase,
test_l3_plugin.L3NatTestCaseMixin):
test_l3_plugin.L3NatTestCaseMixin):
def setUp(self):
super(TestFirewallPluginBasev2, self).setUp(fw_plugin=FW_PLUGIN_KLASS)

View File

@ -49,6 +49,8 @@ tempest.test_plugins =
oslo.config.opts =
neutron.fwaas = neutron_fwaas.opts:list_opts
firewall.agent = neutron_fwaas.opts:list_agent_opts
neutron.agent.l2.extensions =
fwaas_v2 = neutron_fwaas.services.firewall.agents.l2.fwaas_v2:FWaaSV2AgentExtension
neutron.agent.l2.firewall_drivers =
noop = neutron_fwaas.services.firewall.drivers.linux.l2.noop.noop_driver:NoopFirewallL2Driver
neutron.agent.l3.extensions =