444 lines
17 KiB
Python
444 lines
17 KiB
Python
# 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.callbacks import events
|
|
from neutron.callbacks import registry
|
|
from neutron.callbacks import resources
|
|
from neutron.db import address_scope_db
|
|
from neutron.db import api as db_api
|
|
from neutron.db import common_db_mixin
|
|
from neutron.db import l3_db
|
|
from neutron.db.models import securitygroup as sg_models
|
|
from neutron.db import models_v2
|
|
from neutron.db import securitygroups_db
|
|
from neutron.extensions import address_scope as ext_address_scope
|
|
from neutron.extensions import securitygroup as ext_sg
|
|
from neutron.objects import subnetpool as subnetpool_obj
|
|
from neutron.plugins.ml2 import db as ml2_db
|
|
from neutron_lib.api import validators
|
|
from neutron_lib import exceptions as n_exc
|
|
from oslo_log import log
|
|
from oslo_utils import uuidutils
|
|
from sqlalchemy import event
|
|
from sqlalchemy.orm import exc
|
|
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
# REVISIT(ivar): Monkey patch to allow explicit router_id to be set in Neutron
|
|
# for Floating Ip creation (for internal calls only). Once we split the server,
|
|
# this could be part of a GBP Neutron L3 driver.
|
|
def _get_assoc_data(self, context, fip, floatingip_db):
|
|
(internal_port, internal_subnet_id,
|
|
internal_ip_address) = self._internal_fip_assoc_data(
|
|
context, fip, floatingip_db['tenant_id'])
|
|
if fip.get('router_id'):
|
|
router_id = fip['router_id']
|
|
del fip['router_id']
|
|
else:
|
|
router_id = self._get_router_for_floatingip(
|
|
context, internal_port, internal_subnet_id,
|
|
floatingip_db['floating_network_id'])
|
|
|
|
return fip['port_id'], internal_ip_address, router_id
|
|
|
|
|
|
l3_db.L3_NAT_dbonly_mixin._get_assoc_data = _get_assoc_data
|
|
|
|
|
|
# REVISIT(ivar): Neutron adds a tenant filter on SG lookup for a given port,
|
|
# this breaks our service chain plumbing model so for now we should monkey
|
|
# patch the specific method. A follow up with the Neutron team is needed to
|
|
# figure out the reason for this and how to proceed for future releases.
|
|
def _get_security_groups_on_port(self, context, port):
|
|
"""Check that all security groups on port belong to tenant.
|
|
|
|
:returns: all security groups IDs on port belonging to tenant.
|
|
"""
|
|
p = port['port']
|
|
if not validators.is_attr_set(
|
|
p.get(securitygroups_db.ext_sg.SECURITYGROUPS)):
|
|
return
|
|
if p.get('device_owner') and p['device_owner'].startswith('network:'):
|
|
return
|
|
|
|
port_sg = p.get(securitygroups_db.ext_sg.SECURITYGROUPS, [])
|
|
filters = {'id': port_sg}
|
|
valid_groups = set(g['id'] for g in
|
|
self.get_security_groups(context, fields=['id'],
|
|
filters=filters))
|
|
|
|
requested_groups = set(port_sg)
|
|
port_sg_missing = requested_groups - valid_groups
|
|
if port_sg_missing:
|
|
raise securitygroups_db.ext_sg.SecurityGroupNotFound(
|
|
id=', '.join(port_sg_missing))
|
|
|
|
return requested_groups
|
|
|
|
securitygroups_db.SecurityGroupDbMixin._get_security_groups_on_port = (
|
|
_get_security_groups_on_port)
|
|
|
|
|
|
# REVISIT(kent): Neutron doesn't pass the remote_group_id while creating the
|
|
# ingress rule for the default SG. It also doesn't pass the newly created SG
|
|
# for the PRECOMMIT_CREATE event. Note that we should remove this in Pike as
|
|
# upstream has fixed the bug there
|
|
def create_security_group(self, context, security_group, default_sg=False):
|
|
"""Create security group.
|
|
|
|
If default_sg is true that means we are a default security group for
|
|
a given tenant if it does not exist.
|
|
"""
|
|
s = security_group['security_group']
|
|
kwargs = {
|
|
'context': context,
|
|
'security_group': s,
|
|
'is_default': default_sg,
|
|
}
|
|
|
|
self._registry_notify(resources.SECURITY_GROUP, events.BEFORE_CREATE,
|
|
exc_cls=ext_sg.SecurityGroupConflict, **kwargs)
|
|
|
|
tenant_id = s['tenant_id']
|
|
|
|
if not default_sg:
|
|
self._ensure_default_security_group(context, tenant_id)
|
|
else:
|
|
existing_def_sg_id = self._get_default_sg_id(context, tenant_id)
|
|
if existing_def_sg_id is not None:
|
|
# default already exists, return it
|
|
return self.get_security_group(context, existing_def_sg_id)
|
|
|
|
with db_api.autonested_transaction(context.session):
|
|
security_group_db = sg_models.SecurityGroup(id=s.get('id') or (
|
|
uuidutils.generate_uuid()),
|
|
description=s['description'],
|
|
tenant_id=tenant_id,
|
|
name=s['name'])
|
|
context.session.add(security_group_db)
|
|
if default_sg:
|
|
context.session.add(sg_models.DefaultSecurityGroup(
|
|
security_group=security_group_db,
|
|
tenant_id=security_group_db['tenant_id']))
|
|
for ethertype in ext_sg.sg_supported_ethertypes:
|
|
if default_sg:
|
|
# Allow intercommunication
|
|
ingress_rule = sg_models.SecurityGroupRule(
|
|
id=uuidutils.generate_uuid(), tenant_id=tenant_id,
|
|
security_group=security_group_db,
|
|
direction='ingress',
|
|
ethertype=ethertype,
|
|
remote_group_id=security_group_db.id,
|
|
source_group=security_group_db)
|
|
context.session.add(ingress_rule)
|
|
|
|
egress_rule = sg_models.SecurityGroupRule(
|
|
id=uuidutils.generate_uuid(), tenant_id=tenant_id,
|
|
security_group=security_group_db,
|
|
direction='egress',
|
|
ethertype=ethertype)
|
|
context.session.add(egress_rule)
|
|
|
|
secgroup_dict = self._make_security_group_dict(security_group_db)
|
|
kwargs['security_group'] = secgroup_dict
|
|
self._registry_notify(resources.SECURITY_GROUP,
|
|
events.PRECOMMIT_CREATE,
|
|
exc_cls=ext_sg.SecurityGroupConflict,
|
|
**kwargs)
|
|
|
|
registry.notify(resources.SECURITY_GROUP, events.AFTER_CREATE, self,
|
|
**kwargs)
|
|
return secgroup_dict
|
|
|
|
securitygroups_db.SecurityGroupDbMixin.create_security_group = (
|
|
create_security_group)
|
|
|
|
|
|
# REVISIT(kent): Neutron doesn't pass the updated SG for the PRECOMMIT_UPDATE
|
|
# event. Note that we should remove this in Pike as upstream has fixed the bug
|
|
# there
|
|
def update_security_group(self, context, id, security_group):
|
|
s = security_group['security_group']
|
|
|
|
kwargs = {
|
|
'context': context,
|
|
'security_group_id': id,
|
|
'security_group': s,
|
|
}
|
|
self._registry_notify(resources.SECURITY_GROUP, events.BEFORE_UPDATE,
|
|
exc_cls=ext_sg.SecurityGroupConflict, **kwargs)
|
|
|
|
with context.session.begin(subtransactions=True):
|
|
sg = self._get_security_group(context, id)
|
|
if sg['name'] == 'default' and 'name' in s:
|
|
raise ext_sg.SecurityGroupCannotUpdateDefault()
|
|
sg_dict = self._make_security_group_dict(sg)
|
|
kwargs['original_security_group'] = sg_dict
|
|
sg.update(s)
|
|
sg_dict = self._make_security_group_dict(sg)
|
|
kwargs['security_group'] = sg_dict
|
|
self._registry_notify(
|
|
resources.SECURITY_GROUP,
|
|
events.PRECOMMIT_UPDATE,
|
|
exc_cls=ext_sg.SecurityGroupConflict, **kwargs)
|
|
|
|
registry.notify(resources.SECURITY_GROUP, events.AFTER_UPDATE, self,
|
|
**kwargs)
|
|
return sg_dict
|
|
|
|
securitygroups_db.SecurityGroupDbMixin.update_security_group = (
|
|
update_security_group)
|
|
|
|
|
|
# REVISIT(kent): Neutron doesn't pass the SG rules for the PRECOMMIT_DELETE
|
|
# event. Note that we should remove this in Pike as upstream has fixed the bug
|
|
# there
|
|
def delete_security_group(self, context, id):
|
|
filters = {'security_group_id': [id]}
|
|
ports = self._get_port_security_group_bindings(context, filters)
|
|
if ports:
|
|
raise ext_sg.SecurityGroupInUse(id=id)
|
|
# confirm security group exists
|
|
sg = self._get_security_group(context, id)
|
|
|
|
if sg['name'] == 'default' and not context.is_admin:
|
|
raise ext_sg.SecurityGroupCannotRemoveDefault()
|
|
kwargs = {
|
|
'context': context,
|
|
'security_group_id': id,
|
|
'security_group': sg,
|
|
}
|
|
self._registry_notify(resources.SECURITY_GROUP, events.BEFORE_DELETE,
|
|
exc_cls=ext_sg.SecurityGroupInUse, id=id,
|
|
**kwargs)
|
|
|
|
with context.session.begin(subtransactions=True):
|
|
# pass security_group_rule_ids to ensure
|
|
# consistency with deleted rules
|
|
kwargs['security_group_rule_ids'] = [r['id'] for r in sg.rules]
|
|
kwargs['security_group'] = self._make_security_group_dict(sg)
|
|
self._registry_notify(resources.SECURITY_GROUP,
|
|
events.PRECOMMIT_DELETE,
|
|
exc_cls=ext_sg.SecurityGroupInUse, id=id,
|
|
**kwargs)
|
|
context.session.delete(sg)
|
|
|
|
kwargs.pop('security_group')
|
|
registry.notify(resources.SECURITY_GROUP, events.AFTER_DELETE, self,
|
|
**kwargs)
|
|
|
|
securitygroups_db.SecurityGroupDbMixin.delete_security_group = (
|
|
delete_security_group)
|
|
|
|
|
|
# REVISIT(kent): Neutron doesn't pass the newly created SG rule for the
|
|
# PRECOMMIT_CREATE event. Note that we should remove this in Pike as upstream
|
|
# has fixed the bug there
|
|
def _create_security_group_rule(self, context, security_group_rule,
|
|
validate=True):
|
|
if validate:
|
|
self._validate_security_group_rule(context, security_group_rule)
|
|
rule_dict = security_group_rule['security_group_rule']
|
|
kwargs = {
|
|
'context': context,
|
|
'security_group_rule': rule_dict
|
|
}
|
|
self._registry_notify(resources.SECURITY_GROUP_RULE,
|
|
events.BEFORE_CREATE,
|
|
exc_cls=ext_sg.SecurityGroupConflict, **kwargs)
|
|
|
|
with context.session.begin(subtransactions=True):
|
|
if validate:
|
|
self._check_for_duplicate_rules_in_db(context,
|
|
security_group_rule)
|
|
db = sg_models.SecurityGroupRule(
|
|
id=(rule_dict.get('id') or uuidutils.generate_uuid()),
|
|
tenant_id=rule_dict['tenant_id'],
|
|
security_group_id=rule_dict['security_group_id'],
|
|
direction=rule_dict['direction'],
|
|
remote_group_id=rule_dict.get('remote_group_id'),
|
|
ethertype=rule_dict['ethertype'],
|
|
protocol=rule_dict['protocol'],
|
|
port_range_min=rule_dict['port_range_min'],
|
|
port_range_max=rule_dict['port_range_max'],
|
|
remote_ip_prefix=rule_dict.get('remote_ip_prefix'),
|
|
description=rule_dict.get('description')
|
|
)
|
|
context.session.add(db)
|
|
res_rule_dict = self._make_security_group_rule_dict(db)
|
|
kwargs['security_group_rule'] = res_rule_dict
|
|
self._registry_notify(resources.SECURITY_GROUP_RULE,
|
|
events.PRECOMMIT_CREATE,
|
|
exc_cls=ext_sg.SecurityGroupConflict, **kwargs)
|
|
registry.notify(
|
|
resources.SECURITY_GROUP_RULE, events.AFTER_CREATE, self,
|
|
**kwargs)
|
|
return res_rule_dict
|
|
|
|
securitygroups_db.SecurityGroupDbMixin._create_security_group_rule = (
|
|
_create_security_group_rule)
|
|
|
|
|
|
# REVISIT(kent): Neutron doesn't pass the SG ID of the rule for the
|
|
# PRECOMMIT_DELETE event. Note that we should remove this in Pike as upstream
|
|
# has fixed the bug there
|
|
def delete_security_group_rule(self, context, id):
|
|
kwargs = {
|
|
'context': context,
|
|
'security_group_rule_id': id
|
|
}
|
|
self._registry_notify(resources.SECURITY_GROUP_RULE,
|
|
events.BEFORE_DELETE, id=id,
|
|
exc_cls=ext_sg.SecurityGroupRuleInUse, **kwargs)
|
|
|
|
with context.session.begin(subtransactions=True):
|
|
query = self._model_query(context,
|
|
sg_models.SecurityGroupRule).filter(
|
|
sg_models.SecurityGroupRule.id == id)
|
|
try:
|
|
# As there is a filter on a primary key it is not possible for
|
|
# MultipleResultsFound to be raised
|
|
sg_rule = query.one()
|
|
except exc.NoResultFound:
|
|
raise ext_sg.SecurityGroupRuleNotFound(id=id)
|
|
|
|
kwargs['security_group_id'] = sg_rule['security_group_id']
|
|
self._registry_notify(resources.SECURITY_GROUP_RULE,
|
|
events.PRECOMMIT_DELETE,
|
|
exc_cls=ext_sg.SecurityGroupRuleInUse, id=id,
|
|
**kwargs)
|
|
context.session.delete(sg_rule)
|
|
|
|
registry.notify(
|
|
resources.SECURITY_GROUP_RULE, events.AFTER_DELETE, self,
|
|
**kwargs)
|
|
|
|
securitygroups_db.SecurityGroupDbMixin.delete_security_group_rule = (
|
|
delete_security_group_rule)
|
|
|
|
|
|
def get_port_from_device_mac(context, device_mac):
|
|
LOG.debug("get_port_from_device_mac() called for mac %s", device_mac)
|
|
qry = context.session.query(models_v2.Port).filter_by(
|
|
mac_address=device_mac).order_by(models_v2.Port.device_owner.desc())
|
|
return qry.first()
|
|
|
|
ml2_db.get_port_from_device_mac = get_port_from_device_mac
|
|
|
|
PUSH_NOTIFICATIONS_METHOD = None
|
|
DISCARD_NOTIFICATIONS_METHOD = None
|
|
|
|
|
|
def gbp_after_transaction(session, transaction):
|
|
if transaction and not transaction._parent and (
|
|
not transaction.is_active and not transaction.nested):
|
|
if transaction in session.notification_queue:
|
|
# push the queued notifications only when the
|
|
# outermost transaction completes
|
|
PUSH_NOTIFICATIONS_METHOD(session, transaction)
|
|
|
|
|
|
def gbp_after_rollback(session):
|
|
# We discard all queued notifiactions if the transaction fails.
|
|
DISCARD_NOTIFICATIONS_METHOD(session)
|
|
|
|
|
|
def pre_session():
|
|
from gbpservice.network.neutronv2 import local_api
|
|
|
|
# The folowing are declared as global so that they can
|
|
# used in the inner functions that follow.
|
|
global PUSH_NOTIFICATIONS_METHOD
|
|
global DISCARD_NOTIFICATIONS_METHOD
|
|
PUSH_NOTIFICATIONS_METHOD = (
|
|
local_api.post_notifications_from_queue)
|
|
DISCARD_NOTIFICATIONS_METHOD = (
|
|
local_api.discard_notifications_after_rollback)
|
|
|
|
|
|
def post_session(new_session):
|
|
from gbpservice.network.neutronv2 import local_api
|
|
new_session.notification_queue = {}
|
|
|
|
if local_api.QUEUE_OUT_OF_PROCESS_NOTIFICATIONS:
|
|
event.listen(new_session, "after_transaction_end",
|
|
gbp_after_transaction)
|
|
event.listen(new_session, "after_rollback",
|
|
gbp_after_rollback)
|
|
|
|
|
|
def get_session(autocommit=True, expire_on_commit=False, use_slave=False):
|
|
|
|
pre_session()
|
|
# The following two lines are copied from the original
|
|
# implementation of db_api.get_session() and should be updated
|
|
# if the original implementation changes.
|
|
new_session = db_api.context_manager.get_legacy_facade().get_session(
|
|
autocommit=autocommit, expire_on_commit=expire_on_commit,
|
|
use_slave=use_slave)
|
|
|
|
post_session(new_session)
|
|
return new_session
|
|
|
|
|
|
def get_writer_session():
|
|
pre_session()
|
|
|
|
new_session = db_api.context_manager.writer.get_sessionmaker()()
|
|
post_session(new_session)
|
|
return new_session
|
|
|
|
|
|
db_api.get_session = get_session
|
|
db_api.get_writer_session = get_writer_session
|
|
|
|
|
|
# REVISIT: This is temporary, the correct fix is to use
|
|
# the 'project_id' directly from the context rather than
|
|
# calling this method.
|
|
def _get_tenant_id_for_create(self, context, resource):
|
|
if context.is_admin and 'tenant_id' in resource:
|
|
tenant_id = resource['tenant_id']
|
|
elif ('tenant_id' in resource and
|
|
resource['tenant_id'] != context.project_id):
|
|
reason = _('Cannot create resource for another tenant')
|
|
raise n_exc.AdminRequired(reason=reason)
|
|
else:
|
|
tenant_id = context.project_id
|
|
|
|
return tenant_id
|
|
|
|
|
|
common_db_mixin.CommonDbMixin._get_tenant_id_for_create = (
|
|
_get_tenant_id_for_create)
|
|
|
|
|
|
# REVISIT: In ocata, the switch to new engine facade in neutron is partial.
|
|
# This can result in different facades being mixed up within same transaction,
|
|
# and inconsistent behavior. Specifically, when L3 policy is deleted,
|
|
# subnetpool is deleted (old facade), and address scope (new facade) fails to
|
|
# be deleted since the dependent subnetpool deletion is in different session
|
|
# that is not yet commited. The workaround is to switch address scope to old
|
|
# engine facade. This workaround should be removed in Pike.
|
|
def _delete_address_scope(self, context, id):
|
|
with context.session.begin(subtransactions=True):
|
|
if subnetpool_obj.SubnetPool.get_objects(context,
|
|
address_scope_id=id):
|
|
raise ext_address_scope.AddressScopeInUse(address_scope_id=id)
|
|
address_scope = self._get_address_scope(context, id)
|
|
address_scope.delete()
|
|
|
|
address_scope_db.AddressScopeDbMixin.delete_address_scope = (
|
|
_delete_address_scope)
|