From 0043044f2e8ee3de8a9995bdbf51aad77f1a3fa9 Mon Sep 17 00:00:00 2001 From: Ryota MIBU Date: Tue, 9 Jul 2013 17:18:10 +0900 Subject: [PATCH] Improve packet-filter test coverage in NEC Plugin blueprint nec-plugin-test-coverage This commit adds unit tests for packet-filter in NEC Plugin. This commit refactors packet-filter in NEC Plugin. - Put packet-filter classes and methods into nec/packet_filter.py (a) and nec/db/packetfilter.py (b), NEC Plugin specific codes are in (a) - Change stateless methods to class methods in extenstions/packetfilter.py - Add 'convert_to' option to the attribute map of packet-filter to convert some string parameter to int at the api layer Also, this commit includes the following changes in packet-filter. - Fix attribute map of packet-filter; set in_port to allow_put=False - Add new methods to update attribute map properly - Make packet-filters ignore status of associated resource (network) Change-Id: I7c0b76afb603f1f078b28610181b16ce66225d37 --- neutron/plugins/nec/common/exceptions.py | 4 - neutron/plugins/nec/db/models.py | 23 -- neutron/plugins/nec/db/nec_plugin_base.py | 121 ------ neutron/plugins/nec/db/packetfilter.py | 148 +++++++ .../plugins/nec/extensions/packetfilter.py | 93 +++-- neutron/plugins/nec/nec_plugin.py | 187 ++------- neutron/plugins/nec/packet_filter.py | 170 +++++++++ neutron/tests/unit/nec/test_packet_filter.py | 360 +++++++++++++++++- 8 files changed, 754 insertions(+), 352 deletions(-) delete mode 100644 neutron/plugins/nec/db/nec_plugin_base.py create mode 100644 neutron/plugins/nec/db/packetfilter.py create mode 100644 neutron/plugins/nec/packet_filter.py diff --git a/neutron/plugins/nec/common/exceptions.py b/neutron/plugins/nec/common/exceptions.py index a5f09a4cab..226ce6e996 100644 --- a/neutron/plugins/nec/common/exceptions.py +++ b/neutron/plugins/nec/common/exceptions.py @@ -33,7 +33,3 @@ class OFCConsistencyBroken(qexc.NeutronException): class PortInfoNotFound(qexc.NotFound): message = _("PortInfo %(id)s could not be found") - - -class PacketFilterNotFound(qexc.NotFound): - message = _("PacketFilter %(id)s could not be found") diff --git a/neutron/plugins/nec/db/models.py b/neutron/plugins/nec/db/models.py index 22b7b28feb..a04edf5287 100644 --- a/neutron/plugins/nec/db/models.py +++ b/neutron/plugins/nec/db/models.py @@ -80,26 +80,3 @@ class PortInfo(model_base.BASEV2, models_v2.HasId): port_no = sa.Column(sa.Integer, nullable=False) vlan_id = sa.Column(sa.Integer, nullable=False) mac = sa.Column(sa.String(32), nullable=False) - - -class PacketFilter(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant): - """Represents a packet filter.""" - name = sa.Column(sa.String(255)) - network_id = sa.Column(sa.String(36), - sa.ForeignKey('networks.id', ondelete="CASCADE"), - nullable=False) - priority = sa.Column(sa.Integer, nullable=False) - action = sa.Column(sa.String(16), nullable=False) - # condition - in_port = sa.Column(sa.String(36), nullable=False) - src_mac = sa.Column(sa.String(32), nullable=False) - dst_mac = sa.Column(sa.String(32), nullable=False) - eth_type = sa.Column(sa.Integer, nullable=False) - src_cidr = sa.Column(sa.String(64), nullable=False) - dst_cidr = sa.Column(sa.String(64), nullable=False) - protocol = sa.Column(sa.String(16), nullable=False) - src_port = sa.Column(sa.Integer, nullable=False) - dst_port = sa.Column(sa.Integer, nullable=False) - # status - admin_state_up = sa.Column(sa.Boolean(), nullable=False) - status = sa.Column(sa.String(16), nullable=False) diff --git a/neutron/plugins/nec/db/nec_plugin_base.py b/neutron/plugins/nec/db/nec_plugin_base.py deleted file mode 100644 index 7693783191..0000000000 --- a/neutron/plugins/nec/db/nec_plugin_base.py +++ /dev/null @@ -1,121 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 NEC Corporation. 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. -# @author: Ryota MIBU - -from sqlalchemy.orm import exc - -from neutron.api.v2 import attributes -from neutron.db import db_base_plugin_v2 -from neutron.openstack.common import log as logging -from neutron.openstack.common import uuidutils -from neutron.plugins.nec.common import exceptions as q_exc -from neutron.plugins.nec.db import models as nmodels - - -LOG = logging.getLogger(__name__) - - -class NECPluginV2Base(db_base_plugin_v2.NeutronDbPluginV2): - - """Base class of plugins that handle packet filters.""" - - def _make_packet_filter_dict(self, packet_filter, fields=None): - res = {'id': packet_filter['id'], - 'name': packet_filter['name'], - 'tenant_id': packet_filter['tenant_id'], - 'network_id': packet_filter['network_id'], - 'action': packet_filter['action'], - 'priority': packet_filter['priority'], - 'in_port': packet_filter['in_port'], - 'src_mac': packet_filter['src_mac'], - 'dst_mac': packet_filter['dst_mac'], - 'eth_type': packet_filter['eth_type'], - 'src_cidr': packet_filter['src_cidr'], - 'dst_cidr': packet_filter['dst_cidr'], - 'protocol': packet_filter['protocol'], - 'src_port': packet_filter['src_port'], - 'dst_port': packet_filter['dst_port'], - 'admin_state_up': packet_filter['admin_state_up'], - 'status': packet_filter['status']} - return self._fields(res, fields) - - def _get_packet_filter(self, context, id): - try: - packet_filter = self._get_by_id(context, nmodels.PacketFilter, id) - except exc.NoResultFound: - raise q_exc.PacketFilterNotFound(id=id) - return packet_filter - - def get_packet_filter(self, context, id, fields=None): - packet_filter = self._get_packet_filter(context, id) - return self._make_packet_filter_dict(packet_filter, fields) - - def get_packet_filters(self, context, filters=None, fields=None): - return self._get_collection(context, - nmodels.PacketFilter, - self._make_packet_filter_dict, - filters=filters, - fields=fields) - - def create_packet_filter(self, context, packet_filter): - pf = packet_filter['packet_filter'] - tenant_id = self._get_tenant_id_for_create(context, pf) - - # validate network ownership - super(NECPluginV2Base, self).get_network(context, pf['network_id']) - if pf.get('in_port') is not attributes.ATTR_NOT_SPECIFIED: - # validate port ownership - super(NECPluginV2Base, self).get_port(context, pf['in_port']) - - params = {'tenant_id': tenant_id, - 'id': pf.get('id') or uuidutils.generate_uuid(), - 'name': pf['name'], - 'network_id': pf['network_id'], - 'priority': pf['priority'], - 'action': pf['action'], - 'admin_state_up': pf.get('admin_state_up', True), - 'status': "ACTIVE"} - conditions = {'in_port': '', - 'src_mac': '', - 'dst_mac': '', - 'eth_type': 0, - 'src_cidr': '', - 'dst_cidr': '', - 'src_port': 0, - 'dst_port': 0, - 'protocol': ''} - for key, default in conditions.items(): - if pf.get(key) is attributes.ATTR_NOT_SPECIFIED: - params.update({key: default}) - else: - params.update({key: pf.get(key)}) - - with context.session.begin(subtransactions=True): - pf_entry = nmodels.PacketFilter(**params) - context.session.add(pf_entry) - return self._make_packet_filter_dict(pf_entry) - - def update_packet_filter(self, context, id, packet_filter): - pf = packet_filter['packet_filter'] - with context.session.begin(subtransactions=True): - pf_entry = self._get_packet_filter(context, id) - pf_entry.update(pf) - return self._make_packet_filter_dict(pf_entry) - - def delete_packet_filter(self, context, id): - with context.session.begin(subtransactions=True): - packet_filter = self._get_packet_filter(context, id) - context.session.delete(packet_filter) diff --git a/neutron/plugins/nec/db/packetfilter.py b/neutron/plugins/nec/db/packetfilter.py new file mode 100644 index 0000000000..a45bb26fe7 --- /dev/null +++ b/neutron/plugins/nec/db/packetfilter.py @@ -0,0 +1,148 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012-2013 NEC Corporation. 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. +# @author: Ryota MIBU + +import sqlalchemy as sa +from sqlalchemy.orm import exc as sa_exc + +from neutron.api.v2 import attributes +from neutron.common import exceptions +from neutron.db import model_base +from neutron.db import models_v2 +from neutron.openstack.common import uuidutils + + +PF_STATUS_ACTIVE = 'ACTIVE' +PF_STATUS_DOWN = 'DOWN' +PF_STATUS_ERROR = 'ERROR' + + +class PacketFilterNotFound(exceptions.NotFound): + message = _("PacketFilter %(id)s could not be found") + + +class PacketFilter(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant): + """Represents a packet filter.""" + name = sa.Column(sa.String(255)) + network_id = sa.Column(sa.String(36), + sa.ForeignKey('networks.id', ondelete="CASCADE"), + nullable=False) + priority = sa.Column(sa.Integer, nullable=False) + action = sa.Column(sa.String(16), nullable=False) + # condition + in_port = sa.Column(sa.String(36), nullable=False) + src_mac = sa.Column(sa.String(32), nullable=False) + dst_mac = sa.Column(sa.String(32), nullable=False) + eth_type = sa.Column(sa.Integer, nullable=False) + src_cidr = sa.Column(sa.String(64), nullable=False) + dst_cidr = sa.Column(sa.String(64), nullable=False) + protocol = sa.Column(sa.String(16), nullable=False) + src_port = sa.Column(sa.Integer, nullable=False) + dst_port = sa.Column(sa.Integer, nullable=False) + # status + admin_state_up = sa.Column(sa.Boolean(), nullable=False) + status = sa.Column(sa.String(16), nullable=False) + + +class PacketFilterDbMixin(object): + + def _make_packet_filter_dict(self, pf_entry, fields=None): + res = {'id': pf_entry['id'], + 'name': pf_entry['name'], + 'tenant_id': pf_entry['tenant_id'], + 'network_id': pf_entry['network_id'], + 'action': pf_entry['action'], + 'priority': pf_entry['priority'], + 'in_port': pf_entry['in_port'], + 'src_mac': pf_entry['src_mac'], + 'dst_mac': pf_entry['dst_mac'], + 'eth_type': pf_entry['eth_type'], + 'src_cidr': pf_entry['src_cidr'], + 'dst_cidr': pf_entry['dst_cidr'], + 'protocol': pf_entry['protocol'], + 'src_port': pf_entry['src_port'], + 'dst_port': pf_entry['dst_port'], + 'admin_state_up': pf_entry['admin_state_up'], + 'status': pf_entry['status']} + return self._fields(res, fields) + + def _get_packet_filter(self, context, id): + try: + pf_entry = self._get_by_id(context, PacketFilter, id) + except sa_exc.NoResultFound: + raise PacketFilterNotFound(id=id) + return pf_entry + + def get_packet_filter(self, context, id, fields=None): + pf_entry = self._get_packet_filter(context, id) + return self._make_packet_filter_dict(pf_entry, fields) + + def get_packet_filters(self, context, filters=None, fields=None): + return self._get_collection(context, + PacketFilter, + self._make_packet_filter_dict, + filters=filters, + fields=fields) + + def create_packet_filter(self, context, packet_filter): + pf_dict = packet_filter['packet_filter'] + tenant_id = self._get_tenant_id_for_create(context, pf_dict) + + if pf_dict['in_port'] == attributes.ATTR_NOT_SPECIFIED: + # validate network ownership + self.get_network(context, pf_dict['network_id']) + else: + # validate port ownership + self.get_port(context, pf_dict['in_port']) + + params = {'tenant_id': tenant_id, + 'id': pf_dict.get('id') or uuidutils.generate_uuid(), + 'name': pf_dict['name'], + 'network_id': pf_dict['network_id'], + 'priority': pf_dict['priority'], + 'action': pf_dict['action'], + 'admin_state_up': pf_dict.get('admin_state_up', True), + 'status': PF_STATUS_DOWN, + 'in_port': pf_dict['in_port'], + 'src_mac': pf_dict['src_mac'], + 'dst_mac': pf_dict['dst_mac'], + 'eth_type': pf_dict['eth_type'], + 'src_cidr': pf_dict['src_cidr'], + 'dst_cidr': pf_dict['dst_cidr'], + 'src_port': pf_dict['src_port'], + 'dst_port': pf_dict['dst_port'], + 'protocol': pf_dict['protocol']} + for key, default in params.items(): + if params[key] == attributes.ATTR_NOT_SPECIFIED: + params.update({key: ''}) + + with context.session.begin(subtransactions=True): + pf_entry = PacketFilter(**params) + context.session.add(pf_entry) + + return self._make_packet_filter_dict(pf_entry) + + def update_packet_filter(self, context, id, packet_filter): + pf = packet_filter['packet_filter'] + with context.session.begin(subtransactions=True): + pf_entry = self._get_packet_filter(context, id) + pf_entry.update(pf) + return self._make_packet_filter_dict(pf_entry) + + def delete_packet_filter(self, context, id): + with context.session.begin(subtransactions=True): + pf_entry = self._get_packet_filter(context, id) + context.session.delete(pf_entry) diff --git a/neutron/plugins/nec/extensions/packetfilter.py b/neutron/plugins/nec/extensions/packetfilter.py index ce485eb16f..52591a64b1 100644 --- a/neutron/plugins/nec/extensions/packetfilter.py +++ b/neutron/plugins/nec/extensions/packetfilter.py @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2012 NEC Corporation. +# Copyright 2012-2013 NEC Corporation. # All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -23,6 +23,7 @@ from oslo.config import cfg from neutron.api import extensions from neutron.api.v2 import attributes from neutron.api.v2 import base +from neutron.common import exceptions from neutron.manager import NeutronManager from neutron import quota @@ -33,20 +34,36 @@ quota_packet_filter_opts = [ help=_("Number of packet_filters allowed per tenant, " "-1 for unlimited")) ] -# Register the configuration options cfg.CONF.register_opts(quota_packet_filter_opts, 'QUOTAS') -PACKET_FILTER_ACTION_REGEX = "(?i)^(allow|accept|drop|deny)$" -PACKET_FILTER_NUMBER_REGEX = "(?i)^(0x[0-9a-fA-F]+|[0-9]+)$" -PACKET_FILTER_PROTOCOL_REGEX = "(?i)^(icmp|tcp|udp|arp|0x[0-9a-fA-F]+|[0-9]+)$" -PACKET_FILTER_ATTR_MAP = { +def convert_to_int(data): + try: + return int(data, 0) + except (ValueError, TypeError): + pass + try: + return int(data) + except (ValueError, TypeError): + msg = _("'%s' is not a integer") % data + raise exceptions.InvalidInput(error_message=msg) + + +ALIAS = 'packet-filter' +RESOURCE = 'packet_filter' +COLLECTION = 'packet_filters' +PACKET_FILTER_ACTION_REGEX = '(?i)^(allow|accept|drop|deny)$' +PACKET_FILTER_PROTOCOL_REGEX = ( + '(?i)^(icmp|tcp|udp|arp|0x[0-9a-fA-F]+|[0-9]+|)$') +PACKET_FILTER_ATTR_PARAMS = { 'id': {'allow_post': False, 'allow_put': False, 'validate': {'type:uuid': None}, 'is_visible': True}, 'name': {'allow_post': True, 'allow_put': True, 'default': '', + 'validate': {'type:string': None}, 'is_visible': True}, 'tenant_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:string': None}, 'required_by_policy': True, 'is_visible': True}, 'network_id': {'allow_post': True, 'allow_put': False, @@ -62,9 +79,9 @@ PACKET_FILTER_ATTR_MAP = { 'validate': {'type:regex': PACKET_FILTER_ACTION_REGEX}, 'is_visible': True}, 'priority': {'allow_post': True, 'allow_put': True, - 'validate': {'type:regex': PACKET_FILTER_NUMBER_REGEX}, + 'convert_to': convert_to_int, 'is_visible': True}, - 'in_port': {'allow_post': True, 'allow_put': True, + 'in_port': {'allow_post': True, 'allow_put': False, 'default': attributes.ATTR_NOT_SPECIFIED, 'validate': {'type:uuid': None}, 'is_visible': True}, @@ -78,7 +95,7 @@ PACKET_FILTER_ATTR_MAP = { 'is_visible': True}, 'eth_type': {'allow_post': True, 'allow_put': True, 'default': attributes.ATTR_NOT_SPECIFIED, - 'validate': {'type:regex': PACKET_FILTER_NUMBER_REGEX}, + 'convert_to': convert_to_int, 'is_visible': True}, 'src_cidr': {'allow_post': True, 'allow_put': True, 'default': attributes.ATTR_NOT_SPECIFIED, @@ -94,40 +111,58 @@ PACKET_FILTER_ATTR_MAP = { 'is_visible': True}, 'src_port': {'allow_post': True, 'allow_put': True, 'default': attributes.ATTR_NOT_SPECIFIED, - 'validate': {'type:regex': PACKET_FILTER_NUMBER_REGEX}, + 'convert_to': convert_to_int, 'is_visible': True}, 'dst_port': {'allow_post': True, 'allow_put': True, 'default': attributes.ATTR_NOT_SPECIFIED, - 'validate': {'type:regex': PACKET_FILTER_NUMBER_REGEX}, + 'convert_to': convert_to_int, 'is_visible': True}, } +PACKET_FILTER_ATTR_MAP = {COLLECTION: PACKET_FILTER_ATTR_PARAMS} class Packetfilter(extensions.ExtensionDescriptor): - def get_name(self): - return "PacketFilters" + @classmethod + def get_name(cls): + return ALIAS - def get_alias(self): - return "PacketFilters" + @classmethod + def get_alias(cls): + return ALIAS - def get_description(self): - return "PacketFilters" + @classmethod + def get_description(cls): + return "PacketFilters on OFC" - def get_namespace(self): + @classmethod + def get_namespace(cls): return "http://www.nec.co.jp/api/ext/packet_filter/v2.0" - def get_updated(self): - return "2012-07-24T00:00:00+09:00" + @classmethod + def get_updated(cls): + return "2013-07-16T00:00:00+09:00" - def get_resources(self): - resource = base.create_resource('packet_filters', 'packet_filter', - NeutronManager.get_plugin(), - PACKET_FILTER_ATTR_MAP) - qresource = quota.CountableResource('packet_filter', + @classmethod + def get_resources(cls): + qresource = quota.CountableResource(RESOURCE, quota._count_resource, - 'quota_packet_filter') + 'quota_%s' % RESOURCE) quota.QUOTAS.register_resource(qresource) - return [extensions.ResourceExtension('packet_filters', - resource, - attr_map=PACKET_FILTER_ATTR_MAP)] + + resource = base.create_resource(COLLECTION, RESOURCE, + NeutronManager.get_plugin(), + PACKET_FILTER_ATTR_PARAMS) + pf_ext = extensions.ResourceExtension( + COLLECTION, resource, attr_map=PACKET_FILTER_ATTR_PARAMS) + return [pf_ext] + + def update_attributes_map(self, attributes): + super(Packetfilter, self).update_attributes_map( + attributes, extension_attrs_map=PACKET_FILTER_ATTR_MAP) + + def get_extended_resources(self, version): + if version == "2.0": + return PACKET_FILTER_ATTR_MAP + else: + return {} diff --git a/neutron/plugins/nec/nec_plugin.py b/neutron/plugins/nec/nec_plugin.py index 3f3752455c..08b74403e6 100644 --- a/neutron/plugins/nec/nec_plugin.py +++ b/neutron/plugins/nec/nec_plugin.py @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # -# Copyright 2012 NEC Corporation. All rights reserved. +# Copyright 2012-2013 NEC Corporation. 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 @@ -39,8 +39,9 @@ from neutron.openstack.common.rpc import proxy from neutron.plugins.nec.common import config from neutron.plugins.nec.common import exceptions as nexc from neutron.plugins.nec.db import api as ndb -from neutron.plugins.nec.db import nec_plugin_base +from neutron.plugins.nec.db import packetfilter as pf_db from neutron.plugins.nec import ofc_manager +from neutron.plugins.nec import packet_filter LOG = logging.getLogger(__name__) @@ -60,12 +61,13 @@ class OperationalStatus: ERROR = "ERROR" -class NECPluginV2(nec_plugin_base.NECPluginV2Base, +class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2, extraroute_db.ExtraRoute_db_mixin, l3_gwmode_db.L3_NAT_db_mixin, sg_db_rpc.SecurityGroupServerRpcMixin, agentschedulers_db.L3AgentSchedulerDbMixin, - agentschedulers_db.DhcpAgentSchedulerDbMixin): + agentschedulers_db.DhcpAgentSchedulerDbMixin, + packet_filter.PacketFilterMixin): """NECPluginV2 controls an OpenFlow Controller. The Neutron NECPluginV2 maps L2 logical networks to L2 virtualized networks @@ -82,13 +84,15 @@ class NECPluginV2(nec_plugin_base.NECPluginV2Base, "binding", "security-group", "extraroute", "agent", "l3_agent_scheduler", - "dhcp_agent_scheduler"] + "dhcp_agent_scheduler", + "packet-filter"] @property def supported_extension_aliases(self): if not hasattr(self, '_aliases'): aliases = self._supported_extension_aliases[:] sg_rpc.disable_security_group_extension_if_noop_driver(aliases) + self.remove_packet_filter_extension_if_disabled(aliases) self._aliases = aliases return self._aliases @@ -96,11 +100,6 @@ class NECPluginV2(nec_plugin_base.NECPluginV2Base, ndb.initialize() self.ofc = ofc_manager.OFCManager() - self.packet_filter_enabled = (config.OFC.enable_packet_filter and - self.ofc.driver.filter_supported()) - if self.packet_filter_enabled: - self.supported_extension_aliases.append("PacketFilters") - # Set the plugin default extension path # if no api_extensions_path is specified. if not config.CONF.api_extensions_path: @@ -172,14 +171,11 @@ class NECPluginV2(nec_plugin_base.NECPluginV2Base, if self.packet_filter_enabled: if port_status is OperationalStatus.ACTIVE: filters = dict(in_port=[port['id']], - status=[OperationalStatus.DOWN], + status=[pf_db.PF_STATUS_DOWN], admin_state_up=[True]) - pfs = (super(NECPluginV2, self). - get_packet_filters(context, filters=filters)) + pfs = self.get_packet_filters(context, filters=filters) for pf in pfs: - self._activate_packet_filter_if_ready(context, pf, - network=network, - in_port=port) + self.activate_packet_filter_if_ready(context, pf) if port_status in [OperationalStatus.ACTIVE]: if self.ofc.exists_ofc_port(context, port['id']): @@ -221,11 +217,10 @@ class NECPluginV2(nec_plugin_base.NECPluginV2Base, # deactivate packet_filters after the port has deleted from OFC. if self.packet_filter_enabled: filters = dict(in_port=[port['id']], - status=[OperationalStatus.ACTIVE]) - pfs = super(NECPluginV2, self).get_packet_filters(context, - filters=filters) + status=[pf_db.PF_STATUS_ACTIVE]) + pfs = self.get_packet_filters(context, filters=filters) for pf in pfs: - self._deactivate_packet_filter(context, pf) + self.deactivate_packet_filter(context, pf) # Quantm Plugin Basic methods @@ -280,32 +275,22 @@ class NECPluginV2(nec_plugin_base.NECPluginV2Base, if changed and not new_net['admin_state_up']: self._update_resource_status(context, "network", id, OperationalStatus.DOWN) - # disable all active ports and packet_filters of the network + # disable all active ports of the network filters = dict(network_id=[id], status=[OperationalStatus.ACTIVE]) ports = super(NECPluginV2, self).get_ports(context, filters=filters) for port in ports: self.deactivate_port(context, port) - if self.packet_filter_enabled: - pfs = (super(NECPluginV2, self). - get_packet_filters(context, filters=filters)) - for pf in pfs: - self._deactivate_packet_filter(context, pf) elif changed and new_net['admin_state_up']: self._update_resource_status(context, "network", id, OperationalStatus.ACTIVE) - # enable ports and packet_filters of the network + # enable ports of the network filters = dict(network_id=[id], status=[OperationalStatus.DOWN], admin_state_up=[True]) ports = super(NECPluginV2, self).get_ports(context, filters=filters) for port in ports: self.activate_port_if_ready(context, port, new_net) - if self.packet_filter_enabled: - pfs = (super(NECPluginV2, self). - get_packet_filters(context, filters=filters)) - for pf in pfs: - self._activate_packet_filter_if_ready(context, pf, new_net) return new_net @@ -329,11 +314,12 @@ class NECPluginV2(nec_plugin_base.NECPluginV2Base, ' from OFC: %s'), port) self.deactivate_port(context, port) - # get packet_filters associated with the network + # delete all packet_filters of the network if self.packet_filter_enabled: filters = dict(network_id=[id]) - pfs = (super(NECPluginV2, self). - get_packet_filters(context, filters=filters)) + pfs = self.get_packet_filters(context, filters=filters) + for pf in pfs: + self.delete_packet_filter(context, pf['id']) super(NECPluginV2, self).delete_network(context, id) try: @@ -346,11 +332,6 @@ class NECPluginV2(nec_plugin_base.NECPluginV2Base, # resources, so this plugin just warns. LOG.warn(reason) - # delete all packet_filters of the network - if self.packet_filter_enabled: - for pf in pfs: - self.delete_packet_filter(context, pf['id']) - # delete unnessary ofc_tenant filters = dict(tenant_id=[tenant_id]) nets = super(NECPluginV2, self).get_networks(context, filters=filters) @@ -426,8 +407,7 @@ class NECPluginV2(nec_plugin_base.NECPluginV2Base, # delete all packet_filters of the port if self.packet_filter_enabled: filters = dict(port_id=[id]) - pfs = (super(NECPluginV2, self). - get_packet_filters(context, filters=filters)) + pfs = self.get_packet_filters(context, filters=filters) for packet_filter in pfs: self.delete_packet_filter(context, packet_filter['id']) @@ -456,129 +436,6 @@ class NECPluginV2(nec_plugin_base.NECPluginV2Base, self._extend_port_dict_binding(context, port) return [self._fields(port, fields) for port in ports] - # For PacketFilter Extension - - def _activate_packet_filter_if_ready(self, context, packet_filter, - network=None, in_port=None): - """Activate packet_filter by creating filter on OFC if ready. - - Conditions to create packet_filter on OFC are: - * packet_filter admin_state is UP - * network admin_state is UP - * (if 'in_port' is specified) portinfo is available - """ - net_id = packet_filter['network_id'] - if not network: - network = super(NECPluginV2, self).get_network(context, net_id) - in_port_id = packet_filter.get("in_port") - if in_port_id and not in_port: - in_port = super(NECPluginV2, self).get_port(context, in_port_id) - - pf_status = OperationalStatus.ACTIVE - if not packet_filter['admin_state_up']: - LOG.debug(_("_activate_packet_filter_if_ready(): skip, " - "packet_filter.admin_state_up is False.")) - pf_status = OperationalStatus.DOWN - elif not network['admin_state_up']: - LOG.debug(_("_activate_packet_filter_if_ready(): skip, " - "network.admin_state_up is False.")) - pf_status = OperationalStatus.DOWN - elif in_port_id and in_port_id is in_port.get('id'): - LOG.debug(_("_activate_packet_filter_if_ready(): skip, " - "invalid in_port_id.")) - pf_status = OperationalStatus.DOWN - elif in_port_id and not ndb.get_portinfo(context.session, in_port_id): - LOG.debug(_("_activate_packet_filter_if_ready(): skip, " - "no portinfo for in_port.")) - pf_status = OperationalStatus.DOWN - - if pf_status in [OperationalStatus.ACTIVE]: - if self.ofc.exists_ofc_packet_filter(context, packet_filter['id']): - LOG.debug(_("_activate_packet_filter_if_ready(): skip, " - "ofc_packet_filter already exists.")) - else: - try: - (self.ofc. - create_ofc_packet_filter(context, - packet_filter['id'], - packet_filter)) - except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc: - reason = _("create_ofc_packet_filter() failed due to " - "%s") % exc - LOG.error(reason) - pf_status = OperationalStatus.ERROR - - if pf_status is not packet_filter['status']: - self._update_resource_status(context, "packet_filter", - packet_filter['id'], pf_status) - - def _deactivate_packet_filter(self, context, packet_filter): - """Deactivate packet_filter by deleting filter from OFC if exixts.""" - pf_status = OperationalStatus.DOWN - if not self.ofc.exists_ofc_packet_filter(context, packet_filter['id']): - LOG.debug(_("_deactivate_packet_filter(): skip, " - "ofc_packet_filter does not exist.")) - else: - try: - self.ofc.delete_ofc_packet_filter(context, packet_filter['id']) - except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc: - reason = _("delete_ofc_packet_filter() failed due to " - "%s") % exc - LOG.error(reason) - pf_status = OperationalStatus.ERROR - - if pf_status is not packet_filter['status']: - self._update_resource_status(context, "packet_filter", - packet_filter['id'], pf_status) - - def create_packet_filter(self, context, packet_filter): - """Create a new packet_filter entry on DB, then try to activate it.""" - LOG.debug(_("NECPluginV2.create_packet_filter() called, " - "packet_filter=%s ."), packet_filter) - new_pf = super(NECPluginV2, self).create_packet_filter(context, - packet_filter) - self._update_resource_status(context, "packet_filter", new_pf['id'], - OperationalStatus.BUILD) - - self._activate_packet_filter_if_ready(context, new_pf) - - return new_pf - - def update_packet_filter(self, context, id, packet_filter): - """Update packet_filter entry on DB, and recreate it if changed. - - If any rule of the packet_filter was changed, recreate it on OFC. - """ - LOG.debug(_("NECPluginV2.update_packet_filter() called, " - "id=%(id)s packet_filter=%(packet_filter)s ."), - {'id': id, 'packet_filter': packet_filter}) - with context.session.begin(subtransactions=True): - old_pf = super(NECPluginV2, self).get_packet_filter(context, id) - new_pf = super(NECPluginV2, self).update_packet_filter( - context, id, packet_filter) - - changed = False - exclude_items = ["id", "name", "tenant_id", "network_id", "status"] - for key in new_pf['packet_filter'].keys(): - if key not in exclude_items: - if old_pf[key] is not new_pf[key]: - changed = True - break - - if changed: - self._deactivate_packet_filter(context, old_pf) - self._activate_packet_filter_if_ready(context, new_pf) - - return new_pf - - def delete_packet_filter(self, context, id): - """Deactivate and delete packet_filter.""" - LOG.debug(_("NECPluginV2.delete_packet_filter() called, id=%s ."), id) - pf = super(NECPluginV2, self).get_packet_filter(context, id) - self._deactivate_packet_filter(context, pf) - - super(NECPluginV2, self).delete_packet_filter(context, id) - class NECPluginV2AgentNotifierApi(proxy.RpcProxy, sg_rpc.SecurityGroupAgentRpcApiMixin): diff --git a/neutron/plugins/nec/packet_filter.py b/neutron/plugins/nec/packet_filter.py new file mode 100644 index 0000000000..43f1cf89fb --- /dev/null +++ b/neutron/plugins/nec/packet_filter.py @@ -0,0 +1,170 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012-2013 NEC Corporation. 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. +# @author: Ryota MIBU + +from neutron.openstack.common import log as logging +from neutron.plugins.nec.common import config +from neutron.plugins.nec.common import exceptions as nexc +from neutron.plugins.nec.db import api as ndb +from neutron.plugins.nec.db import packetfilter as pf_db + + +LOG = logging.getLogger(__name__) + + +class PacketFilterMixin(pf_db.PacketFilterDbMixin): + """Mixin class to add packet filter to NECPluginV2.""" + + @property + def packet_filter_enabled(self): + if not hasattr(self, '_packet_filter_enabled'): + self._packet_filter_enabled = ( + config.OFC.enable_packet_filter and + self.ofc.driver.filter_supported()) + return self._packet_filter_enabled + + def remove_packet_filter_extension_if_disabled(self, aliases): + if not self.packet_filter_enabled: + LOG.debug(_('Disabled packet-filter extension.')) + aliases.remove('packet-filter') + + def create_packet_filter(self, context, packet_filter): + """Create a new packet_filter entry on DB, then try to activate it.""" + LOG.debug(_("create_packet_filter() called, packet_filter=%s ."), + packet_filter) + + pf = super(PacketFilterMixin, self).create_packet_filter( + context, packet_filter) + + return self.activate_packet_filter_if_ready(context, pf) + + def update_packet_filter(self, context, id, packet_filter): + """Update packet_filter entry on DB, and recreate it if changed. + + If any rule of the packet_filter was changed, recreate it on OFC. + """ + LOG.debug(_("update_packet_filter() called, " + "id=%(id)s packet_filter=%(packet_filter)s ."), + {'id': id, 'packet_filter': packet_filter}) + + # validate ownership + pf_old = self.get_packet_filter(context, id) + + pf = super(PacketFilterMixin, self).update_packet_filter( + context, id, packet_filter) + + def _packet_filter_changed(old_pf, new_pf): + for key in new_pf: + if key not in ('id', 'name', 'tenant_id', 'network_id', + 'in_port', 'status'): + if old_pf[key] != new_pf[key]: + return True + return False + + if _packet_filter_changed(pf_old, pf): + pf = self.deactivate_packet_filter(context, pf) + pf = self.activate_packet_filter_if_ready(context, pf) + + return pf + + def delete_packet_filter(self, context, id): + """Deactivate and delete packet_filter.""" + LOG.debug(_("delete_packet_filter() called, id=%s ."), id) + + # validate ownership + pf = self.get_packet_filter(context, id) + + pf = self.deactivate_packet_filter(context, pf) + if pf['status'] == pf_db.PF_STATUS_ERROR: + msg = _("failed to delete packet_filter id=%s which remains in " + "error status.") % id + LOG.error(msg) + raise nexc.OFCException(reason=msg) + + super(PacketFilterMixin, self).delete_packet_filter(context, id) + + def activate_packet_filter_if_ready(self, context, packet_filter): + """Activate packet_filter by creating filter on OFC if ready. + + Conditions to create packet_filter on OFC are: + * packet_filter admin_state is UP + * (if 'in_port' is specified) portinfo is available + """ + LOG.debug(_("activate_packet_filter_if_ready() called, " + "packet_filter=%s."), packet_filter) + + pf_id = packet_filter['id'] + in_port_id = packet_filter.get('in_port') + current = packet_filter['status'] + + pf_status = current + if not packet_filter['admin_state_up']: + LOG.debug(_("activate_packet_filter_if_ready(): skip pf_id=%s, " + "packet_filter.admin_state_up is False."), pf_id) + elif in_port_id and not ndb.get_portinfo(context.session, in_port_id): + LOG.debug(_("activate_packet_filter_if_ready(): skip " + "pf_id=%s, no portinfo for the in_port."), pf_id) + elif self.ofc.exists_ofc_packet_filter(context, packet_filter['id']): + LOG.debug(_("_activate_packet_filter_if_ready(): skip, " + "ofc_packet_filter already exists.")) + else: + LOG.debug(_("activate_packet_filter_if_ready(): create " + "packet_filter id=%s on OFC."), pf_id) + try: + self.ofc.create_ofc_packet_filter(context, pf_id, + packet_filter) + pf_status = pf_db.PF_STATUS_ACTIVE + except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc: + LOG.error(_("failed to create packet_filter id=%(id)s on " + "OFC: %(exc)s"), {'id': pf_id, 'exc': str(exc)}) + pf_status = pf_db.PF_STATUS_ERROR + + if pf_status != current: + self._update_resource_status(context, "packet_filter", pf_id, + pf_status) + packet_filter.update({'status': pf_status}) + + return packet_filter + + def deactivate_packet_filter(self, context, packet_filter): + """Deactivate packet_filter by deleting filter from OFC if exixts.""" + LOG.debug(_("deactivate_packet_filter_if_ready() called, " + "packet_filter=%s."), packet_filter) + pf_id = packet_filter['id'] + current = packet_filter['status'] + + pf_status = current + if self.ofc.exists_ofc_packet_filter(context, pf_id): + LOG.debug(_("deactivate_packet_filter(): " + "deleting packet_filter id=%s from OFC."), pf_id) + try: + self.ofc.delete_ofc_packet_filter(context, pf_id) + pf_status = pf_db.PF_STATUS_DOWN + except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc: + LOG.error(_("failed to delete packet_filter id=%(id)s from " + "OFC: %(exc)s"), {'id': pf_id, 'exc': str(exc)}) + pf_status = pf_db.PF_STATUS_ERROR + else: + LOG.debug(_("deactivate_packet_filter(): skip, " + "Not found OFC Mapping for packet_filter id=%s."), + pf_id) + + if pf_status != current: + self._update_resource_status(context, "packet_filter", pf_id, + pf_status) + packet_filter.update({'status': pf_status}) + + return packet_filter diff --git a/neutron/tests/unit/nec/test_packet_filter.py b/neutron/tests/unit/nec/test_packet_filter.py index 37924b1517..92c1fa12b2 100644 --- a/neutron/tests/unit/nec/test_packet_filter.py +++ b/neutron/tests/unit/nec/test_packet_filter.py @@ -21,35 +21,41 @@ import webob.exc from neutron.api.v2 import attributes from neutron.common.test_lib import test_config from neutron import context +from neutron.plugins.nec.common import exceptions as nexc from neutron.plugins.nec.extensions import packetfilter +from neutron.tests.unit.nec import test_nec_plugin from neutron.tests.unit import test_db_plugin as test_plugin -PLUGIN_NAME = 'neutron.plugins.nec.nec_plugin.NECPluginV2' -OFC_MANAGER = 'neutron.plugins.nec.nec_plugin.ofc_manager.OFCManager' +NEC_PLUGIN_PF_INI = """ +[DEFAULT] +api_extensions_path = neutron/plugins/nec/extensions +[OFC] +driver = neutron.tests.unit.nec.stub_ofc_driver.StubOFCDriver +enable_packet_filter = True +""" class PacketfilterExtensionManager(packetfilter.Packetfilter): - def get_resources(self): + @classmethod + def get_resources(cls): # Add the resources to the global attribute map # This is done here as the setup process won't # initialize the main API router which extends # the global attribute map attributes.RESOURCE_ATTRIBUTE_MAP.update( {'packet_filters': packetfilter.PACKET_FILTER_ATTR_MAP}) - return super(PacketfilterExtensionManager, self).get_resources() + return super(PacketfilterExtensionManager, cls).get_resources() -class TestNecPluginPacketFilter(test_plugin.NeutronDbPluginV2TestCase): +class TestNecPluginPacketFilter(test_nec_plugin.NecPluginV2TestCase): + + _nec_ini = NEC_PLUGIN_PF_INI def setUp(self): - self.addCleanup(mock.patch.stopall) - ofc_manager_cls = mock.patch(OFC_MANAGER).start() - ofc_driver = ofc_manager_cls.return_value.driver - ofc_driver.filter_supported.return_value = True test_config['extension_manager'] = PacketfilterExtensionManager() - super(TestNecPluginPacketFilter, self).setUp(PLUGIN_NAME) + super(TestNecPluginPacketFilter, self).setUp() def _create_packet_filter(self, fmt, net_id, expected_res_status=None, arg_list=None, **kwargs): @@ -98,6 +104,124 @@ class TestNecPluginPacketFilter(test_plugin.NeutronDbPluginV2TestCase): if do_delete: self._delete('packet_filters', pf['packet_filter']['id']) + @contextlib.contextmanager + def packet_filter_on_port(self, port=None, fmt=None, do_delete=True, + set_portinfo=True, **kwargs): + with test_plugin.optional_ctx(port, self.port) as port_to_use: + net_id = port_to_use['port']['network_id'] + port_id = port_to_use['port']['id'] + + if set_portinfo: + portinfo = {'id': port_id, + 'port_no': kwargs.get('port_no', 123)} + kw = {'added': [portinfo]} + if 'datapath_id' in kwargs: + kw['datapath_id'] = kwargs['datapath_id'] + self.rpcapi_update_ports(**kw) + + kwargs['in_port'] = port_id + pf = self._make_packet_filter(fmt or self.fmt, net_id, **kwargs) + self.assertEqual(port_id, pf['packet_filter']['in_port']) + try: + yield pf + finally: + if do_delete: + self._delete('packet_filters', pf['packet_filter']['id']) + + def test_list_packet_filters(self): + self._list('packet_filters') + + def test_create_pf_on_network_no_ofc_creation(self): + with self.packet_filter_on_network(admin_state_up=False) as pf: + self.assertEqual(pf['packet_filter']['status'], 'DOWN') + + self.assertFalse(self.ofc.create_ofc_packet_filter.called) + self.assertFalse(self.ofc.delete_ofc_packet_filter.called) + + def test_create_pf_on_port_no_ofc_creation(self): + with self.packet_filter_on_port(admin_state_up=False, + set_portinfo=False) as pf: + self.assertEqual(pf['packet_filter']['status'], 'DOWN') + + self.assertFalse(self.ofc.create_ofc_packet_filter.called) + self.assertFalse(self.ofc.delete_ofc_packet_filter.called) + + def test_create_pf_on_network_with_ofc_creation(self): + with self.packet_filter_on_network() as pf: + pf_id = pf['packet_filter']['id'] + self.assertEqual(pf['packet_filter']['status'], 'ACTIVE') + + ctx = mock.ANY + pf_dict = mock.ANY + expected = [ + mock.call.exists_ofc_packet_filter(ctx, pf_id), + mock.call.create_ofc_packet_filter(ctx, pf_id, pf_dict), + mock.call.exists_ofc_packet_filter(ctx, pf_id), + mock.call.delete_ofc_packet_filter(ctx, pf_id), + ] + self.ofc.assert_has_calls(expected) + self.assertEqual(self.ofc.create_ofc_packet_filter.call_count, 1) + self.assertEqual(self.ofc.delete_ofc_packet_filter.call_count, 1) + + def test_create_pf_on_port_with_ofc_creation(self): + with self.packet_filter_on_port() as pf: + pf_id = pf['packet_filter']['id'] + self.assertEqual(pf['packet_filter']['status'], 'ACTIVE') + + ctx = mock.ANY + pf_dict = mock.ANY + expected = [ + mock.call.exists_ofc_packet_filter(ctx, pf_id), + mock.call.create_ofc_packet_filter(ctx, pf_id, pf_dict), + mock.call.exists_ofc_packet_filter(ctx, pf_id), + mock.call.delete_ofc_packet_filter(ctx, pf_id), + ] + self.ofc.assert_has_calls(expected) + self.assertEqual(self.ofc.create_ofc_packet_filter.call_count, 1) + self.assertEqual(self.ofc.delete_ofc_packet_filter.call_count, 1) + + def test_create_pf_with_invalid_priority(self): + with self.network() as net: + net_id = net['network']['id'] + kwargs = {'priority': 'high'} + self._create_packet_filter(self.fmt, net_id, + webob.exc.HTTPBadRequest.code, + **kwargs) + + self.assertFalse(self.ofc.create_ofc_packet_filter.called) + + def test_create_pf_with_ofc_creation_failure(self): + self.ofc.set_raise_exc('create_ofc_packet_filter', + nexc.OFCException(reason='hoge')) + + with self.packet_filter_on_network() as pf: + pf_id = pf['packet_filter']['id'] + pf_ref = self._show('packet_filters', pf_id) + self.assertEqual(pf_ref['packet_filter']['status'], 'ERROR') + + self.ofc.set_raise_exc('create_ofc_packet_filter', None) + + # Retry deactivate packet_filter. + data = {'packet_filter': {'priority': 1000}} + self._update('packet_filters', pf_id, data) + + pf_ref = self._show('packet_filters', pf_id) + self.assertEqual(pf_ref['packet_filter']['status'], 'ACTIVE') + + ctx = mock.ANY + pf_dict = mock.ANY + expected = [ + mock.call.exists_ofc_packet_filter(ctx, pf_id), + mock.call.create_ofc_packet_filter(ctx, pf_id, pf_dict), + + mock.call.exists_ofc_packet_filter(ctx, pf_id), + + mock.call.exists_ofc_packet_filter(ctx, pf_id), + mock.call.create_ofc_packet_filter(ctx, pf_id, pf_dict), + ] + self.ofc.assert_has_calls(expected) + self.assertEqual(self.ofc.create_ofc_packet_filter.call_count, 2) + def test_show_pf_on_network(self): kwargs = { 'name': 'test-pf-net', @@ -125,3 +249,219 @@ class TestNecPluginPacketFilter(test_plugin.NeutronDbPluginV2TestCase): self.assertEqual(pf_id, pf_ref['packet_filter']['id']) for key in kwargs: self.assertEqual(kwargs[key], pf_ref['packet_filter'][key]) + + def test_show_pf_on_port(self): + kwargs = { + 'name': 'test-pf-port', + 'admin_state_up': False, + 'action': 'DENY', + 'priority': '0o147', + 'src_mac': '00:11:22:33:44:55', + 'dst_mac': '66:77:88:99:aa:bb', + 'eth_type': 2048, + 'src_cidr': '192.168.1.0/24', + 'dst_cidr': '192.168.2.0/24', + 'protocol': 'TCP', + 'dst_port': '0x50' + } + + with self.packet_filter_on_port(**kwargs) as pf: + pf_id = pf['packet_filter']['id'] + pf_ref = self._show('packet_filters', pf_id) + + # convert string to int. + kwargs.update({'priority': 103, 'eth_type': 2048, + 'src_port': u'', 'dst_port': 80}) + + self.assertEqual(pf_id, pf_ref['packet_filter']['id']) + for key in kwargs: + self.assertEqual(kwargs[key], pf_ref['packet_filter'][key]) + + def test_show_pf_not_found(self): + pf_id = '00000000-ffff-ffff-ffff-000000000000' + + self._show('packet_filters', pf_id, + expected_code=webob.exc.HTTPNotFound.code) + + def test_update_pf_on_network(self): + ctx = mock.ANY + pf_dict = mock.ANY + with self.packet_filter_on_network(admin_state_up=False) as pf: + pf_id = pf['packet_filter']['id'] + + self.assertFalse(self.ofc.create_ofc_packet_filter.called) + data = {'packet_filter': {'admin_state_up': True}} + self._update('packet_filters', pf_id, data) + self.ofc.create_ofc_packet_filter.assert_called_once_with( + ctx, pf_id, pf_dict) + + self.assertFalse(self.ofc.delete_ofc_packet_filter.called) + data = {'packet_filter': {'admin_state_up': False}} + self._update('packet_filters', pf_id, data) + self.ofc.delete_ofc_packet_filter.assert_called_once_with( + ctx, pf_id) + + def test_update_pf_on_port(self): + ctx = mock.ANY + pf_dict = mock.ANY + with self.packet_filter_on_port(admin_state_up=False) as pf: + pf_id = pf['packet_filter']['id'] + + self.assertFalse(self.ofc.create_ofc_packet_filter.called) + data = {'packet_filter': {'admin_state_up': True}} + self._update('packet_filters', pf_id, data) + self.ofc.create_ofc_packet_filter.assert_called_once_with( + ctx, pf_id, pf_dict) + + self.assertFalse(self.ofc.delete_ofc_packet_filter.called) + data = {'packet_filter': {'admin_state_up': False}} + self._update('packet_filters', pf_id, data) + self.ofc.delete_ofc_packet_filter.assert_called_once_with( + ctx, pf_id) + + def test_activate_pf_on_port_triggered_by_update_port(self): + ctx = mock.ANY + pf_dict = mock.ANY + with self.packet_filter_on_port(set_portinfo=False) as pf: + pf_id = pf['packet_filter']['id'] + in_port_id = pf['packet_filter']['in_port'] + + self.assertFalse(self.ofc.create_ofc_packet_filter.called) + portinfo = {'id': in_port_id, 'port_no': 123} + kw = {'added': [portinfo]} + self.rpcapi_update_ports(**kw) + self.ofc.create_ofc_packet_filter.assert_called_once_with( + ctx, pf_id, pf_dict) + + self.assertFalse(self.ofc.delete_ofc_packet_filter.called) + kw = {'removed': [in_port_id]} + self.rpcapi_update_ports(**kw) + self.ofc.delete_ofc_packet_filter.assert_called_once_with( + ctx, pf_id) + + # Ensure pf was created before in_port has activated. + ctx = mock.ANY + pf_dict = mock.ANY + port_dict = mock.ANY + expected = [ + mock.call.exists_ofc_packet_filter(ctx, pf_id), + mock.call.create_ofc_packet_filter(ctx, pf_id, pf_dict), + mock.call.exists_ofc_port(ctx, in_port_id), + mock.call.create_ofc_port(ctx, in_port_id, port_dict), + + mock.call.exists_ofc_port(ctx, in_port_id), + mock.call.delete_ofc_port(ctx, in_port_id, port_dict), + mock.call.exists_ofc_packet_filter(ctx, pf_id), + mock.call.delete_ofc_packet_filter(ctx, pf_id), + ] + self.ofc.assert_has_calls(expected) + self.assertEqual(self.ofc.create_ofc_packet_filter.call_count, 1) + self.assertEqual(self.ofc.delete_ofc_packet_filter.call_count, 1) + + def test_activate_pf_while_exists_on_ofc(self): + ctx = mock.ANY + with self.packet_filter_on_network() as pf: + pf_id = pf['packet_filter']['id'] + + self.ofc.set_raise_exc('delete_ofc_packet_filter', + nexc.OFCException(reason='hoge')) + + # This update request will make plugin reactivate pf. + data = {'packet_filter': {'priority': 1000}} + self._update('packet_filters', pf_id, data) + + self.ofc.set_raise_exc('delete_ofc_packet_filter', None) + + ctx = mock.ANY + pf_dict = mock.ANY + expected = [ + mock.call.exists_ofc_packet_filter(ctx, pf_id), + mock.call.create_ofc_packet_filter(ctx, pf_id, pf_dict), + + mock.call.exists_ofc_packet_filter(ctx, pf_id), + mock.call.delete_ofc_packet_filter(ctx, pf_id), + + mock.call.exists_ofc_packet_filter(ctx, pf_id), + + mock.call.exists_ofc_packet_filter(ctx, pf_id), + ] + self.ofc.assert_has_calls(expected) + self.assertEqual(self.ofc.delete_ofc_packet_filter.call_count, 2) + + def test_deactivate_pf_with_ofc_deletion_failure(self): + ctx = mock.ANY + with self.packet_filter_on_network() as pf: + pf_id = pf['packet_filter']['id'] + + self.ofc.set_raise_exc('delete_ofc_packet_filter', + nexc.OFCException(reason='hoge')) + + data = {'packet_filter': {'admin_state_up': False}} + self._update('packet_filters', pf_id, data) + + pf_ref = self._show('packet_filters', pf_id) + self.assertEqual(pf_ref['packet_filter']['status'], 'ERROR') + + self.ofc.set_raise_exc('delete_ofc_packet_filter', None) + + data = {'packet_filter': {'priority': 1000}} + self._update('packet_filters', pf_id, data) + + pf_ref = self._show('packet_filters', pf_id) + self.assertEqual(pf_ref['packet_filter']['status'], 'DOWN') + + ctx = mock.ANY + pf_dict = mock.ANY + expected = [ + mock.call.exists_ofc_packet_filter(ctx, pf_id), + mock.call.create_ofc_packet_filter(ctx, pf_id, pf_dict), + + mock.call.exists_ofc_packet_filter(ctx, pf_id), + mock.call.delete_ofc_packet_filter(ctx, pf_id), + + mock.call.exists_ofc_packet_filter(ctx, pf_id), + mock.call.delete_ofc_packet_filter(ctx, pf_id), + + mock.call.exists_ofc_packet_filter(ctx, pf_id), + ] + self.ofc.assert_has_calls(expected) + self.assertEqual(self.ofc.delete_ofc_packet_filter.call_count, 2) + + def test_delete_pf_with_ofc_deletion_failure(self): + self.ofc.set_raise_exc('delete_ofc_packet_filter', + nexc.OFCException(reason='hoge')) + + with self.packet_filter_on_network() as pf: + pf_id = pf['packet_filter']['id'] + + self._delete('packet_filters', pf_id, + expected_code=webob.exc.HTTPInternalServerError.code) + + pf_ref = self._show('packet_filters', pf_id) + self.assertEqual(pf_ref['packet_filter']['status'], 'ERROR') + + self.ofc.set_raise_exc('delete_ofc_packet_filter', None) + # Then, self._delete('packet_filters', pf_id) will success. + + ctx = mock.ANY + pf_dict = mock.ANY + expected = [ + mock.call.exists_ofc_packet_filter(ctx, pf_id), + mock.call.create_ofc_packet_filter(ctx, pf_id, pf_dict), + + mock.call.exists_ofc_packet_filter(ctx, pf_id), + mock.call.delete_ofc_packet_filter(ctx, pf_id), + + mock.call.exists_ofc_packet_filter(ctx, pf_id), + mock.call.delete_ofc_packet_filter(ctx, pf_id), + ] + self.ofc.assert_has_calls(expected) + self.assertEqual(self.ofc.delete_ofc_packet_filter.call_count, 2) + + def test_auto_delete_pf_in_network_deletion(self): + with self.packet_filter_on_network(admin_state_up=False, + do_delete=False) as pf: + pf_id = pf['packet_filter']['id'] + + self._show('packet_filters', pf_id, + expected_code=webob.exc.HTTPNotFound.code)