Adds FWaaS service plugin for Cisco CSR1kv
Support for Cisco CSR1kv Firewall: Add API extensions and associated db for insertion of FW service on Cisco CSR1kv router. There are no changes to the reference implementation model for FWaaS rules and policies. Change-Id: I830c78d1b538da632e3dfd7d78ff82a81d534208 Implements: blueprint fwaas-cisco
This commit is contained in:
0
neutron_fwaas/db/cisco/__init__.py
Normal file
0
neutron_fwaas/db/cisco/__init__.py
Normal file
65
neutron_fwaas/db/cisco/cisco_fwaas_db.py
Normal file
65
neutron_fwaas/db/cisco/cisco_fwaas_db.py
Normal file
@@ -0,0 +1,65 @@
|
||||
# Copyright 2015 Cisco Systems, Inc.
|
||||
#
|
||||
# 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 sqlalchemy as sa
|
||||
|
||||
from neutron.common import log
|
||||
from neutron.db import model_base
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CiscoFirewallAssociation(model_base.BASEV2):
|
||||
|
||||
"""Represents FW association with CSR interface and attributes"""
|
||||
__tablename__ = 'cisco_firewall_associations'
|
||||
|
||||
fw_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('firewalls.id', ondelete="CASCADE"),
|
||||
primary_key=True)
|
||||
port_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('ports.id', ondelete="CASCADE"))
|
||||
direction = sa.Column(sa.String(16))
|
||||
acl_id = sa.Column(sa.String(36))
|
||||
router_id = sa.Column(sa.String(36))
|
||||
|
||||
|
||||
class CiscoFirewall_db_mixin(object):
|
||||
|
||||
@log.log
|
||||
def add_firewall_csr_association(self, context, fw):
|
||||
with context.session.begin(subtransactions=True):
|
||||
firewall_db = CiscoFirewallAssociation(fw_id=fw['id'],
|
||||
port_id=fw['port_id'],
|
||||
direction=fw['direction'],
|
||||
acl_id=fw['acl_id'],
|
||||
router_id=fw['router_id'])
|
||||
context.session.add(firewall_db)
|
||||
|
||||
@log.log
|
||||
def lookup_firewall_csr_association(self, context, fwid):
|
||||
with context.session.begin(subtransactions=True):
|
||||
csr_fw_qry = context.session.query(CiscoFirewallAssociation)
|
||||
csr_fw = csr_fw_qry.filter_by(fw_id=fwid).first()
|
||||
return csr_fw
|
||||
|
||||
@log.log
|
||||
def update_firewall_csr_association(self, context, fwid, firewall):
|
||||
with context.session.begin(subtransactions=True):
|
||||
csr_fw_qry = context.session.query(CiscoFirewallAssociation)
|
||||
csr_fw = csr_fw_qry.filter_by(fw_id=fwid).first()
|
||||
csr_fw.update(firewall)
|
||||
return firewall
|
||||
@@ -0,0 +1,51 @@
|
||||
# Copyright 2015 OpenStack Foundation
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
"""cisco_csr_fwaas
|
||||
|
||||
Revision ID: 796c68dffbb
|
||||
Revises: 540142f314f4
|
||||
Create Date: 2015-02-02 13:11:55.184112
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '796c68dffbb'
|
||||
down_revision = '540142f314f4'
|
||||
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade(active_plugins=None, options=None):
|
||||
|
||||
op.create_table('cisco_firewall_associations',
|
||||
sa.Column('fw_id', sa.String(length=36), nullable=False),
|
||||
sa.Column('port_id', sa.String(length=36), nullable=True),
|
||||
sa.Column('direction', sa.String(length=16), nullable=True),
|
||||
sa.Column('acl_id', sa.String(length=36), nullable=True),
|
||||
sa.Column('router_id', sa.String(length=36), nullable=True),
|
||||
sa.ForeignKeyConstraint(['fw_id'], ['firewalls.id'],
|
||||
ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['port_id'], ['ports.id'],
|
||||
ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('fw_id')
|
||||
)
|
||||
|
||||
|
||||
def downgrade(active_plugins=None, options=None):
|
||||
|
||||
op.drop_table('cisco_firewall_associations')
|
||||
@@ -1 +1 @@
|
||||
540142f314f4
|
||||
796c68dffbb
|
||||
|
||||
0
neutron_fwaas/extensions/cisco/__init__.py
Normal file
0
neutron_fwaas/extensions/cisco/__init__.py
Normal file
80
neutron_fwaas/extensions/cisco/csr_firewall_insertion.py
Normal file
80
neutron_fwaas/extensions/cisco/csr_firewall_insertion.py
Normal file
@@ -0,0 +1,80 @@
|
||||
# Copyright 2015 Cisco Systems, Inc. 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.
|
||||
|
||||
from neutron.api import extensions
|
||||
from neutron.api.v2 import attributes as attr
|
||||
from neutron.common import exceptions as excp
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class InvalidInterfaceForCSRFW(excp.NotFound):
|
||||
message = _("Interface id %(port_id)s provided "
|
||||
"not valid for Cisco CSR Firewall")
|
||||
|
||||
|
||||
class InvalidRouterAssociationForCSRFW(excp.InvalidInput):
|
||||
message = _("Port id %(port_id)s provided "
|
||||
"for Cisco CSR Firewall associated with different Router")
|
||||
|
||||
|
||||
class InvalidRouterHostingInfoForCSRFW(excp.NotFound):
|
||||
message = _("Interface id %(port_id)s provided "
|
||||
"does not have Hosting Info for Cisco CSR Firewall")
|
||||
|
||||
|
||||
csr_firewall_direction = ['inside', 'outside', 'both']
|
||||
|
||||
EXTENDED_ATTRIBUTES_2_0 = {
|
||||
'firewalls': {
|
||||
'port_id': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:uuid': None},
|
||||
'is_visible': True, 'default': attr.ATTR_NOT_SPECIFIED},
|
||||
'direction': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:values': csr_firewall_direction},
|
||||
'is_visible': True, 'default': attr.ATTR_NOT_SPECIFIED},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Csr_firewall_insertion(extensions.ExtensionDescriptor):
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return "CSR Firewall insertion"
|
||||
|
||||
@classmethod
|
||||
def get_alias(cls):
|
||||
return "csrfirewallinsertion"
|
||||
|
||||
@classmethod
|
||||
def get_description(cls):
|
||||
return "Firewall insertion for Cisco CSR"
|
||||
|
||||
@classmethod
|
||||
def get_namespace(cls):
|
||||
return ("http://docs.openstack.org/ext/neutron/"
|
||||
"csrfirewallinsertion/api/v1.0")
|
||||
|
||||
@classmethod
|
||||
def get_updated(cls):
|
||||
return "2014-08-13T10:00:00-00:00"
|
||||
|
||||
def get_extended_resources(self, version):
|
||||
if version == "2.0":
|
||||
return EXTENDED_ATTRIBUTES_2_0
|
||||
else:
|
||||
return {}
|
||||
0
neutron_fwaas/services/firewall/plugins/__init__.py
Normal file
0
neutron_fwaas/services/firewall/plugins/__init__.py
Normal file
@@ -0,0 +1,376 @@
|
||||
# Copyright 2015 Cisco Systems, Inc. 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.
|
||||
#
|
||||
|
||||
from neutron.api.v2 import attributes as attr
|
||||
from neutron.common import constants as l3_const
|
||||
from neutron.common import log
|
||||
from neutron.common import rpc as n_rpc
|
||||
from neutron import context as neutron_context
|
||||
from neutron.i18n import _LW
|
||||
from neutron import manager
|
||||
from neutron.plugins.common import constants as const
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
import oslo_messaging
|
||||
|
||||
from neutron_fwaas.db.cisco import cisco_fwaas_db as csrfw_db
|
||||
import neutron_fwaas.extensions
|
||||
from neutron_fwaas.extensions.cisco import csr_firewall_insertion as csr_ext
|
||||
from neutron_fwaas.services.firewall import fwaas_plugin as ref_fw_plugin
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FirewallCallbacks(object):
|
||||
|
||||
target = oslo_messaging.Target(version='1.0')
|
||||
|
||||
def __init__(self, plugin):
|
||||
super(FirewallCallbacks, self).__init__()
|
||||
self.plugin = plugin
|
||||
|
||||
@log.log
|
||||
def set_firewall_status(self, context, firewall_id, status,
|
||||
status_data=None, **kwargs):
|
||||
"""Agent uses this to set a firewall's status."""
|
||||
with context.session.begin(subtransactions=True):
|
||||
fw_db = self.plugin._get_firewall(context, firewall_id)
|
||||
# ignore changing status if firewall expects to be deleted
|
||||
# That case means that while some pending operation has been
|
||||
# performed on the backend, neutron server received delete request
|
||||
# and changed firewall status to const.PENDING_DELETE
|
||||
if status == const.ERROR:
|
||||
fw_db.status = const.ERROR
|
||||
return False
|
||||
if fw_db.status == const.PENDING_DELETE:
|
||||
LOG.debug("Firewall %(fw_id)s in PENDING_DELETE state, "
|
||||
"not changing to %(status)s",
|
||||
{'fw_id': firewall_id, 'status': status})
|
||||
return False
|
||||
if status in (const.ACTIVE, const.INACTIVE):
|
||||
fw_db.status = status
|
||||
csrfw = self.plugin.lookup_firewall_csr_association(context,
|
||||
firewall_id)
|
||||
_fw = {'id': csrfw['fw_id'], 'port_id': csrfw['port_id'],
|
||||
'direction': csrfw['direction'],
|
||||
'acl_id': status_data['acl_id']}
|
||||
self.plugin.update_firewall_csr_association(context,
|
||||
firewall_id, _fw)
|
||||
else:
|
||||
fw_db.status = const.ERROR
|
||||
|
||||
@log.log
|
||||
def firewall_deleted(self, context, firewall_id, **kwargs):
|
||||
"""Agent uses this to indicate firewall is deleted."""
|
||||
with context.session.begin(subtransactions=True):
|
||||
fw_db = self.plugin._get_firewall(context, firewall_id)
|
||||
# allow to delete firewalls in ERROR state
|
||||
if fw_db.status in (const.PENDING_DELETE, const.ERROR):
|
||||
self.plugin.delete_db_firewall_object(context, firewall_id)
|
||||
return True
|
||||
LOG.warn(_LW('Firewall %(fw)s unexpectedly deleted by agent, '
|
||||
'status was %(status)s'),
|
||||
{'fw': firewall_id, 'status': fw_db.status})
|
||||
fw_db.status = const.ERROR
|
||||
return False
|
||||
|
||||
@log.log
|
||||
def get_firewalls_for_tenant(self, context, **kwargs):
|
||||
"""Agent uses this to get all firewalls and rules for a tenant."""
|
||||
fw_list = []
|
||||
for fw in self.plugin.get_firewalls(context):
|
||||
fw_with_rules = (
|
||||
self.plugin._make_firewall_dict_with_rules(context, fw['id']))
|
||||
csrfw = self.plugin.lookup_firewall_csr_association(context,
|
||||
fw['id'])
|
||||
router_id = csrfw['router_id']
|
||||
fw_with_rules['vendor_ext'] = self.plugin._get_hosting_info(
|
||||
context, csrfw['port_id'], router_id, csrfw['direction'])
|
||||
fw_with_rules['vendor_ext']['acl_id'] = csrfw['acl_id']
|
||||
fw_list.append(fw_with_rules)
|
||||
return fw_list
|
||||
|
||||
@log.log
|
||||
def get_firewalls_for_tenant_without_rules(self, context, **kwargs):
|
||||
"""Agent uses this to get all firewalls for a tenant."""
|
||||
return [fw for fw in self.plugin.get_firewalls(context)]
|
||||
|
||||
@log.log
|
||||
def get_tenants_with_firewalls(self, context, **kwargs):
|
||||
"""Agent uses this to get all tenants that have firewalls."""
|
||||
ctx = neutron_context.get_admin_context()
|
||||
fw_list = self.plugin.get_firewalls(ctx)
|
||||
return list(set(fw['tenant_id'] for fw in fw_list))
|
||||
|
||||
|
||||
class FirewallAgentApi(object):
|
||||
"""Plugin side of plugin to agent RPC API."""
|
||||
|
||||
def __init__(self, topic, host):
|
||||
self.host = host
|
||||
target = oslo_messaging.Target(topic=topic, version='1.0')
|
||||
self.client = n_rpc.get_client(target)
|
||||
|
||||
def create_firewall(self, context, firewall):
|
||||
cctxt = self.client.prepare(fanout=True)
|
||||
cctxt.cast(context, 'create_firewall', firewall=firewall,
|
||||
host=self.host)
|
||||
|
||||
def update_firewall(self, context, firewall):
|
||||
cctxt = self.client.prepare(fanout=True)
|
||||
cctxt.cast(context, 'update_firewall', firewall=firewall,
|
||||
host=self.host)
|
||||
|
||||
def delete_firewall(self, context, firewall):
|
||||
cctxt = self.client.prepare(fanout=True)
|
||||
cctxt.cast(context, 'delete_firewall', firewall=firewall,
|
||||
host=self.host)
|
||||
|
||||
|
||||
class CSRFirewallPlugin(ref_fw_plugin.FirewallPlugin,
|
||||
csrfw_db.CiscoFirewall_db_mixin):
|
||||
|
||||
"""Implementation of the Neutron Firewall Service Plugin.
|
||||
|
||||
This class implements the Cisco CSR FWaaS Service Plugin,
|
||||
inherits from the fwaas ref plugin as no changes are made
|
||||
to handling fwaas policy and rules. The CRUD methods are
|
||||
overridden to provide for the specific implementation. The
|
||||
basic fwaas db is managed thru the firewall_db.Firewall_db_mixin.
|
||||
The backend specific associations are captured in the new table,
|
||||
csrfw_db.CiscoFirewall_db_mixin.
|
||||
"""
|
||||
supported_extension_aliases = ["fwaas", "csrfirewallinsertion"]
|
||||
|
||||
def __init__(self):
|
||||
"""Do the initialization for the firewall service plugin here."""
|
||||
|
||||
ext_path = neutron_fwaas.extensions.__path__[0] + '/cisco'
|
||||
if ext_path not in cfg.CONF.api_extensions_path.split(':'):
|
||||
cfg.CONF.set_override('api_extensions_path',
|
||||
cfg.CONF.api_extensions_path + ':' + ext_path)
|
||||
|
||||
self.endpoints = [FirewallCallbacks(self)]
|
||||
|
||||
self.conn = n_rpc.create_connection(new=True)
|
||||
self.conn.create_consumer(
|
||||
'CISCO_FW_PLUGIN', self.endpoints, fanout=False)
|
||||
self.conn.consume_in_threads()
|
||||
|
||||
self.agent_rpc = FirewallAgentApi(
|
||||
'CISCO_FW',
|
||||
cfg.CONF.host
|
||||
)
|
||||
|
||||
def _rpc_update_firewall(self, context, firewall_id):
|
||||
status_update = {"firewall": {"status": const.PENDING_UPDATE}}
|
||||
fw = super(ref_fw_plugin.FirewallPlugin, self).update_firewall(
|
||||
context, firewall_id, status_update)
|
||||
if fw:
|
||||
fw_with_rules = (
|
||||
self._make_firewall_dict_with_rules(context,
|
||||
firewall_id))
|
||||
csrfw = self.lookup_firewall_csr_association(context, firewall_id)
|
||||
fw_with_rules['vendor_ext'] = self._get_hosting_info(context,
|
||||
csrfw['port_id'], csrfw['router_id'], csrfw['direction'])
|
||||
fw_with_rules['vendor_ext']['acl_id'] = csrfw['acl_id']
|
||||
LOG.debug("Update of Rule or policy: fw_with_rules: %s",
|
||||
fw_with_rules)
|
||||
self.agent_rpc.update_firewall(context, fw_with_rules)
|
||||
|
||||
@log.log
|
||||
def _validate_fw_port_and_get_router_id(self, context, tenant_id, port_id):
|
||||
# port validation with router plugin
|
||||
l3_plugin = manager.NeutronManager.get_service_plugins().get(
|
||||
const.L3_ROUTER_NAT)
|
||||
ctx = neutron_context.get_admin_context()
|
||||
routers = l3_plugin.get_routers(ctx)
|
||||
router_ids = [
|
||||
router['id']
|
||||
for router in routers
|
||||
if router['tenant_id'] == tenant_id]
|
||||
port_db = self._core_plugin._get_port(context, port_id)
|
||||
if not (port_db['device_id'] in router_ids and
|
||||
port_db['device_owner'] == l3_const.DEVICE_OWNER_ROUTER_INTF):
|
||||
raise csr_ext.InvalidInterfaceForCSRFW(port_id=port_id)
|
||||
return port_db['device_id']
|
||||
|
||||
def _map_csr_device_info_for_agent(self, hosting_device):
|
||||
return {'host_mngt_ip': hosting_device['management_ip_address'],
|
||||
'host_usr_nm': hosting_device['credentials']['username'],
|
||||
'host_usr_pw': hosting_device['credentials']['password']}
|
||||
|
||||
def _get_service_insertion_points(self, context, interfaces, port_id,
|
||||
direction):
|
||||
insertion_point = dict()
|
||||
hosting_info = dict()
|
||||
for interface in interfaces:
|
||||
if interface['id'] == port_id:
|
||||
hosting_info = interface['hosting_info']
|
||||
if not hosting_info:
|
||||
raise csr_ext.InvalidRouterHostingInfoForCSRFW(port_id=port_id)
|
||||
insertion_point['port'] = {'id': port_id,
|
||||
'hosting_info': hosting_info}
|
||||
insertion_point['direction'] = direction
|
||||
return [insertion_point]
|
||||
|
||||
def _get_hosting_info(self, context, port_id, router_id, direction):
|
||||
l3_plugin = manager.NeutronManager.get_service_plugins().get(
|
||||
const.L3_ROUTER_NAT)
|
||||
ctx = neutron_context.get_admin_context()
|
||||
routers = l3_plugin.get_sync_data_ext(ctx)
|
||||
for router in routers:
|
||||
if router['id'] == router_id:
|
||||
vendor_ext = self._map_csr_device_info_for_agent(
|
||||
router['hosting_device'])
|
||||
vendor_ext['if_list'] = self._get_service_insertion_points(
|
||||
context, router['_interfaces'], port_id, direction)
|
||||
return vendor_ext
|
||||
# TODO(sridar): we may need to raise an excp - check backlogging
|
||||
|
||||
@log.log
|
||||
def create_firewall(self, context, firewall):
|
||||
tenant_id = self._get_tenant_id_for_create(context,
|
||||
firewall['firewall'])
|
||||
port_id = firewall['firewall'].pop('port_id', None)
|
||||
direction = firewall['firewall'].pop('direction', None)
|
||||
|
||||
if port_id == attr.ATTR_NOT_SPECIFIED:
|
||||
LOG.debug("create_firewall() called")
|
||||
port_id = None
|
||||
router_id = None
|
||||
else:
|
||||
# TODO(sridar): add check to see if the new port-id does not have
|
||||
# any associated firewall.
|
||||
router_id = self._validate_fw_port_and_get_router_id(context,
|
||||
tenant_id, port_id)
|
||||
|
||||
if direction == attr.ATTR_NOT_SPECIFIED:
|
||||
direction = None
|
||||
|
||||
firewall['firewall']['status'] = const.PENDING_CREATE
|
||||
fw = super(ref_fw_plugin.FirewallPlugin, self).create_firewall(
|
||||
context, firewall)
|
||||
fw_with_rules = (
|
||||
self._make_firewall_dict_with_rules(context, fw['id']))
|
||||
|
||||
if not port_id and not direction:
|
||||
return fw
|
||||
|
||||
# Add entry into firewall associations table
|
||||
_fw = {'id': fw['id'], 'port_id': port_id,
|
||||
'direction': direction, 'router_id': router_id, 'acl_id': None}
|
||||
self.add_firewall_csr_association(context, _fw)
|
||||
|
||||
if port_id and direction:
|
||||
fw_with_rules['vendor_ext'] = self._get_hosting_info(context,
|
||||
port_id, router_id, direction)
|
||||
fw_with_rules['vendor_ext']['acl_id'] = None
|
||||
|
||||
self.agent_rpc.create_firewall(context, fw_with_rules)
|
||||
return fw
|
||||
|
||||
@log.log
|
||||
def update_firewall(self, context, fwid, firewall):
|
||||
self._ensure_update_firewall(context, fwid)
|
||||
tenant_id = self._get_tenant_id_for_create(context,
|
||||
firewall['firewall'])
|
||||
csrfw = self.lookup_firewall_csr_association(context, fwid)
|
||||
|
||||
port_id = firewall['firewall'].pop('port_id', None)
|
||||
direction = firewall['firewall'].pop('direction', None)
|
||||
|
||||
_fw = {'id': fwid}
|
||||
|
||||
if port_id:
|
||||
router_id = self._validate_fw_port_and_get_router_id(context,
|
||||
tenant_id, port_id)
|
||||
if csrfw and csrfw['port_id']:
|
||||
# TODO(sridar): add check to see if the new port_id does not
|
||||
# have any associated firewall.
|
||||
|
||||
# we only support a different port if associated
|
||||
# with the same router
|
||||
if router_id != csrfw['router_id']:
|
||||
raise csr_ext.InvalidRouterAssociationForCSRFW(
|
||||
port_id=port_id)
|
||||
_fw['port_id'] = port_id
|
||||
_fw['router_id'] = router_id
|
||||
else:
|
||||
_fw['port_id'] = csrfw['port_id'] if csrfw else None
|
||||
_fw['router_id'] = csrfw['router_id'] if csrfw else None
|
||||
|
||||
if direction:
|
||||
_fw['direction'] = direction
|
||||
else:
|
||||
_fw['direction'] = csrfw['direction'] if csrfw else None
|
||||
|
||||
_fw['acl_id'] = csrfw['acl_id'] if csrfw else None
|
||||
|
||||
self.update_firewall_csr_association(context, fwid, _fw)
|
||||
|
||||
firewall['firewall']['status'] = const.PENDING_UPDATE
|
||||
|
||||
fw = super(ref_fw_plugin.FirewallPlugin, self).update_firewall(
|
||||
context, fwid, firewall)
|
||||
fw_with_rules = (
|
||||
self._make_firewall_dict_with_rules(context, fw['id']))
|
||||
|
||||
if _fw['port_id'] and _fw['direction']:
|
||||
|
||||
fw_with_rules['vendor_ext'] = self._get_hosting_info(context,
|
||||
port_id, csrfw['router_id'], direction)
|
||||
fw_with_rules['vendor_ext']['acl_id'] = csrfw['acl_id']
|
||||
LOG.debug("CSR Plugin update: fw_with_rules: %s", fw_with_rules)
|
||||
self.agent_rpc.update_firewall(context, fw_with_rules)
|
||||
return fw
|
||||
|
||||
@log.log
|
||||
def delete_firewall(self, context, fwid):
|
||||
self._ensure_update_firewall(context, fwid)
|
||||
|
||||
status_update = {"firewall": {"status": const.PENDING_DELETE}}
|
||||
fw = super(ref_fw_plugin.FirewallPlugin, self).update_firewall(
|
||||
context, fwid, status_update)
|
||||
|
||||
# given that we are not in a PENDING_CREATE we should have
|
||||
# an acl_id - since it is not present something bad has happened
|
||||
# on the backend and no sense in sending a msg to the agent.
|
||||
# Clean up ...
|
||||
csrfw = self.lookup_firewall_csr_association(context, fwid)
|
||||
if not csrfw or not csrfw['acl_id']:
|
||||
self.delete_db_firewall_object(context, fwid)
|
||||
return
|
||||
|
||||
fw_with_rules = (
|
||||
self._make_firewall_dict_with_rules(context, fw['id']))
|
||||
|
||||
fw_with_rules['vendor_ext'] = self._get_hosting_info(context,
|
||||
csrfw['port_id'], csrfw['router_id'], csrfw['direction'])
|
||||
fw_with_rules['vendor_ext']['acl_id'] = csrfw['acl_id']
|
||||
|
||||
self.agent_rpc.delete_firewall(context, fw_with_rules)
|
||||
|
||||
@log.log
|
||||
def get_firewall(self, context, fwid, fields=None):
|
||||
res = super(ref_fw_plugin.FirewallPlugin, self).get_firewall(
|
||||
context, fwid, fields)
|
||||
csrfw = self.lookup_firewall_csr_association(context, res['id'])
|
||||
if not csrfw:
|
||||
return res
|
||||
res['port_id'] = csrfw['port_id']
|
||||
res['direction'] = csrfw['direction']
|
||||
res['router_id'] = csrfw['router_id']
|
||||
return res
|
||||
@@ -0,0 +1,431 @@
|
||||
# Copyright 2015 Cisco Systems, Inc. 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 contextlib
|
||||
import mock
|
||||
|
||||
from neutron.api.v2 import attributes as attr
|
||||
from neutron import context
|
||||
from neutron import manager
|
||||
from neutron.plugins.common import constants as const
|
||||
from neutron.tests.unit import test_l3_plugin
|
||||
from neutron.tests.unit import testlib_plugin
|
||||
|
||||
import neutron_fwaas
|
||||
from neutron_fwaas.db.cisco import cisco_fwaas_db as csrfw_db
|
||||
from neutron_fwaas.extensions.cisco import csr_firewall_insertion
|
||||
from neutron_fwaas.extensions import firewall
|
||||
from neutron_fwaas.services.firewall.plugins.cisco import cisco_fwaas_plugin
|
||||
from neutron_fwaas.tests.unit.db.firewall import test_db_firewall
|
||||
from oslo_config import cfg
|
||||
|
||||
# We need the test_l3_plugin to ensure we have a valid port_id corresponding
|
||||
# to a router interface.
|
||||
CORE_PLUGIN_KLASS = 'neutron.tests.unit.test_l3_plugin.TestNoL3NatPlugin'
|
||||
L3_PLUGIN_KLASS = 'neutron.tests.unit.test_l3_plugin.TestL3NatServicePlugin'
|
||||
# the plugin under test
|
||||
CSR_FW_PLUGIN_KLASS = (
|
||||
"neutron_fwaas.services.firewall.plugins.cisco.cisco_fwaas_plugin."
|
||||
"CSRFirewallPlugin"
|
||||
)
|
||||
extensions_path = neutron_fwaas.extensions.__path__[0] + '/cisco'
|
||||
|
||||
|
||||
class CSR1kvFirewallTestExtensionManager(
|
||||
test_l3_plugin.L3TestExtensionManager):
|
||||
|
||||
def get_resources(self):
|
||||
res = super(CSR1kvFirewallTestExtensionManager, self).get_resources()
|
||||
firewall.RESOURCE_ATTRIBUTE_MAP['firewalls'].update(
|
||||
csr_firewall_insertion.EXTENDED_ATTRIBUTES_2_0['firewalls'])
|
||||
return res + firewall.Firewall.get_resources()
|
||||
|
||||
def get_actions(self):
|
||||
return []
|
||||
|
||||
def get_request_extensions(self):
|
||||
return []
|
||||
|
||||
|
||||
class CSR1kvFirewallTestCaseBase(test_db_firewall.FirewallPluginDbTestCase,
|
||||
testlib_plugin.NotificationSetupHelper,
|
||||
test_l3_plugin.L3NatTestCaseMixin):
|
||||
|
||||
def setUp(self, core_plugin=None, l3_plugin=None, fw_plugin=None,
|
||||
ext_mgr=None):
|
||||
self.agentapi_delf_p = mock.patch(test_db_firewall.DELETEFW_PATH,
|
||||
create=True, new=test_db_firewall.FakeAgentApi().delete_firewall)
|
||||
self.agentapi_delf_p.start()
|
||||
cfg.CONF.set_override('api_extensions_path', extensions_path)
|
||||
# for these tests we need to enable overlapping ips
|
||||
cfg.CONF.set_default('allow_overlapping_ips', True)
|
||||
cfg.CONF.set_default('max_routes', 3)
|
||||
self.saved_attr_map = {}
|
||||
for resource, attrs in attr.RESOURCE_ATTRIBUTE_MAP.iteritems():
|
||||
self.saved_attr_map[resource] = attrs.copy()
|
||||
if not core_plugin:
|
||||
core_plugin = CORE_PLUGIN_KLASS
|
||||
if l3_plugin is None:
|
||||
l3_plugin = L3_PLUGIN_KLASS
|
||||
if not fw_plugin:
|
||||
fw_plugin = CSR_FW_PLUGIN_KLASS
|
||||
service_plugins = {'l3_plugin_name': l3_plugin,
|
||||
'fw_plugin_name': fw_plugin}
|
||||
if not ext_mgr:
|
||||
ext_mgr = CSR1kvFirewallTestExtensionManager()
|
||||
super(test_db_firewall.FirewallPluginDbTestCase, self).setUp(
|
||||
plugin=core_plugin, service_plugins=service_plugins,
|
||||
ext_mgr=ext_mgr)
|
||||
|
||||
self.core_plugin = manager.NeutronManager.get_plugin()
|
||||
self.l3_plugin = manager.NeutronManager.get_service_plugins().get(
|
||||
const.L3_ROUTER_NAT)
|
||||
self.plugin = manager.NeutronManager.get_service_plugins().get(
|
||||
const.FIREWALL)
|
||||
self.callbacks = self.plugin.endpoints[0]
|
||||
|
||||
self.setup_notification_driver()
|
||||
|
||||
def restore_attribute_map(self):
|
||||
# Remove the csrfirewallinsertion extension
|
||||
firewall.RESOURCE_ATTRIBUTE_MAP['firewalls'].pop('port_id')
|
||||
firewall.RESOURCE_ATTRIBUTE_MAP['firewalls'].pop('direction')
|
||||
# Restore the original RESOURCE_ATTRIBUTE_MAP
|
||||
attr.RESOURCE_ATTRIBUTE_MAP = self.saved_attr_map
|
||||
|
||||
def tearDown(self):
|
||||
self.restore_attribute_map()
|
||||
super(CSR1kvFirewallTestCaseBase, self).tearDown()
|
||||
|
||||
def _create_firewall(self, fmt, name, description, firewall_policy_id,
|
||||
admin_state_up=True, expected_res_status=None,
|
||||
**kwargs):
|
||||
tenant_id = kwargs.get('tenant_id', self._tenant_id)
|
||||
port_id = kwargs.get('port_id')
|
||||
direction = kwargs.get('direction')
|
||||
data = {'firewall': {'name': name,
|
||||
'description': description,
|
||||
'firewall_policy_id': firewall_policy_id,
|
||||
'admin_state_up': admin_state_up,
|
||||
'tenant_id': tenant_id}}
|
||||
if port_id:
|
||||
data['firewall']['port_id'] = port_id
|
||||
if direction:
|
||||
data['firewall']['direction'] = direction
|
||||
firewall_req = self.new_create_request('firewalls', data, fmt)
|
||||
firewall_res = firewall_req.get_response(self.ext_api)
|
||||
if expected_res_status:
|
||||
self.assertEqual(expected_res_status, firewall_res.status_int)
|
||||
return firewall_res
|
||||
|
||||
|
||||
class TestCiscoFirewallCallbacks(test_db_firewall.FirewallPluginDbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestCiscoFirewallCallbacks, self).setUp()
|
||||
self.plugin = cisco_fwaas_plugin.CSRFirewallPlugin()
|
||||
self.callbacks = self.plugin.endpoints[0]
|
||||
|
||||
def test_firewall_deleted(self):
|
||||
ctx = context.get_admin_context()
|
||||
with self.firewall_policy(do_delete=False) as fwp:
|
||||
fwp_id = fwp['firewall_policy']['id']
|
||||
with self.firewall(firewall_policy_id=fwp_id,
|
||||
admin_state_up=test_db_firewall.ADMIN_STATE_UP,
|
||||
do_delete=False) as fw:
|
||||
fw_id = fw['firewall']['id']
|
||||
with ctx.session.begin(subtransactions=True):
|
||||
fw_db = self.plugin._get_firewall(ctx, fw_id)
|
||||
fw_db['status'] = const.PENDING_DELETE
|
||||
ctx.session.flush()
|
||||
res = self.callbacks.firewall_deleted(ctx, fw_id,
|
||||
host='dummy')
|
||||
self.assertTrue(res)
|
||||
self.assertRaises(firewall.FirewallNotFound,
|
||||
self.plugin.get_firewall,
|
||||
ctx, fw_id)
|
||||
|
||||
|
||||
class TestCiscoFirewallPlugin(CSR1kvFirewallTestCaseBase,
|
||||
csrfw_db.CiscoFirewall_db_mixin):
|
||||
|
||||
def setUp(self):
|
||||
super(TestCiscoFirewallPlugin, self).setUp()
|
||||
self.fake_vendor_ext = {
|
||||
'host_mngt_ip': '1.2.3.4',
|
||||
'host_usr_nm': 'admin',
|
||||
'host_usr_pw': 'cisco',
|
||||
'if_list': {'port': {'id': 0, 'hosting_info': 'csr'},
|
||||
'direction': 'default'}
|
||||
}
|
||||
self.mock_get_hosting_info = mock.patch.object(
|
||||
self.plugin, '_get_hosting_info').start()
|
||||
|
||||
def test_create_csr_firewall(self):
|
||||
|
||||
with contextlib.nested(
|
||||
self.router(tenant_id=self._tenant_id),
|
||||
self.subnet(),
|
||||
) as (r, s):
|
||||
|
||||
body = self._router_interface_action(
|
||||
'add',
|
||||
r['router']['id'],
|
||||
s['subnet']['id'],
|
||||
None)
|
||||
port_id = body['port_id']
|
||||
|
||||
self.fake_vendor_ext['if_list']['port']['id'] = port_id
|
||||
self.fake_vendor_ext['if_list']['direction'] = 'inside'
|
||||
self.mock_get_hosting_info.return_value = self.fake_vendor_ext
|
||||
|
||||
with self.firewall(port_id=body['port_id'],
|
||||
direction='inside') as fw:
|
||||
ctx = context.get_admin_context()
|
||||
fw_id = fw['firewall']['id']
|
||||
csrfw = self.lookup_firewall_csr_association(
|
||||
ctx, fw_id)
|
||||
# cant be in PENDING_XXX state for delete clean up
|
||||
with ctx.session.begin(subtransactions=True):
|
||||
fw_db = self.plugin._get_firewall(ctx, fw_id)
|
||||
fw_db['status'] = const.ACTIVE
|
||||
ctx.session.flush()
|
||||
self._router_interface_action(
|
||||
'remove',
|
||||
r['router']['id'],
|
||||
s['subnet']['id'],
|
||||
None)
|
||||
|
||||
self.assertEqual('firewall_1', fw['firewall']['name'])
|
||||
self.assertEqual(port_id, csrfw['port_id'])
|
||||
self.assertEqual('inside', csrfw['direction'])
|
||||
|
||||
def test_create_csr_firewall_only_port_id_specified(self):
|
||||
|
||||
with contextlib.nested(
|
||||
self.router(tenant_id=self._tenant_id),
|
||||
self.subnet(),
|
||||
) as (r, s):
|
||||
|
||||
body = self._router_interface_action(
|
||||
'add',
|
||||
r['router']['id'],
|
||||
s['subnet']['id'],
|
||||
None)
|
||||
port_id = body['port_id']
|
||||
|
||||
self.fake_vendor_ext['if_list']['port']['id'] = port_id
|
||||
self.fake_vendor_ext['if_list']['direction'] = None
|
||||
self.mock_get_hosting_info.return_value = self.fake_vendor_ext
|
||||
|
||||
with self.firewall(port_id=body['port_id']) as fw:
|
||||
ctx = context.get_admin_context()
|
||||
fw_id = fw['firewall']['id']
|
||||
csrfw = self.lookup_firewall_csr_association(
|
||||
ctx, fw_id)
|
||||
# cant be in PENDING_XXX state for delete clean up
|
||||
with ctx.session.begin(subtransactions=True):
|
||||
fw_db = self.plugin._get_firewall(ctx, fw_id)
|
||||
fw_db['status'] = const.ACTIVE
|
||||
ctx.session.flush()
|
||||
self._router_interface_action(
|
||||
'remove',
|
||||
r['router']['id'],
|
||||
s['subnet']['id'],
|
||||
None)
|
||||
|
||||
self.assertEqual('firewall_1', fw['firewall']['name'])
|
||||
self.assertEqual(port_id, csrfw['port_id'])
|
||||
self.assertEqual(None, csrfw['direction'])
|
||||
|
||||
def test_create_csr_firewall_no_port_id_no_direction_specified(self):
|
||||
|
||||
with self.firewall() as fw:
|
||||
ctx = context.get_admin_context()
|
||||
fw_id = fw['firewall']['id']
|
||||
csrfw = self.lookup_firewall_csr_association(
|
||||
ctx, fw_id)
|
||||
# cant be in PENDING_XXX state for delete clean up
|
||||
with ctx.session.begin(subtransactions=True):
|
||||
fw_db = self.plugin._get_firewall(ctx, fw_id)
|
||||
fw_db['status'] = const.ACTIVE
|
||||
ctx.session.flush()
|
||||
|
||||
self.assertEqual('firewall_1', fw['firewall']['name'])
|
||||
self.assertEqual(None, csrfw)
|
||||
|
||||
def test_update_csr_firewall(self):
|
||||
|
||||
with contextlib.nested(
|
||||
self.router(tenant_id=self._tenant_id),
|
||||
self.subnet(),
|
||||
) as (r, s):
|
||||
|
||||
body = self._router_interface_action(
|
||||
'add',
|
||||
r['router']['id'],
|
||||
s['subnet']['id'],
|
||||
None)
|
||||
port_id = body['port_id']
|
||||
|
||||
self.fake_vendor_ext['if_list']['port']['id'] = port_id
|
||||
self.fake_vendor_ext['if_list']['direction'] = 'inside'
|
||||
self.mock_get_hosting_info.return_value = self.fake_vendor_ext
|
||||
|
||||
with self.firewall(port_id=body['port_id'],
|
||||
direction='both') as fw:
|
||||
ctx = context.get_admin_context()
|
||||
fw_id = fw['firewall']['id']
|
||||
csrfw = self.lookup_firewall_csr_association(
|
||||
ctx, fw_id)
|
||||
status_data = {'acl_id': 100}
|
||||
|
||||
res = self.callbacks.set_firewall_status(ctx, fw_id,
|
||||
const.ACTIVE, status_data)
|
||||
|
||||
# update direction on same port
|
||||
data = {'firewall': {'name': 'firewall_2',
|
||||
'direction': 'both', 'port_id': port_id}}
|
||||
req = self.new_update_request('firewalls', data,
|
||||
fw['firewall']['id'])
|
||||
req.environ['neutron.context'] = context.Context(
|
||||
'', 'test-tenant')
|
||||
res = self.deserialize(self.fmt,
|
||||
req.get_response(self.ext_api))
|
||||
|
||||
csrfw = self.lookup_firewall_csr_association(ctx,
|
||||
fw['firewall']['id'])
|
||||
|
||||
self.assertEqual('firewall_2', res['firewall']['name'])
|
||||
self.assertEqual(port_id, csrfw['port_id'])
|
||||
self.assertEqual('both', csrfw['direction'])
|
||||
|
||||
# cant be in PENDING_XXX state for delete clean up
|
||||
with ctx.session.begin(subtransactions=True):
|
||||
fw_db = self.plugin._get_firewall(ctx, fw_id)
|
||||
fw_db['status'] = const.ACTIVE
|
||||
ctx.session.flush()
|
||||
self._router_interface_action(
|
||||
'remove',
|
||||
r['router']['id'],
|
||||
s['subnet']['id'],
|
||||
None)
|
||||
|
||||
def test_update_csr_firewall_port_id(self):
|
||||
|
||||
with contextlib.nested(
|
||||
self.router(tenant_id=self._tenant_id),
|
||||
self.subnet(),
|
||||
self.subnet(cidr='20.0.0.0/24'),
|
||||
) as (r, s1, s2):
|
||||
|
||||
body = self._router_interface_action(
|
||||
'add',
|
||||
r['router']['id'],
|
||||
s1['subnet']['id'],
|
||||
None)
|
||||
port_id1 = body['port_id']
|
||||
|
||||
body = self._router_interface_action(
|
||||
'add',
|
||||
r['router']['id'],
|
||||
s2['subnet']['id'],
|
||||
None)
|
||||
port_id2 = body['port_id']
|
||||
|
||||
self.fake_vendor_ext['if_list']['port']['id'] = port_id1
|
||||
self.fake_vendor_ext['if_list']['direction'] = 'inside'
|
||||
self.mock_get_hosting_info.return_value = self.fake_vendor_ext
|
||||
|
||||
with self.firewall(port_id=port_id1,
|
||||
direction='both') as fw:
|
||||
ctx = context.get_admin_context()
|
||||
fw_id = fw['firewall']['id']
|
||||
status_data = {'acl_id': 100}
|
||||
|
||||
res = self.callbacks.set_firewall_status(ctx, fw_id,
|
||||
const.ACTIVE, status_data)
|
||||
|
||||
# update direction on same port
|
||||
data = {'firewall': {'name': 'firewall_2',
|
||||
'direction': 'both', 'port_id': port_id2}}
|
||||
req = self.new_update_request('firewalls', data,
|
||||
fw['firewall']['id'])
|
||||
req.environ['neutron.context'] = context.Context(
|
||||
'', 'test-tenant')
|
||||
res = self.deserialize(self.fmt,
|
||||
req.get_response(self.ext_api))
|
||||
|
||||
csrfw = self.lookup_firewall_csr_association(ctx,
|
||||
fw['firewall']['id'])
|
||||
|
||||
self.assertEqual('firewall_2', res['firewall']['name'])
|
||||
self.assertEqual(port_id2, csrfw['port_id'])
|
||||
self.assertEqual('both', csrfw['direction'])
|
||||
|
||||
# cant be in PENDING_XXX state for delete clean up
|
||||
with ctx.session.begin(subtransactions=True):
|
||||
fw_db = self.plugin._get_firewall(ctx, fw_id)
|
||||
fw_db['status'] = const.ACTIVE
|
||||
ctx.session.flush()
|
||||
self._router_interface_action('remove',
|
||||
r['router']['id'],
|
||||
s1['subnet']['id'],
|
||||
None)
|
||||
self._router_interface_action(
|
||||
'remove',
|
||||
r['router']['id'],
|
||||
s2['subnet']['id'],
|
||||
None)
|
||||
|
||||
def test_delete_csr_firewall(self):
|
||||
|
||||
with contextlib.nested(
|
||||
self.router(tenant_id=self._tenant_id),
|
||||
self.subnet(),
|
||||
) as (r, s):
|
||||
body = self._router_interface_action(
|
||||
'add',
|
||||
r['router']['id'],
|
||||
s['subnet']['id'],
|
||||
None)
|
||||
port_id = body['port_id']
|
||||
|
||||
self.fake_vendor_ext['if_list']['port']['id'] = port_id
|
||||
self.fake_vendor_ext['if_list']['direction'] = 'inside'
|
||||
self.mock_get_hosting_info.return_value = self.fake_vendor_ext
|
||||
|
||||
with self.firewall(port_id=port_id,
|
||||
direction='inside', do_delete=False) as fw:
|
||||
fw_id = fw['firewall']['id']
|
||||
ctx = context.get_admin_context()
|
||||
csrfw = self.lookup_firewall_csr_association(ctx,
|
||||
fw_id)
|
||||
self.assertNotEqual(None, csrfw)
|
||||
req = self.new_delete_request('firewalls', fw_id)
|
||||
req.get_response(self.ext_api)
|
||||
with ctx.session.begin(subtransactions=True):
|
||||
fw_db = self.plugin._get_firewall(ctx, fw_id)
|
||||
fw_db['status'] = const.PENDING_DELETE
|
||||
ctx.session.flush()
|
||||
self.callbacks.firewall_deleted(ctx, fw_id)
|
||||
csrfw = self.lookup_firewall_csr_association(ctx,
|
||||
fw_id)
|
||||
self.assertEqual(None, csrfw)
|
||||
self._router_interface_action(
|
||||
'remove',
|
||||
r['router']['id'],
|
||||
s['subnet']['id'],
|
||||
None)
|
||||
Reference in New Issue
Block a user