Implements: blueprint quantum-fwaas blueprint: quantum-fwaas-plugin This is the first iteration of the FWaaS implementation and is geared towards implementing the model that will be required to at least address the reference implementation. This iteration will not include implementation of the following features: * grouping or dynamic objects * application/service objects Change-Id: I57a62d6e9d3f1e6c4dd44cd5c745710a3d9e488echanges/04/29004/51
parent
c0d3c5864c
commit
a5a88c7ed3
@ -0,0 +1,16 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# 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.
|
@ -0,0 +1,470 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2013 Big Switch Networks, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, sumitnaiksatam@gmail.com, Big Switch Networks, Inc.
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.ext.orderinglist import ordering_list
|
||||
from sqlalchemy import orm
|
||||
from sqlalchemy.orm import exc
|
||||
|
||||
from neutron.db import db_base_plugin_v2 as base_db
|
||||
from neutron.db import model_base
|
||||
from neutron.db import models_v2
|
||||
from neutron.extensions import firewall
|
||||
from neutron import manager
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.openstack.common import uuidutils
|
||||
from neutron.plugins.common import constants as const
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FirewallRule(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
|
||||
"""Represents a Firewall rule."""
|
||||
__tablename__ = 'firewall_rules'
|
||||
name = sa.Column(sa.String(255))
|
||||
description = sa.Column(sa.String(1024))
|
||||
firewall_policy_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('firewall_policies.id'),
|
||||
nullable=True)
|
||||
shared = sa.Column(sa.Boolean)
|
||||
protocol = sa.Column(sa.String(40))
|
||||
ip_version = sa.Column(sa.Integer, nullable=False)
|
||||
source_ip_address = sa.Column(sa.String(46))
|
||||
destination_ip_address = sa.Column(sa.String(46))
|
||||
source_port_range_min = sa.Column(sa.Integer)
|
||||
source_port_range_max = sa.Column(sa.Integer)
|
||||
destination_port_range_min = sa.Column(sa.Integer)
|
||||
destination_port_range_max = sa.Column(sa.Integer)
|
||||
action = sa.Column(sa.Enum('allow', 'deny', name='firewallrules_action'))
|
||||
enabled = sa.Column(sa.Boolean)
|
||||
position = sa.Column(sa.Integer)
|
||||
|
||||
|
||||
class Firewall(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
|
||||
"""Represents a Firewall resource."""
|
||||
__tablename__ = 'firewalls'
|
||||
name = sa.Column(sa.String(255))
|
||||
description = sa.Column(sa.String(1024))
|
||||
shared = sa.Column(sa.Boolean)
|
||||
admin_state_up = sa.Column(sa.Boolean)
|
||||
status = sa.Column(sa.String(16))
|
||||
firewall_policy_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('firewall_policies.id'),
|
||||
nullable=True)
|
||||
|
||||
|
||||
class FirewallPolicy(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
|
||||
"""Represents a Firewall Policy resource."""
|
||||
__tablename__ = 'firewall_policies'
|
||||
name = sa.Column(sa.String(255))
|
||||
description = sa.Column(sa.String(1024))
|
||||
shared = sa.Column(sa.Boolean)
|
||||
firewall_rules = orm.relationship(
|
||||
FirewallRule,
|
||||
backref=orm.backref('firewall_policies', cascade='all, delete'),
|
||||
order_by='FirewallRule.position',
|
||||
collection_class=ordering_list('position', count_from=1))
|
||||
audited = sa.Column(sa.Boolean)
|
||||
firewalls = orm.relationship(Firewall, backref='firewall_policies')
|
||||
|
||||
|
||||
class Firewall_db_mixin(firewall.FirewallPluginBase, base_db.CommonDbMixin):
|
||||
"""Mixin class for Firewall DB implementation."""
|
||||
|
||||
@property
|
||||
def _core_plugin(self):
|
||||
return manager.NeutronManager.get_plugin()
|
||||
|
||||
def _get_firewall(self, context, id):
|
||||
try:
|
||||
return self._get_by_id(context, Firewall, id)
|
||||
except exc.NoResultFound:
|
||||
raise firewall.FirewallNotFound(firewall_id=id)
|
||||
|
||||
def _get_firewall_policy(self, context, id):
|
||||
try:
|
||||
return self._get_by_id(context, FirewallPolicy, id)
|
||||
except exc.NoResultFound:
|
||||
raise firewall.FirewallPolicyNotFound(firewall_policy_id=id)
|
||||
|
||||
def _get_firewall_rule(self, context, id):
|
||||
try:
|
||||
return self._get_by_id(context, FirewallRule, id)
|
||||
except exc.NoResultFound:
|
||||
raise firewall.FirewallRuleNotFound(firewall_rule_id=id)
|
||||
|
||||
def _make_firewall_dict(self, fw, fields=None):
|
||||
res = {'id': fw['id'],
|
||||
'tenant_id': fw['tenant_id'],
|
||||
'name': fw['name'],
|
||||
'description': fw['description'],
|
||||
'shared': fw['shared'],
|
||||
'admin_state_up': fw['admin_state_up'],
|
||||
'status': fw['status'],
|
||||
'firewall_policy_id': fw['firewall_policy_id']}
|
||||
return self._fields(res, fields)
|
||||
|
||||
def _make_firewall_policy_dict(self, firewall_policy, fields=None):
|
||||
fw_rules = [rule['id'] for rule in firewall_policy['firewall_rules']]
|
||||
firewalls = [fw['id'] for fw in firewall_policy['firewalls']]
|
||||
res = {'id': firewall_policy['id'],
|
||||
'tenant_id': firewall_policy['tenant_id'],
|
||||
'name': firewall_policy['name'],
|
||||
'description': firewall_policy['description'],
|
||||
'shared': firewall_policy['shared'],
|
||||
'audited': firewall_policy['audited'],
|
||||
'firewall_rules': fw_rules,
|
||||
'firewall_list': firewalls}
|
||||
return self._fields(res, fields)
|
||||
|
||||
def _make_firewall_rule_dict(self, firewall_rule, fields=None):
|
||||
position = None
|
||||
# We return the position only if the firewall_rule is bound to a
|
||||
# firewall_policy.
|
||||
if firewall_rule['firewall_policy_id']:
|
||||
position = firewall_rule['position']
|
||||
src_port_range = self._get_port_range_from_min_max_ports(
|
||||
firewall_rule['source_port_range_min'],
|
||||
firewall_rule['source_port_range_max'])
|
||||
dst_port_range = self._get_port_range_from_min_max_ports(
|
||||
firewall_rule['destination_port_range_min'],
|
||||
firewall_rule['destination_port_range_max'])
|
||||
res = {'id': firewall_rule['id'],
|
||||
'tenant_id': firewall_rule['tenant_id'],
|
||||
'name': firewall_rule['name'],
|
||||
'description': firewall_rule['description'],
|
||||
'firewall_policy_id': firewall_rule['firewall_policy_id'],
|
||||
'shared': firewall_rule['shared'],
|
||||
'protocol': firewall_rule['protocol'],
|
||||
'ip_version': firewall_rule['ip_version'],
|
||||
'source_ip_address': firewall_rule['source_ip_address'],
|
||||
'destination_ip_address':
|
||||
firewall_rule['destination_ip_address'],
|
||||
'source_port': src_port_range,
|
||||
'destination_port': dst_port_range,
|
||||
'action': firewall_rule['action'],
|
||||
'position': position,
|
||||
'enabled': firewall_rule['enabled']}
|
||||
return self._fields(res, fields)
|
||||
|
||||
def _set_rules_for_policy(self, context, firewall_policy_db, rule_id_list):
|
||||
fwp_db = firewall_policy_db
|
||||
with context.session.begin(subtransactions=True):
|
||||
if not rule_id_list:
|
||||
fwp_db.firewall_rules = []
|
||||
fwp_db.audited = False
|
||||
return
|
||||
# We will first check if the new list of rules is valid
|
||||
filters = {'id': [r_id for r_id in rule_id_list]}
|
||||
rules_in_db = self._get_collection_query(context, FirewallRule,
|
||||
filters=filters)
|
||||
rules_dict = dict((fwr_db['id'], fwr_db) for fwr_db in rules_in_db)
|
||||
for fwrule_id in rule_id_list:
|
||||
if fwrule_id not in rules_dict:
|
||||
# If we find an invalid rule in the list we
|
||||
# do not perform the update since this breaks
|
||||
# the integrity of this list.
|
||||
raise firewall.FirewallRuleNotFound(firewall_rule_id=
|
||||
fwrule_id)
|
||||
# New list of rules is valid so we will first reset the existing
|
||||
# list and then add each rule in order.
|
||||
# Note that the list could be empty in which case we interpret
|
||||
# it as clearing existing rules.
|
||||
fwp_db.firewall_rules = []
|
||||
for fwrule_id in rule_id_list:
|
||||
fwp_db.firewall_rules.append(rules_dict[fwrule_id])
|
||||
fwp_db.audited = False
|
||||
|
||||
def _process_rule_for_policy(self, context, firewall_policy_id,
|
||||
firewall_rule_db, position):
|
||||
with context.session.begin(subtransactions=True):
|
||||
fwp_query = context.session.query(
|
||||
FirewallPolicy).with_lockmode('update')
|
||||
fwp_db = fwp_query.filter_by(id=firewall_policy_id).one()
|
||||
if position:
|
||||
# Note that although position numbering starts at 1,
|
||||
# internal ordering of the list starts at 0, so we compensate.
|
||||
fwp_db.firewall_rules.insert(position - 1, firewall_rule_db)
|
||||
else:
|
||||
fwp_db.firewall_rules.remove(firewall_rule_db)
|
||||
fwp_db.firewall_rules.reorder()
|
||||
fwp_db.audited = False
|
||||
return self._make_firewall_policy_dict(fwp_db)
|
||||
|
||||
def _get_min_max_ports_from_range(self, port_range):
|
||||
if not port_range:
|
||||
return [None, None]
|
||||
ports = port_range.split(':')
|
||||
ports[0] = int(ports[0])
|
||||
if len(ports) < 2:
|
||||
ports.append(ports[0])
|
||||
else:
|
||||
ports[1] = int(ports[1])
|
||||
return ports
|
||||
|
||||
def _get_port_range_from_min_max_ports(self, min_port, max_port):
|
||||
if not min_port:
|
||||
return None
|
||||
if min_port == max_port:
|
||||
return str(min_port)
|
||||
else:
|
||||
return str(min_port) + ':' + str(max_port)
|
||||
|
||||
def create_firewall(self, context, firewall):
|
||||
LOG.debug(_("create_firewall() called"))
|
||||
fw = firewall['firewall']
|
||||
tenant_id = self._get_tenant_id_for_create(context, fw)
|
||||
with context.session.begin(subtransactions=True):
|
||||
firewall_db = Firewall(id=uuidutils.generate_uuid(),
|
||||
tenant_id=tenant_id,
|
||||
name=fw['name'],
|
||||
description=fw['description'],
|
||||
firewall_policy_id=
|
||||
fw['firewall_policy_id'],
|
||||
admin_state_up=fw['admin_state_up'],
|
||||
status=const.PENDING_CREATE)
|
||||
context.session.add(firewall_db)
|
||||
return self._make_firewall_dict(firewall_db)
|
||||
|
||||
def update_firewall(self, context, id, firewall):
|
||||
LOG.debug(_("update_firewall() called"))
|
||||
fw = firewall['firewall']
|
||||
with context.session.begin(subtransactions=True):
|
||||
fw_query = context.session.query(
|
||||
Firewall).with_lockmode('update')
|
||||
firewall_db = fw_query.filter_by(id=id).one()
|
||||
firewall_db.update(fw)
|
||||
return self._make_firewall_dict(firewall_db)
|
||||
|
||||
def delete_firewall(self, context, id):
|
||||
LOG.debug(_("delete_firewall() called"))
|
||||
with context.session.begin(subtransactions=True):
|
||||
fw_query = context.session.query(
|
||||
Firewall).with_lockmode('update')
|
||||
firewall_db = fw_query.filter_by(id=id).one()
|
||||
# Note: Plugin should ensure that it's okay to delete if the
|
||||
# firewall is active
|
||||
context.session.delete(firewall_db)
|
||||
|
||||
def get_firewall(self, context, id, fields=None):
|
||||
LOG.debug(_("get_firewall() called"))
|
||||
fw = self._get_firewall(context, id)
|
||||
return self._make_firewall_dict(fw, fields)
|
||||
|
||||
def get_firewalls(self, context, filters=None, fields=None):
|
||||
LOG.debug(_("get_firewalls() called"))
|
||||
return self._get_collection(context, Firewall,
|
||||
self._make_firewall_dict,
|
||||
filters=filters, fields=fields)
|
||||
|
||||
def get_firewalls_count(self, context, filters=None):
|
||||
LOG.debug(_("get_firewalls_count() called"))
|
||||
return self._get_collection_count(context, Firewall,
|
||||
filters=filters)
|
||||
|
||||
def create_firewall_policy(self, context, firewall_policy):
|
||||
LOG.debug(_("create_firewall_policy() called"))
|
||||
fwp = firewall_policy['firewall_policy']
|
||||
tenant_id = self._get_tenant_id_for_create(context, fwp)
|
||||
with context.session.begin(subtransactions=True):
|
||||
fwp_db = FirewallPolicy(id=uuidutils.generate_uuid(),
|
||||
tenant_id=tenant_id,
|
||||
name=fwp['name'],
|
||||
description=fwp['description'],
|
||||
shared=fwp['shared'])
|
||||
context.session.add(fwp_db)
|
||||
self._set_rules_for_policy(context, fwp_db,
|
||||
fwp['firewall_rules'])
|
||||
fwp_db.audited = fwp['audited']
|
||||
return self._make_firewall_policy_dict(fwp_db)
|
||||
|
||||
def update_firewall_policy(self, context, id, firewall_policy):
|
||||
LOG.debug(_("update_firewall_policy() called"))
|
||||
fwp = firewall_policy['firewall_policy']
|
||||
with context.session.begin(subtransactions=True):
|
||||
fwp_db = self._get_firewall_policy(context, id)
|
||||
if 'firewall_rules' in fwp:
|
||||
self._set_rules_for_policy(context, fwp_db,
|
||||
fwp['firewall_rules'])
|
||||
del fwp['firewall_rules']
|
||||
fwp_db.update(fwp)
|
||||
return self._make_firewall_policy_dict(fwp_db)
|
||||
|
||||
def delete_firewall_policy(self, context, id):
|
||||
LOG.debug(_("delete_firewall_policy() called"))
|
||||
with context.session.begin(subtransactions=True):
|
||||
fwp = self._get_firewall_policy(context, id)
|
||||
# Ensure that the firewall_policy is not
|
||||
# being used
|
||||
qry = context.session.query(Firewall)
|
||||
if qry.filter_by(firewall_policy_id=id).first():
|
||||
raise firewall.FirewallPolicyInUse(firewall_policy_id=id)
|
||||
else:
|
||||
context.session.delete(fwp)
|
||||
|
||||
def get_firewall_policy(self, context, id, fields=None):
|
||||
LOG.debug(_("get_firewall_policy() called"))
|
||||
fwp = self._get_firewall_policy(context, id)
|
||||
return self._make_firewall_policy_dict(fwp, fields)
|
||||
|
||||
def get_firewall_policies(self, context, filters=None, fields=None):
|
||||
LOG.debug(_("get_firewall_policies() called"))
|
||||
return self._get_collection(context, FirewallPolicy,
|
||||
self._make_firewall_policy_dict,
|
||||
filters=filters, fields=fields)
|
||||
|
||||
def get_firewalls_policies_count(self, context, filters=None):
|
||||
LOG.debug(_("get_firewall_policies_count() called"))
|
||||
return self._get_collection_count(context, FirewallPolicy,
|
||||
filters=filters)
|
||||
|
||||
def create_firewall_rule(self, context, firewall_rule):
|
||||
LOG.debug(_("create_firewall_rule() called"))
|
||||
fwr = firewall_rule['firewall_rule']
|
||||
tenant_id = self._get_tenant_id_for_create(context, fwr)
|
||||
src_port_min, src_port_max = self._get_min_max_ports_from_range(
|
||||
fwr['source_port'])
|
||||
dst_port_min, dst_port_max = self._get_min_max_ports_from_range(
|
||||
fwr['destination_port'])
|
||||
with context.session.begin(subtransactions=True):
|
||||
fwr_db = FirewallRule(id=uuidutils.generate_uuid(),
|
||||
tenant_id=tenant_id,
|
||||
name=fwr['name'],
|
||||
description=fwr['description'],
|
||||
shared=fwr['shared'],
|
||||
protocol=fwr['protocol'],
|
||||
ip_version=fwr['ip_version'],
|
||||
source_ip_address=fwr['source_ip_address'],
|
||||
destination_ip_address=
|
||||
fwr['destination_ip_address'],
|
||||
source_port_range_min=src_port_min,
|
||||
source_port_range_max=src_port_max,
|
||||
destination_port_range_min=dst_port_min,
|
||||
destination_port_range_max=dst_port_max,
|
||||
action=fwr['action'],
|
||||
enabled=fwr['enabled'])
|
||||
context.session.add(fwr_db)
|
||||
return self._make_firewall_rule_dict(fwr_db)
|
||||
|
||||
def update_firewall_rule(self, context, id, firewall_rule):
|
||||
LOG.debug(_("update_firewall_rule() called"))
|
||||
fwr = firewall_rule['firewall_rule']
|
||||
if 'source_port' in fwr:
|
||||
src_port_min, src_port_max = self._get_min_max_ports_from_range(
|
||||
fwr['source_port'])
|
||||
fwr['source_port_range_min'] = src_port_min
|
||||
fwr['source_port_range_max'] = src_port_max
|
||||
del fwr['source_port']
|
||||
if 'destination_port' in fwr:
|
||||
dst_port_min, dst_port_max = self._get_min_max_ports_from_range(
|
||||
fwr['destination_port'])
|
||||
fwr['destination_port_range_min'] = dst_port_min
|
||||
fwr['destination_port_range_max'] = dst_port_max
|
||||
del fwr['destination_port']
|
||||
with context.session.begin(subtransactions=True):
|
||||
fwr_db = self._get_firewall_rule(context, id)
|
||||
fwr_db.update(fwr)
|
||||
if fwr_db.firewall_policy_id:
|
||||
fwp_db = self._get_firewall_policy(context,
|
||||
fwr_db.firewall_policy_id)
|
||||
fwp_db.audited = False
|
||||
return self._make_firewall_rule_dict(fwr_db)
|
||||
|
||||
def delete_firewall_rule(self, context, id):
|
||||
LOG.debug(_("delete_firewall_rule() called"))
|
||||
with context.session.begin(subtransactions=True):
|
||||
fwr = self._get_firewall_rule(context, id)
|
||||
if fwr.firewall_policy_id:
|
||||
raise firewall.FirewallRuleInUse(firewall_rule_id=id)
|
||||
context.session.delete(fwr)
|
||||
|
||||
def get_firewall_rule(self, context, id, fields=None):
|
||||
LOG.debug(_("get_firewall_rule() called"))
|
||||
fwr = self._get_firewall_rule(context, id)
|
||||
return self._make_firewall_rule_dict(fwr, fields)
|
||||
|
||||
def get_firewall_rules(self, context, filters=None, fields=None):
|
||||
LOG.debug(_("get_firewall_rules() called"))
|
||||
return self._get_collection(context, FirewallRule,
|
||||
self._make_firewall_rule_dict,
|
||||
filters=filters, fields=fields)
|
||||
|
||||
def get_firewalls_rules_count(self, context, filters=None):
|
||||
LOG.debug(_("get_firewall_rules_count() called"))
|
||||
return self._get_collection_count(context, FirewallRule,
|
||||
filters=filters)
|
||||
|
||||
def _validate_insert_remove_rule_request(self, id, rule_info):
|
||||
if not rule_info or 'firewall_rule_id' not in rule_info:
|
||||
raise firewall.FirewallRuleInfoMissing()
|
||||
|
||||
def insert_rule(self, context, id, rule_info):
|
||||
LOG.debug(_("insert_rule() called"))
|
||||
self._validate_insert_remove_rule_request(id, rule_info)
|
||||
firewall_rule_id = rule_info['firewall_rule_id']
|
||||
insert_before = True
|
||||
ref_firewall_rule_id = None
|
||||
if not firewall_rule_id:
|
||||
raise firewall.FirewallRuleNotFound(firewall_rule_id=None)
|
||||
if 'insert_before' in rule_info:
|
||||
ref_firewall_rule_id = rule_info['insert_before']
|
||||
if not ref_firewall_rule_id and 'insert_after' in rule_info:
|
||||
# If insert_before is set, we will ignore insert_after.
|
||||
ref_firewall_rule_id = rule_info['insert_after']
|
||||
insert_before = False
|
||||
with context.session.begin(subtransactions=True):
|
||||
fwr_db = self._get_firewall_rule(context, firewall_rule_id)
|
||||
if fwr_db.firewall_policy_id:
|
||||
raise firewall.FirewallRuleInUse(firewall_rule_id=fwr_db['id'])
|
||||
if ref_firewall_rule_id:
|
||||
# If reference_firewall_rule_id is set, the new rule
|
||||
# is inserted depending on the value of insert_before.
|
||||
# If insert_before is set, the new rule is inserted before
|
||||
# reference_firewall_rule_id, and if it is not set the new
|
||||
# rule is inserted after reference_firewall_rule_id.
|
||||
ref_fwr_db = self._get_firewall_rule(
|
||||
context, ref_firewall_rule_id)
|
||||
if insert_before:
|
||||
position = ref_fwr_db.position
|
||||
else:
|
||||
position = ref_fwr_db.position + 1
|
||||
else:
|
||||
# If reference_firewall_rule_id is not set, it is assumed
|
||||
# that the new rule needs to be inserted at the top.
|
||||
# insert_before field is ignored.
|
||||
# So default insertion is always at the top.
|
||||
# Also note that position numbering starts at 1.
|
||||
position = 1
|
||||
return self._process_rule_for_policy(context, id, fwr_db,
|
||||
position)
|
||||
|
||||
def remove_rule(self, context, id, rule_info):
|
||||
LOG.debug(_("remove_rule() called"))
|
||||
self._validate_insert_remove_rule_request(id, rule_info)
|
||||
firewall_rule_id = rule_info['firewall_rule_id']
|
||||
if not firewall_rule_id:
|
||||
raise firewall.FirewallRuleNotFound(firewall_rule_id=None)
|
||||
with context.session.begin(subtransactions=True):
|
||||
fwr_db = self._get_firewall_rule(context, firewall_rule_id)
|
||||
if fwr_db.firewall_policy_id != id:
|
||||
raise firewall.FirewallRuleNotAssociatedWithPolicy(
|
||||
firewall_rule_id=fwr_db['id'],
|
||||
firewall_policy_id=id)
|
||||
return self._process_rule_for_policy(context, id, fwr_db, None)
|
@ -0,0 +1,105 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2013 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.
|
||||
#
|
||||
|
||||
"""FWaaS Havana-2 model
|
||||
|
||||
Revision ID: 39cf3f799352
|
||||
Revises: e6b16a30d97
|
||||
Create Date: 2013-07-10 16:16:51.302943
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '39cf3f799352'
|
||||
down_revision = 'e6b16a30d97'
|
||||
|
||||
# Change to ['*'] if this migration applies to all plugins
|
||||
|
||||
migration_for_plugins = ['*']
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
from neutron.db import migration
|
||||
|
||||
|
||||
def downgrade(active_plugin=None, options=None):
|
||||
if not migration.should_run(active_plugin, migration_for_plugins):
|
||||
return
|
||||
|
||||
op.drop_table('firewall_rules')
|
||||
op.drop_table('firewalls')
|
||||
op.drop_table('firewall_policies')
|
||||
|
||||
|
||||
def upgrade(active_plugin=None, options=None):
|
||||
if not migration.should_run(active_plugin, migration_for_plugins):
|
||||
return
|
||||
|
||||
op.create_table(
|
||||
'firewall_policies',
|
||||
sa.Column('tenant_id', sa.String(length=255), nullable=True),
|
||||
sa.Column('id', sa.String(length=36), nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=True),
|
||||
sa.Column('description', sa.String(length=1024), nullable=True),
|
||||
sa.Column('shared', sa.Boolean(), autoincrement=False, nullable=True),
|
||||
sa.Column('audited', sa.Boolean(), autoincrement=False,
|
||||
nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'))
|
||||
op.create_table(
|
||||
'firewalls', sa.Column('tenant_id', sa.String(length=255),
|
||||
nullable=True),
|
||||
sa.Column('id', sa.String(length=36), nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=True),
|
||||
sa.Column('description', sa.String(length=1024), nullable=True),
|
||||
sa.Column('shared', sa.Boolean(), autoincrement=False, nullable=True),
|
||||
sa.Column('admin_state_up', sa.Boolean(), autoincrement=False,
|
||||
nullable=True),
|
||||
sa.Column('status', sa.String(length=16), nullable=True),
|
||||
sa.Column('firewall_policy_id', sa.String(length=36), nullable=True),
|
||||
sa.ForeignKeyConstraint(['firewall_policy_id'],
|
||||
['firewall_policies.id'],
|
||||
name='firewalls_ibfk_1'),
|
||||
sa.PrimaryKeyConstraint('id'))
|
||||
op.create_table(
|
||||
'firewall_rules',
|
||||
sa.Column('tenant_id', sa.String(length=255), nullable=True),
|
||||
sa.Column('id', sa.String(length=36), nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=True),
|
||||
sa.Column('description', sa.String(length=1024), nullable=True),
|
||||
sa.Column('firewall_policy_id', sa.String(length=36), nullable=True),
|
||||
sa.Column('shared', sa.Boolean(), autoincrement=False,
|
||||
nullable=True),
|
||||
sa.Column('protocol', sa.String(length=24), nullable=True),
|
||||
sa.Column('ip_version', sa.Integer(), autoincrement=False,
|
||||
nullable=False),
|
||||
sa.Column('source_ip_address', sa.String(length=46), nullable=True),
|
||||
sa.Column('destination_ip_address', sa.String(length=46),
|
||||
nullable=True),
|
||||
sa.Column('source_port_range_min', sa.Integer(), nullable=True),
|
||||
sa.Column('source_port_range_max', sa.Integer(), nullable=True),
|
||||
sa.Column('destination_port_range_min', sa.Integer(), nullable=True),
|
||||
sa.Column('destination_port_range_max', sa.Integer(), nullable=True),
|
||||
sa.Column('action', sa.Enum(), nullable=True),
|
||||
sa.Column('enabled', sa.Boolean(), autoincrement=False,
|
||||
nullable=True),
|
||||
sa.Column('position', sa.Integer(), autoincrement=False,
|
||||
nullable=True),
|
||||
sa.ForeignKeyConstraint(['firewall_policy_id'],
|
||||
['firewall_policies.id'],
|
||||
name='firewall_rules_ibfk_1'),
|
||||
sa.PrimaryKeyConstraint('id'))
|
@ -0,0 +1,448 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2013 Big Switch Networks, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, sumitnaiksatam@gmail.com, Big Switch Networks, Inc.
|
||||
|
||||
import abc
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.api import extensions
|
||||
from neutron.api.v2 import attributes as attr
|
||||
from neutron.api.v2 import base
|
||||
from neutron.common import exceptions as qexception
|
||||
from neutron import manager
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.services.service_base import ServicePluginBase
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Firewall Exceptions
|
||||
class FirewallNotFound(qexception.NotFound):
|
||||
message = _("Firewall %(firewall_id)s could not be found.")
|
||||
|
||||
|
||||
class FirewallInUse(qexception.InUse):
|
||||
message = _("Firewall %(firewall_id)s is still active.")
|
||||
|
||||
|
||||
class FirewallInPendingState(qexception.Conflict):
|
||||
message = _("Operation cannot be performed since associated Firewall "
|
||||
"%(firewall_id)s is in %(pending_state)s.")
|
||||
|
||||
|
||||
class FirewallPolicyNotFound(qexception.NotFound):
|
||||
message = _("Firewall Policy %(firewall_policy_id)s could not be found.")
|
||||
|
||||
|
||||
class FirewallPolicyInUse(qexception.InUse):
|
||||
message = _("Firewall Policy %(firewall_policy_id)s is being used.")
|
||||
|
||||
|
||||
class FirewallRuleNotFound(qexception.NotFound):
|
||||
message = _("Firewall Rule %(firewall_rule_id)s could not be found.")
|
||||
|
||||
|
||||
class FirewallRuleInUse(qexception.InUse):
|
||||
message = _("Firewall Rule %(firewall_rule_id)s is being used.")
|
||||
|
||||
|
||||
class FirewallRuleNotAssociatedWithPolicy(qexception.InvalidInput):
|
||||
message = _("Firewall Rule %(firewall_rule_id)s is not associated "
|
||||
" with Firewall Policy %(firewall_policy_id)s.")
|
||||
|
||||
|
||||
class FirewallRuleInvalidProtocol(qexception.InvalidInput):
|
||||
message = _("Firewall Rule protocol %(protocol)s is not supported. "
|
||||
"Only protocol values %(values)s and their integer "
|
||||
"representation (0 to 255) are supported.")
|
||||
|
||||
|
||||
class FirewallRuleInvalidAction(qexception.InvalidInput):
|
||||
message = _("Firewall rule action %(action)s is not supported. "
|
||||
"Only action values %(values)s are supported.")
|
||||
|
||||
|
||||
class FirewallInvalidPortValue(qexception.InvalidInput):
|
||||
message = _("Invalid value for port %(port)s.")
|
||||
|
||||
|
||||
class FirewallRuleInfoMissing(qexception.InvalidInput):
|
||||
message = _("Missing rule info argument for insert/remove "
|
||||
"rule opertaion.")
|
||||
|
||||
|
||||
fw_valid_protocol_values = [None, constants.TCP, constants.UDP, constants.ICMP]
|
||||
fw_valid_action_values = [constants.FWAAS_ALLOW, constants.FWAAS_DENY]
|
||||
|
||||
|
||||
def convert_protocol(value):
|
||||
if value is None:
|
||||
return
|
||||
if value.isdigit():
|
||||
val = int(value)
|
||||
if 0 <= val <= 255:
|
||||
return val
|
||||
else:
|
||||
raise FirewallRuleInvalidProtocol(protocol=value,
|
||||
values=
|
||||
fw_valid_protocol_values)
|
||||
elif value.lower() in fw_valid_protocol_values:
|
||||
return value.lower()
|
||||
else:
|
||||
raise FirewallRuleInvalidProtocol(protocol=value,
|
||||
values=
|
||||
fw_valid_protocol_values)
|
||||
|
||||
|
||||
def convert_action_to_case_insensitive(value):
|
||||
if value is None:
|
||||
return
|
||||
else:
|
||||
return value.lower()
|
||||
|
||||
|
||||
def convert_port_to_string(value):
|
||||
if value is None:
|
||||
return
|
||||
else:
|
||||
return str(value)
|
||||
|
||||
|
||||
def _validate_port_range(data, key_specs=None):
|
||||
if data is None:
|
||||
return
|
||||
data = str(data)
|
||||
ports = data.split(':')
|
||||
for p in ports:
|
||||
try:
|
||||
val = int(p)
|
||||
except (ValueError, TypeError):
|
||||
msg = _("Port '%s' is not a valid number") % p
|
||||
LOG.debug(msg)
|
||||
return msg
|
||||
if val <= 0 or val > 65535:
|
||||
msg = _("Invalid port '%s'") % p
|
||||
LOG.debug(msg)
|
||||
return msg
|
||||
|
||||
|
||||
def _validate_ip_or_subnet_or_none(data, valid_values=None):
|
||||
if data is None:
|
||||
return None
|
||||
msg_ip = attr._validate_ip_address(data, valid_values)
|
||||
if not msg_ip:
|
||||
return
|
||||
msg_subnet = attr._validate_subnet(data, valid_values)
|
||||
if not msg_subnet:
|
||||
return
|
||||
return _("%(msg_ip)s and %(msg_subnet)s") % {'msg_ip': msg_ip,
|
||||
'msg_subnet': msg_subnet}
|
||||
|
||||
|
||||
attr.validators['type:port_range'] = _validate_port_range
|
||||
attr.validators['type:ip_or_subnet_or_none'] = _validate_ip_or_subnet_or_none
|
||||
|
||||
|
||||
RESOURCE_ATTRIBUTE_MAP = {
|
||||
'firewall_rules': {
|
||||
'id': {'allow_post': False, 'allow_put': False,
|
||||
'validate': {'type:uuid': None},
|
||||
'is_visible': True, 'primary_key': True},
|
||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
||||
'required_by_policy': True,
|
||||
'is_visible': True},
|
||||
'name': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True, 'default': ''},
|
||||
'description': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True, 'default': ''},
|
||||
'firewall_policy_id': {'allow_post': False, 'allow_put': False,
|
||||
'validate': {'type:uuid_or_none': None},
|
||||
'is_visible': True},
|
||||
'shared': {'allow_post': True, 'allow_put': True,
|
||||
'default': False, 'convert_to': attr.convert_to_boolean,
|
||||
'is_visible': True, 'required_by_policy': True,
|
||||
'enforce_policy': True},
|
||||
'protocol': {'allow_post': True, 'allow_put': True,
|
||||
'is_visible': True, 'default': None,
|
||||
'convert_to': convert_protocol,
|
||||
'validate': {'type:values': fw_valid_protocol_values}},
|
||||
'ip_version': {'allow_post': True, 'allow_put': True,
|
||||
'default': 4, 'convert_to': attr.convert_to_int,
|
||||
'validate': {'type:values': [4, 6]},
|
||||
'is_visible': True},
|
||||
'source_ip_address': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:ip_or_subnet_or_none': None},
|
||||
'is_visible': True, 'default': None},
|
||||
'destination_ip_address': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:ip_or_subnet_or_none':
|
||||
None},
|
||||
'is_visible': True, 'default': None},
|
||||
'source_port': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:port_range': None},
|
||||
'convert_to': convert_port_to_string,
|
||||
'default': None, 'is_visible': True},
|
||||
'destination_port': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:port_range': None},
|
||||
'convert_to': convert_port_to_string,
|
||||
'default': None, 'is_visible': True},
|
||||
'position': {'allow_post': False, 'allow_put': False,
|
||||
'default': None, 'is_visible': True},
|
||||
'action': {'allow_post': True, 'allow_put': True,
|
||||
'convert_to': convert_action_to_case_insensitive,
|
||||
'validate': {'type:values': fw_valid_action_values},
|
||||
'is_visible': True, 'default': 'deny'},
|
||||
'enabled': {'allow_post': True, 'allow_put': True,
|
||||
'default': True, 'convert_to': attr.convert_to_boolean,
|
||||
'is_visible': True},
|
||||
},
|
||||
'firewall_policies': {
|
||||
'id': {'allow_post': False, 'allow_put': False,
|
||||
'validate': {'type:uuid': None},
|
||||
'is_visible': True,
|
||||
'primary_key': True},
|
||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
||||
'required_by_policy': True,
|
||||
'is_visible': True},
|
||||
'name': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True, 'default': ''},
|
||||
'description': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True, 'default': ''},
|
||||
'shared': {'allow_post': True, 'allow_put': True,
|
||||
'default': False, 'convert_to': attr.convert_to_boolean,
|
||||
'is_visible': True, 'required_by_policy': True,
|
||||
'enforce_policy': True},
|
||||
'firewall_rules': {'allow_post': True, 'allow_put': True,
|
||||
'convert_to': attr.convert_none_to_empty_list,
|
||||
'default': None, 'is_visible': True},
|
||||
'audited': {'allow_post': True, 'allow_put': True,
|
||||
'default': False, 'convert_to': attr.convert_to_boolean,
|
||||
'is_visible': True},
|
||||
},
|
||||
'firewalls': {
|
||||
'id': {'allow_post': False, 'allow_put': False,
|
||||
'validate': {'type:uuid': None},
|
||||
'is_visible': True,
|
||||
'primary_key': True},
|
||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
||||
'required_by_policy': True,
|
||||
'is_visible': True},
|
||||
'name': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True, 'default': ''},
|
||||
'description': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True, 'default': ''},
|
||||
'admin_state_up': {'allow_post': True, 'allow_put': True,
|
||||
'default': True,
|
||||
'convert_to': attr.convert_to_boolean,
|
||||
'is_visible': True},
|
||||
'status': {'allow_post': False, 'allow_put': False,
|
||||
'is_visible': True},
|
||||
'shared': {'allow_post': True, 'allow_put': True,
|
||||
'default': False, 'convert_to': attr.convert_to_boolean,
|
||||
'is_visible': False, 'required_by_policy': True,
|
||||
'enforce_policy': True},
|
||||
'firewall_policy_id': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:uuid_or_none': None},
|
||||
'is_visible': True},
|
||||
},
|
||||
}
|
||||
|
||||
firewall_quota_opts = [
|
||||
cfg.IntOpt('quota_firewall',
|
||||
default=1,
|
||||
help=_('Number of firewalls allowed per tenant, -1 for '
|
||||
'unlimited')),
|
||||
cfg.IntOpt('quota_firewall_policy',
|
||||
default=1,
|
||||
help=_('Number of firewall policies allowed per tenant, -1 '
|
||||
'for unlimited')),
|
||||
cfg.IntOpt('quota_firewall_rule',
|
||||
default=-1,
|
||||
help=_('Number of firewall rules allowed per tenant, -1 '
|
||||
'for unlimited')),
|
||||
]
|
||||
cfg.CONF.register_opts(firewall_quota_opts, 'QUOTAS')
|
||||
|
||||
|
||||
class Firewall(extensions.ExtensionDescriptor):
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return "Firewall service"
|
||||
|
||||
@classmethod
|
||||
def get_alias(cls):
|
||||
return "fwaas"
|
||||
|
||||
@classmethod
|
||||
def get_description(cls):
|
||||
return "Extension for Firewall service"
|
||||
|
||||
@classmethod
|
||||
def get_namespace(cls):
|
||||
return "http://wiki.openstack.org/Neutron/FWaaS/API_1.0"
|
||||
|
||||
@classmethod
|
||||
def get_updated(cls):
|
||||
return "2013-02-25T10:00:00-00:00"
|
||||
|
||||
@classmethod
|
||||
def get_resources(cls):
|
||||
my_plurals = []
|
||||
for plural in RESOURCE_ATTRIBUTE_MAP:
|
||||
if plural == 'firewall_policies':
|
||||
singular = 'firewall_policy'
|
||||
else:
|
||||
singular = plural[:-1]
|
||||
my_plurals.append((plural, singular))
|
||||
attr.PLURALS.update(dict(my_plurals))
|
||||
resources = []
|
||||
plugin = manager.NeutronManager.get_service_plugins()[
|
||||
constants.FIREWALL]
|
||||
for collection_name in RESOURCE_ATTRIBUTE_MAP:
|
||||
# Special handling needed for resources with 'y' ending
|
||||
if collection_name == 'firewall_policies':
|
||||
resource_name = 'firewall_policy'
|
||||
else:
|
||||
resource_name = collection_name[:-1]
|
||||
|
||||
params = RESOURCE_ATTRIBUTE_MAP[collection_name]
|
||||
|
||||
member_actions = {}
|
||||
if resource_name == 'firewall_policy':
|
||||
member_actions = {'insert_rule': 'PUT',
|
||||
'remove_rule': 'PUT'}
|
||||
|
||||
controller = base.create_resource(
|
||||
collection_name, resource_name, plugin, params,
|
||||
member_actions=member_actions,
|
||||
allow_pagination=cfg.CONF.allow_pagination,
|
||||
allow_sorting=cfg.CONF.allow_sorting)
|
||||
|
||||
resource = extensions.ResourceExtension(
|
||||
collection_name,
|
||||
controller,
|
||||
path_prefix=constants.COMMON_PREFIXES[constants.FIREWALL],
|
||||
member_actions=member_actions,
|
||||
attr_map=params)
|
||||
resources.append(resource)
|
||||
|
||||
return resources
|
||||
|
||||
@classmethod
|
||||
def get_plugin_interface(cls):
|
||||
return FirewallPluginBase
|
||||
|
||||
def update_attributes_map(self, attributes):
|
||||
super(Firewall, self).update_attributes_map(
|
||||
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
|
||||
|
||||
def get_extended_resources(self, version):
|
||||
if version == "2.0":
|
||||
return RESOURCE_ATTRIBUTE_MAP
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
class FirewallPluginBase(ServicePluginBase):
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
def get_plugin_name(self):
|
||||
return constants.FIREWALL
|
||||
|
||||
def get_plugin_type(self):
|
||||
return constants.FIREWALL
|
||||
|
||||
def get_plugin_description(self):
|
||||
return 'Firewall service plugin'
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_firewalls(self, context, filters=None, fields=None):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_firewall(self, context, id, fields=None):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_firewall(self, context, firewall):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_firewall(self, context, id, firewall):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_firewall(self, context, id):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_firewall_rules(self, context, filters=None, fields=None):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_firewall_rule(self, context, id, fields=None):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_firewall_rule(self, context, firewall_rule):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_firewall_rule(self, context, id, firewall_rule):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_firewall_rule(self, context, id):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_firewall_policy(self, context, id, fields=None):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_firewall_policies(self, context, filters=None, fields=None):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_firewall_policy(self, context, firewall_policy):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_firewall_policy(self, context, id, firewall_policy):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_firewall_policy(self, context, id):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def insert_rule(self, context, id, rule_info):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def remove_rule(self, context, id, rule_info):
|
||||
pass
|
@ -0,0 +1,16 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 OpenStack Foundation.
|
||||
# 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.
|
@ -0,0 +1,285 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2013 Big Switch Networks, 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.
|
||||
#
|
||||
# @author: Sumit Naiksatam, sumitnaiksatam@gmail.com, Big Switch Networks, Inc.
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.common import rpc as q_rpc
|
||||
from neutron.common import topics
|
||||
from neutron.db import api as qdbapi
|
||||
from neutron.db.firewall import firewall_db
|
||||
from neutron.extensions import firewall as fw_ext
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.openstack.common import rpc
|
||||
from neutron.openstack.common.rpc import proxy
|
||||
from neutron.plugins.common import constants as const
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FirewallCallbacks(object):
|
||||
RPC_API_VERSION = '1.0'
|
||||
|
||||
def __init__(self, plugin):
|
||||
self.plugin = plugin
|
||||
|
||||
def create_rpc_dispatcher(self):
|
||||
return q_rpc.PluginRpcDispatcher([self])
|
||||
|
||||
def set_firewall_status(self, context, firewall_id, status, **kwargs):
|
||||
"""Agent uses this to set a firewall's status."""
|
||||
LOG.debug(_("set_firewall_status() called"))
|
||||
with context.session.begin(subtransactions=True):
|
||||
fw_db = self.plugin._get_firewall(context, firewall_id)
|
||||
if status in (const.ACTIVE, const.INACTIVE):
|
||||
fw_db.status = status
|
||||
return True
|
||||
else:
|
||||
fw_db.status = const.ERROR
|
||||
return False
|
||||
|
||||
def firewall_deleted(self, context, firewall_id, **kwargs):
|
||||
"""Agent uses this to indicate firewall is deleted."""
|
||||
LOG.debug(_("firewall_deleted() called"))
|
||||
with context.session.begin(subtransactions=True):
|
||||
fw_db = self.plugin._get_firewall(context, firewall_id)
|
||||
if fw_db.status == const.PENDING_DELETE:
|
||||
self.plugin.delete_db_firewall_object(context, firewall_id)
|
||||
return True
|
||||
else:
|
||||
fw_db.status = const.ERROR
|
||||
LOG.warn(_('Firewall %s unexpectedly deleted by agent.'),
|
||||
firewall_id)
|
||||
return False
|
||||
|
||||
def get_firewalls_for_tenant(self, context, **kwargs):
|
||||
"""Agent uses this to get all firewalls and rules for a tenant."""
|
||||
LOG.debug(_("get_firewalls_for_tenant() called"))
|
||||
fw_list = [
|
||||
self.plugin._make_firewall_dict_with_rules(context, fw['id'])
|
||||
for fw in self.plugin.get_firewalls(context)
|
||||
]
|
||||
return fw_list
|
||||
|
||||
def get_firewalls_for_tenant_without_rules(self, context, **kwargs):
|
||||
"""Agent uses this to get all firewalls for a tenant."""
|
||||
LOG.debug(_("get_firewalls_for_tenant_without_rules() called"))
|
||||
fw_list = [fw for fw in self.plugin.get_firewalls(context)]
|
||||
return fw_list
|
||||
|
||||
|
||||
class FirewallAgentApi(proxy.RpcProxy):
|
||||
"""Plugin side of plugin to agent RPC API."""
|
||||
|
||||
API_VERSION = '1.0'
|
||||
|
||||
def __init__(self, topic, host):
|
||||
super(FirewallAgentApi, self).__init__(topic, self.API_VERSION)
|
||||
self.host = host
|
||||
|
||||
def create_firewall(self, context, firewall):
|
||||
return self.fanout_cast(
|
||||
context,
|
||||
self.make_msg('create_firewall', firewall=firewall,
|
||||
host=self.host),
|
||||
topic=self.topic
|
||||
)
|
||||
|
||||
def update_firewall(self, context, firewall):
|
||||
return self.fanout_cast(
|
||||
context,
|
||||
self.make_msg('update_firewall', firewall=firewall,
|
||||
host=self.host),
|
||||
topic=self.topic
|
||||
)
|
||||
|
||||
def delete_firewall(self, context, firewall):
|
||||
return self.fanout_cast(
|
||||
context,
|
||||
self.make_msg('delete_firewall', firewall=firewall,
|
||||
host=self.host),
|
||||
topic=self.topic
|
||||
)
|
||||
|
||||
|
||||
class FirewallPlugin(firewall_db.Firewall_db_mixin):
|
||||
|
||||
"""Implementation of the Neutron Firewall Service Plugin.
|
||||
|
||||
This class manages the workflow of FWaaS request/response.
|
||||
Most DB related works are implemented in class
|
||||
firewall_db.Firewall_db_mixin.
|
||||
"""
|
||||
supported_extension_aliases = ["fwaas"]
|
||||
|
||||
def __init__(self):
|
||||
"""Do the initialization for the firewall service plugin here."""
|
||||
qdbapi.register_models()
|
||||
|
||||
self.callbacks = FirewallCallbacks(self)
|
||||
|
||||
self.conn = rpc.create_connection(new=True)
|
||||
self.conn.create_consumer(
|
||||
topics.FIREWALL_PLUGIN,
|
||||
self.callbacks.create_rpc_dispatcher(),
|
||||
fanout=False)
|
||||
self.conn.consume_in_thread()
|
||||
|
||||
self.agent_rpc = FirewallAgentApi(
|
||||
topics.L3_AGENT,
|
||||
cfg.CONF.host
|
||||
)
|
||||
|
||||
def _make_firewall_dict_with_rules(self, context, firewall_id):
|
||||
firewall = self.get_firewall(context, firewall_id)
|
||||
fw_policy_id = firewall['firewall_policy_id']
|
||||
if fw_policy_id:
|
||||
fw_policy = self.get_firewall_policy(context, fw_policy_id)
|
||||
fw_rules_list = [self.get_firewall_rule(
|
||||
context, rule_id) for rule_id in fw_policy['firewall_rules']]
|
||||
firewall['firewall_rule_list'] = fw_rules_list
|
||||
else:
|
||||
firewall['firewall_rule_list'] = []
|
||||
# FIXME(Sumit): If the size of the firewall object we are creating
|
||||
# here exceeds the largest message size supported by rabbit/qpid
|
||||
# then we will have a problem.
|
||||
return firewall
|
||||
|
||||
def _rpc_update_firewall(self, context, firewall_id):
|
||||
status_update = {"firewall": {"status": const.PENDING_UPDATE}}
|
||||
fw = super(FirewallPlugin, self).update_firewall(context, firewall_id,
|
||||
status_update)
|
||||
if fw:
|
||||
fw_with_rules = (
|
||||
self._make_firewall_dict_with_rules(context,
|
||||
firewall_id))
|
||||
self.agent_rpc.update_firewall(context, fw_with_rules)
|
||||
|
||||
def _rpc_update_firewall_policy(self, context, firewall_policy_id):
|
||||
firewall_policy = self.get_firewall_policy(context, firewall_policy_id)
|
||||
if firewall_policy:
|
||||
for firewall_id in firewall_policy['firewall_list']:
|
||||
self._rpc_update_firewall(context, firewall_id)
|
||||
|
||||
def _ensure_update_firewall(self, context, firewall_id):
|
||||
fwall = self.get_firewall(context, firewall_id)
|
||||
if fwall['status'] in [const.PENDING_CREATE,
|
||||
const.PENDING_UPDATE,
|
||||
const.PENDING_DELETE]:
|
||||
raise fw_ext.FirewallInPendingState(firewall_id=firewall_id,
|
||||
pending_state=fwall['status'])
|
||||
|
||||
def _ensure_update_firewall_policy(self, context, firewall_policy_id):
|
||||
firewall_policy = self.get_firewall_policy(context, firewall_policy_id)
|
||||
if firewall_policy and 'firewall_list' in firewall_policy:
|
||||
for firewall_id in firewall_policy['firewall_list']:
|
||||
self._ensure_update_firewall(context, firewall_id)
|
||||
|
||||
def _ensure_update_or_delete_firewall_rule(self, context,
|
||||
firewall_rule_id):
|
||||
fw_rule = self.get_firewall_rule(context, firewall_rule_id)
|
||||
if 'firewall_policy_id' in fw_rule and fw_rule['firewall_policy_id']:
|
||||
self._ensure_update_firewall_policy(context,
|
||||
fw_rule['firewall_policy_id'])
|
||||
|
||||
def create_firewall(self, context, firewall):
|
||||
LOG.debug(_("create_firewall() called"))
|
||||
firewall['firewall']['status'] = const.PENDING_CREATE
|
||||
fw = super(FirewallPlugin, self).create_firewall(context, firewall)
|
||||
fw_with_rules = (
|
||||
self._make_firewall_dict_with_rules(context, fw['id']))
|
||||
self.agent_rpc.create_firewall(context, fw_with_rules)
|
||||
return fw
|
||||
|
||||
def update_firewall(self, context, id, firewall):
|
||||
LOG.debug(_("update_firewall() called"))
|
||||
self._ensure_update_firewall(context, id)
|
||||
firewall['firewall']['status'] = const.PENDING_UPDATE
|
||||
fw = super(FirewallPlugin, self).update_firewall(context, id, firewall)
|
||||
fw_with_rules = (
|
||||
self._make_firewall_dict_with_rules(context, fw['id']))
|
||||
self.agent_rpc.update_firewall(context, fw_with_rules)
|
||||
return fw
|
||||
|
||||
def delete_db_firewall_object(self, context, id):
|
||||
firewall = self.get_firewall(context, id)
|
||||
if firewall['status'] in [const.PENDING_DELETE]:
|
||||
super(FirewallPlugin, self).delete_firewall(context, id)
|
||||
|
||||
def delete_firewall(self, context, id):
|
||||
LOG.debug(_("delete_firewall() called"))
|
||||
status_update = {"firewall": {"status": const.PENDING_DELETE}}
|
||||
fw = super(FirewallPlugin, self).update_firewall(context, id,
|
||||
status_update)
|
||||
fw_with_rules = (
|
||||
self._make_firewall_dict_with_rules(context, fw['id']))
|
||||
self.agent_rpc.delete_firewall(context, fw_with_rules)
|
||||
|
||||
def update_firewall_policy(self, context, id, firewall_policy):
|
||||
LOG.debug(_("update_firewall_policy() called"))
|
||||
self._ensure_update_firewall_policy(context, id)
|
||||
fwp = super(FirewallPlugin,
|
||||
self).update_firewall_policy(context, id, firewall_policy)
|
||||
self._rpc_update_firewall_policy(context, id)
|
||||
return fwp
|
||||
|
||||
def update_firewall_rule(self, context, id, firewall_rule):
|
||||
LOG.debug(_("update_firewall_rule() called"))
|
||||
self._ensure_update_or_delete_firewall_rule(context, id)
|
||||
fwr = super(FirewallPlugin,
|
||||
self).update_firewall_rule(context, id, firewall_rule)
|
||||
firewall_policy_id = fwr['firewall_policy_id']
|
||||
if firewall_policy_id:
|
||||
self._rpc_update_firewall_policy(context, firewall_policy_id)
|
||||
return fwr
|
||||
|
||||
def delete_firewall_rule(self, context, id):
|
||||
LOG.debug(_("delete_firewall_rule() called"))
|
||||
self._ensure_update_or_delete_firewall_rule(context, id)
|
||||
fwr = self.get_firewall_rule(context, id)
|
||||
firewall_policy_id = fwr['firewall_policy_id']
|
||||
super(FirewallPlugin, self).delete_firewall_rule(context, id)
|
||||
# At this point we have already deleted the rule in the DB,
|
||||
# however it's still not deleted on the backend firewall.
|
||||