Support for stateless security groups

Blueprint: stateless-security-groups

Change-Id: Iae39a89b762786e4f05aa61aa0db634941806d41
This commit is contained in:
Aditya Reddy Nagaram 2018-06-06 15:23:02 +02:00 committed by Tom Stappaerts
parent f71b6b361e
commit cbc473e066
20 changed files with 241 additions and 61 deletions

View File

@ -396,17 +396,54 @@ class IptablesFirewallDriver(firewall.FirewallDriver):
# match by interface for bridge input
match_interface = '-i %s'
match_physdev = '-m physdev --physdev-in %s'
# comment to prevent duplicate warnings for different devices using
# same bridge. truncate start to remove prefixes
comment = '-m comment --comment "Set zone for %s"' % port['device'][4:]
port_sg_rules = self._get_port_sg_rules(port)
if self._are_sg_rules_stateful(port_sg_rules):
# comment to prevent duplicate warnings for different devices using
# same bridge. truncate start to remove prefixes
comment = 'Set zone for %s' % port['device'][4:]
conntrack = '--zone %s' % self.ipconntrack.get_device_zone(port)
else:
comment = 'Make %s stateless' % port['device'][4:]
conntrack = '--notrack'
rules = []
for dev, match in ((br_dev, match_physdev), (br_dev, match_interface),
(port_dev, match_physdev)):
match = match % dev
rule = '%s %s -j CT --zone %s' % (match, comment, zone)
rule = '%s -m comment --comment "%s" -j CT %s' % (match, comment,
conntrack)
rules.append(rule)
return rules
def _get_port_sg_rules(self, port):
port_sg_rules = []
if not any(port.get('device_owner', '').startswith(prefix)
for prefix in constants.DEVICE_OWNER_PREFIXES):
port_sg_ids = port.get('security_groups', [])
if port_sg_ids:
for rule in self.sg_rules.get(port_sg_ids[0], []):
if self.enable_ipset:
port_sg_rules.append(rule)
break
else:
port_sg_rules.extend(
self._expand_sg_rule_with_remote_ips(
rule, port, constants.INGRESS_DIRECTION))
if port_sg_rules:
break
else:
port_sg_rules.extend(
self._expand_sg_rule_with_remote_ips(
rule, port, constants.EGRESS_DIRECTION))
if port_sg_rules:
break
return port_sg_rules
@staticmethod
def _are_sg_rules_stateful(security_group_rules):
for rule in security_group_rules:
return rule.get('stateful', True)
return True
def _add_conntrack_jump(self, port):
for jump_rule in self._get_jump_rules(port):
self._add_raw_rule('PREROUTING', jump_rule)

View File

@ -17,6 +17,7 @@
import functools
from neutron_lib.api.definitions import rbac_security_groups as rbac_sg_apidef
from neutron_lib.api.definitions import stateful_security_group as stateful_sg
from oslo_config import cfg
from oslo_log import log as logging
import oslo_messaging
@ -46,6 +47,7 @@ def disable_security_group_extension_by_config(aliases):
LOG.info('Disabled security-group extension.')
_disable_extension('security-group', aliases)
_disable_extension(rbac_sg_apidef.ALIAS, aliases)
_disable_extension(stateful_sg.ALIAS, aliases)
LOG.info('Disabled allowed-address-pairs extension.')
_disable_extension('allowed-address-pairs', aliases)

View File

@ -24,6 +24,7 @@ from neutron_lib.utils import net
from oslo_log import log as logging
import oslo_messaging
from neutron.api.rpc.callbacks import resources
from neutron.api.rpc.handlers import resources_rpc
from neutron.db import securitygroups_rpc_base as sg_rpc_base
@ -354,3 +355,7 @@ class SecurityGroupServerAPIShim(sg_rpc_base.SecurityGroupInfoAPIMixin):
sg_ids = set((sg_id for p in ports.values()
for sg_id in p['security_group_ids']))
return [(sg_id, ) for sg_id in sg_ids]
def _is_security_group_stateful(self, context, sg_id):
sg = self.rcache.get_resource_by_id(resources.SECURITYGROUP, sg_id)
return sg.stateful

View File

@ -1 +1 @@
2217c4222de6
18a7e90ae768

View File

@ -0,0 +1,37 @@
# Copyright 2018 NOKIA
#
# 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 alembic import op
import sqlalchemy as sa
"""add security group stateful
Revision ID: 18a7e90ae768
Revises: 2217c4222de6
Create Date: 2018-04-26 14:44:52.635576
"""
# revision identifiers, used by Alembic.
revision = '18a7e90ae768'
down_revision = '2217c4222de6'
def upgrade():
op.add_column('securitygroups',
sa.Column('stateful',
sa.Boolean(),
server_default=sa.sql.true(),
nullable=False))

View File

@ -16,6 +16,7 @@ from neutron_lib.db import constants as db_const
from neutron_lib.db import model_base
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy import sql
from neutron.db import models_v2
from neutron.db import rbac_db_models
@ -28,6 +29,9 @@ class SecurityGroup(standard_attr.HasStandardAttributes, model_base.BASEV2,
"""Represents a v2 neutron security group."""
name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
stateful = sa.Column(sa.Boolean,
default=True, server_default=sql.true(),
nullable=False)
rbac_entries = sa.orm.relationship(rbac_db_models.SecurityGroupRBAC,
backref='security_group',
lazy='subquery',

View File

@ -96,6 +96,7 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase,
desired_state=s))
tenant_id = s['tenant_id']
stateful = s.get('stateful', True)
if not default_sg:
self._ensure_default_security_group(context, tenant_id)
@ -109,7 +110,7 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase,
sg = sg_obj.SecurityGroup(
context, id=s.get('id') or uuidutils.generate_uuid(),
description=s['description'], project_id=tenant_id,
name=s['name'], is_default=default_sg)
name=s['name'], is_default=default_sg, stateful=stateful)
sg.create()
delta = len(ext_sg.sg_supported_ethertypes)
@ -277,6 +278,13 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase,
@db_api.retry_if_session_inactive()
def update_security_group(self, context, id, security_group):
s = security_group['security_group']
if 'stateful' in s:
filters = {'security_group_id': [id]}
with db_api.CONTEXT_READER.using(context):
ports = self._get_port_security_group_bindings(context,
filters)
if ports:
raise ext_sg.SecurityGroupInUse(id=id)
kwargs = {
'context': context,
@ -311,6 +319,7 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase,
def _make_security_group_dict(self, security_group, fields=None):
res = {'id': security_group['id'],
'name': security_group['name'],
'stateful': security_group['stateful'],
'tenant_id': security_group['tenant_id'],
'description': security_group['description']}
if security_group.rules:
@ -621,6 +630,15 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase,
tenant_id=rule['tenant_id'])
return security_group_id
@staticmethod
def _validate_sgs_for_port(security_groups):
if not security_groups:
return
if not len(set(sg.stateful for sg in security_groups)) == 1:
msg = ("Cannot apply both stateful and stateless security "
"groups on the same port at the same time")
raise ext_sg.SecurityGroupConflict(reason=msg)
def _validate_security_group_rules(self, context, security_group_rules):
sg_id = self._validate_single_tenant_and_group(security_group_rules)
for rule in security_group_rules['security_group_rules']:
@ -793,15 +811,16 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase,
return port_res
def _process_port_create_security_group(self, context, port,
security_group_ids):
if validators.is_attr_set(security_group_ids):
for security_group_id in security_group_ids:
security_groups):
self._validate_sgs_for_port(security_groups)
if validators.is_attr_set(security_groups):
for sg in security_groups:
self._create_port_security_group_binding(context, port['id'],
security_group_id)
sg.id)
# Convert to list as a set might be passed here and
# this has to be serialized
port[ext_sg.SECURITYGROUPS] = (security_group_ids and
list(security_group_ids) or [])
port[ext_sg.SECURITYGROUPS] = ([sg.id for sg in security_groups] if
security_groups else [])
def _get_default_sg_id(self, context, tenant_id):
default_group = sg_obj.DefaultSecurityGroup.get_object(
@ -844,7 +863,8 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase,
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.
:returns: all security groups on port belonging to tenant)
"""
port = port['port']
if not validators.is_attr_set(port.get(ext_sg.SECURITYGROUPS)):
@ -869,7 +889,7 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase,
if port_sg_missing:
raise ext_sg.SecurityGroupNotFound(id=', '.join(port_sg_missing))
return list(requested_groups)
return sg_objs
def _ensure_default_security_group_on_port(self, context, port):
# we don't apply security groups for dhcp, router
@ -920,13 +940,13 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase,
original_port.get(ext_sg.SECURITYGROUPS),
port_updates[ext_sg.SECURITYGROUPS])):
# delete the port binding and read it with the new rules
port_updates[ext_sg.SECURITYGROUPS] = (
self._get_security_groups_on_port(context, port))
sgs = self._get_security_groups_on_port(context, port)
port_updates[ext_sg.SECURITYGROUPS] = [sg.id for sg in sgs]
self._delete_port_security_group_bindings(context, id)
self._process_port_create_security_group(
context,
updated_port,
port_updates[ext_sg.SECURITYGROUPS])
sgs)
need_notify = True
else:
updated_port[ext_sg.SECURITYGROUPS] = (

View File

@ -27,6 +27,7 @@ from neutron.db.models import securitygroup as sg_models
from neutron.db import models_v2
from neutron.db import securitygroups_db as sg_db
from neutron.extensions import securitygroup as ext_sg
from neutron.objects import securitygroup as sg_obj
DIRECTION_IP_PREFIX = {'ingress': 'source_ip_prefix',
@ -189,9 +190,12 @@ class SecurityGroupInfoAPIMixin(object):
remote_security_group_info[remote_gid][ethertype] = set()
direction = rule_in_db['direction']
stateful = self._is_security_group_stateful(context,
security_group_id)
rule_dict = {
'direction': direction,
'ethertype': ethertype}
'ethertype': ethertype,
'stateful': stateful}
for key in ('protocol', 'port_range_min', 'port_range_max',
'remote_ip_prefix', 'remote_group_id'):
@ -351,6 +355,14 @@ class SecurityGroupInfoAPIMixin(object):
"""
raise NotImplementedError()
def _is_security_group_stateful(self, context, sg_id):
"""Return whether the security group is stateful or not.
Return True if the security group associated with the given ID
is stateful, else False.
"""
return True
class SecurityGroupServerRpcMixin(SecurityGroupInfoAPIMixin,
SecurityGroupServerNotifierRpcMixin):
@ -415,3 +427,7 @@ class SecurityGroupServerRpcMixin(SecurityGroupInfoAPIMixin,
if allowed_addr_ip:
ips_by_group[security_group_id].add(allowed_addr_ip)
return ips_by_group
@db_api.retry_if_session_inactive()
def _is_security_group_stateful(self, context, sg_id):
return sg_obj.SecurityGroup.get_sg_by_id(context, sg_id).stateful

View File

@ -0,0 +1,22 @@
# Copyright (c) 2018 Nokia. 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_lib.api.definitions import stateful_security_group
from neutron_lib.api import extensions
class Stateful_security_group(extensions.APIExtensionDescriptor):
"""Extension class supporting stateful security group."""
api_definition = stateful_security_group

View File

@ -35,7 +35,8 @@ class SecurityGroupRBAC(rbac.RBACBaseObject):
class SecurityGroup(rbac_db.NeutronRbacObject):
# Version 1.0: Initial version
# Version 1.1: Add RBAC support
VERSION = '1.1'
# Version 1.2: Added stateful support
VERSION = '1.2'
# required by RbacNeutronMetaclass
rbac_db_cls = SecurityGroupRBAC
@ -46,6 +47,7 @@ class SecurityGroup(rbac_db.NeutronRbacObject):
'name': obj_fields.StringField(nullable=True),
'project_id': obj_fields.StringField(nullable=True),
'shared': obj_fields.BooleanField(default=False),
'stateful': obj_fields.BooleanField(default=True),
'is_default': obj_fields.BooleanField(default=False),
'rules': obj_fields.ListOfObjectsField(
'SecurityGroupRule', nullable=True
@ -83,10 +85,16 @@ class SecurityGroup(rbac_db.NeutronRbacObject):
bool(db_obj.get('default_security_group')))
self.obj_reset_changes(['is_default'])
@classmethod
def get_sg_by_id(cls, context, sg_id):
return super(SecurityGroup, cls).get_object(context, id=sg_id)
def obj_make_compatible(self, primitive, target_version):
_target_version = versionutils.convert_version_to_tuple(target_version)
if _target_version < (1, 1):
primitive.pop('shared')
if _target_version < (1, 2):
primitive.pop('stateful')
@classmethod
def get_bound_tenant_ids(cls, context, obj_id):

View File

@ -45,6 +45,7 @@ from neutron_lib.api.definitions import portbindings_extended as pbe_ext
from neutron_lib.api.definitions import provider_net
from neutron_lib.api.definitions import rbac_security_groups as rbac_sg_apidef
from neutron_lib.api.definitions import security_groups_port_filtering
from neutron_lib.api.definitions import stateful_security_group
from neutron_lib.api.definitions import subnet as subnet_def
from neutron_lib.api.definitions import subnet_onboard as subnet_onboard_def
from neutron_lib.api.definitions import subnetpool_prefix_ops \
@ -203,7 +204,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
pbe_ext.ALIAS,
agent_resources_synced.ALIAS,
subnet_onboard_def.ALIAS,
subnetpool_prefix_ops_def.ALIAS]
subnetpool_prefix_ops_def.ALIAS,
stateful_security_group.ALIAS]
# List of agent types for which all binding_failed ports should try to be
# rebound when agent revive
@ -1373,8 +1375,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
self._portsec_ext_port_create_processing(context, result, port)
# sgids must be got after portsec checked with security group
sgids = self._get_security_groups_on_port(context, port)
self._process_port_create_security_group(context, result, sgids)
sgs = self._get_security_groups_on_port(context, port)
self._process_port_create_security_group(context, result, sgs)
network = self.get_network(context, result['network_id'])
binding = db.add_port_binding(context, result['id'])
mech_context = driver_context.PortContext(self, context, result,
@ -1425,11 +1427,6 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
return bound_context.current
def _ensure_security_groups_on_port(self, context, port_dict):
port_compat = {'port': port_dict}
sgids = self._get_security_groups_on_port(context, port_compat)
self._process_port_create_security_group(context, port_dict, sgids)
@utils.transaction_guard
@db_api.retry_if_session_inactive()
def create_port_bulk(self, context, ports):
@ -1464,7 +1461,6 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
const.PORT_STATUS_ACTIVE),
device_id=pdata.get('device_id'),
device_owner=pdata.get('device_owner'),
security_group_ids=security_group_ids,
description=pdata.get('description'))
# Ensure that the networks exist.
@ -1527,18 +1523,16 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
process_extensions=False)
port_dict[portbindings.HOST_ID] = pdata.get(
portbindings.HOST_ID)
port_compat = {'port': port_dict}
# Activities immediately post-port-creation
self.extension_manager.process_create_port(context, pdata,
port_dict)
self._portsec_ext_port_create_processing(context, port_dict,
port_compat)
port)
# Ensure the default security group is assigned, unless one was
# specifically requested
if security_group_ids is None:
self._ensure_security_groups_on_port(context, port_dict)
sgs = self._get_security_groups_on_port(context, port)
self._process_port_create_security_group(context, port_dict,
sgs)
# process port binding
binding = db.add_port_binding(context, port_dict['id'])
@ -1554,7 +1548,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
# process allowed address pairs
db_port_obj[addr_apidef.ADDRESS_PAIRS] = (
self._process_create_allowed_address_pairs(
context, port_compat,
context, port_dict,
port_dict.get(addr_apidef.ADDRESS_PAIRS)))
# handle DHCP setup
@ -1582,13 +1576,6 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
# Perform actions after the transaction is committed
completed_ports = []
for port in port_data:
# Ensure security groups are assigned to the port, if
# specifically requested
port_dict = port['port_dict']
if port_dict.get('security_group_ids') is not None:
with db_api.CONTEXT_WRITER.using(context):
self._ensure_security_groups_on_port(context, port_dict)
resource_extend.apply_funcs('ports',
port['port_dict'],
port['port_obj'].db_obj)

View File

@ -61,6 +61,7 @@ NETWORK_API_EXTENSIONS+=",standard-attr-revisions"
NETWORK_API_EXTENSIONS+=",standard-attr-segment"
NETWORK_API_EXTENSIONS+=",standard-attr-timestamp"
NETWORK_API_EXTENSIONS+=",standard-attr-tag"
NETWORK_API_EXTENSIONS+=",stateful-security-group"
NETWORK_API_EXTENSIONS+=",subnet_allocation"
NETWORK_API_EXTENSIONS+=",subnet-dns-publish-fixed-ip"
NETWORK_API_EXTENSIONS+=",tag-ports-during-bulk-creation"

View File

@ -513,13 +513,16 @@ class SGServerRpcCallBackTestCase(test_sg.SecurityGroupDBTestCase):
ctx, devices=devices)
expected = {
'security_groups': {sg1_id: [
{'direction': 'egress', 'ethertype': const.IPv4},
{'direction': 'egress', 'ethertype': const.IPv6},
{'direction': 'egress', 'ethertype': const.IPv4,
'stateful': True},
{'direction': 'egress', 'ethertype': const.IPv6,
'stateful': True},
{'direction': u'ingress',
'protocol': const.PROTO_NAME_TCP,
'ethertype': const.IPv4,
'port_range_max': 25, 'port_range_min': 24,
'remote_group_id': sg2_id}
'remote_group_id': sg2_id,
'stateful': True}
]},
'sg_member_ips': {sg2_id: {
'IPv4': set([port_ip2]),
@ -628,13 +631,16 @@ class SGServerRpcCallBackTestCase(test_sg.SecurityGroupDBTestCase):
ctx, devices=devices)
expected = {
'security_groups': {sg1_id: [
{'direction': 'egress', 'ethertype': const.IPv4},
{'direction': 'egress', 'ethertype': const.IPv6},
{'direction': 'egress', 'ethertype': const.IPv4,
'stateful': True},
{'direction': 'egress', 'ethertype': const.IPv6,
'stateful': True},
{'direction': u'ingress',
'protocol': const.PROTO_NAME_TCP,
'ethertype': const.IPv6,
'port_range_max': 22, 'port_range_min': 22,
'remote_group_id': sg1_id}
'remote_group_id': sg1_id,
'stateful': True}
]},
'sg_member_ips': {sg1_id: {
'IPv6': set(),

View File

@ -97,7 +97,7 @@ class SecurityGroupDbMixinTestCase(testlib_api.SqlTestCase):
def test_update_security_group_conflict(self):
with mock.patch.object(registry, "notify") as mock_notify:
mock_notify.side_effect = exceptions.CallbackFailure(Exception())
secgroup = {'security_group': mock.ANY}
secgroup = {'security_group': FAKE_SECGROUP}
with testtools.ExpectedException(
securitygroup.SecurityGroupConflict):
self.mixin.update_security_group(self.ctx, 'foo_id', secgroup)
@ -272,6 +272,7 @@ class SecurityGroupDbMixinTestCase(testlib_api.SqlTestCase):
'project_id': FAKE_SECGROUP['security_group']['tenant_id'],
'name': 'default',
'description': 'Default security group',
'stateful': mock.ANY,
'security_group_rules': [
# Four rules for egress/ingress and ipv4/ipv6
mock.ANY, mock.ANY, mock.ANY, mock.ANY,

View File

@ -92,8 +92,6 @@ class PortSecurityTestPlugin(db_base_plugin_v2.NeutronDbPluginV2,
def create_port(self, context, port):
p = port['port']
p[ext_sg.SECURITYGROUPS] = self._get_security_groups_on_port(
context, port)
neutron_db = super(PortSecurityTestPlugin, self).create_port(
context, port)
p.update(neutron_db)
@ -111,9 +109,12 @@ class PortSecurityTestPlugin(db_base_plugin_v2.NeutronDbPluginV2,
if has_ip and port_security:
self._ensure_default_security_group_on_port(context, port)
sgs = self._get_security_groups_on_port(context, port)
p[ext_sg.SECURITYGROUPS] = [sg['id'] for sg in sgs] if sgs else None
if (p.get(ext_sg.SECURITYGROUPS) and p[psec.PORTSECURITY]):
self._process_port_create_security_group(
context, p, p[ext_sg.SECURITYGROUPS])
context, p, sgs)
return port['port']
@ -156,11 +157,11 @@ class PortSecurityTestPlugin(db_base_plugin_v2.NeutronDbPluginV2,
if (delete_security_groups or has_security_groups):
# delete the port binding and read it with the new rules.
self._delete_port_security_group_bindings(context, id)
sgids = self._get_security_groups_on_port(context, port)
sgs = self._get_security_groups_on_port(context, port)
# process port create sec groups needs port id
port['id'] = id
self._process_port_create_security_group(context,
ret_port, sgids)
ret_port, sgs)
if psec.PORTSECURITY in port['port']:
self._process_port_port_security_update(

View File

@ -210,24 +210,24 @@ class SecurityGroupTestPlugin(db_base_plugin_v2.NeutronDbPluginV2,
if not validators.is_attr_set(port['port'].get(ext_sg.SECURITYGROUPS)):
port['port'][ext_sg.SECURITYGROUPS] = [default_sg]
with db_api.CONTEXT_WRITER.using(context):
sgids = self._get_security_groups_on_port(context, port)
sgs = self._get_security_groups_on_port(context, port)
port = super(SecurityGroupTestPlugin, self).create_port(context,
port)
self._process_port_create_security_group(context, port,
sgids)
sgs)
return port
def update_port(self, context, id, port):
with db_api.CONTEXT_WRITER.using(context):
if ext_sg.SECURITYGROUPS in port['port']:
port['port'][ext_sg.SECURITYGROUPS] = (
self._get_security_groups_on_port(context, port))
sgs = self._get_security_groups_on_port(context, port)
port['port'][ext_sg.SECURITYGROUPS] = [
sg['id'] for sg in sgs] if sgs else None
# delete the port binding and read it with the new rules
self._delete_port_security_group_bindings(context, id)
port['port']['id'] = id
self._process_port_create_security_group(
context, port['port'],
port['port'].get(ext_sg.SECURITYGROUPS))
context, port['port'], sgs)
port = super(SecurityGroupTestPlugin, self).update_port(
context, id, port)
return port

View File

@ -98,7 +98,7 @@ object_data = {
'RouterL3AgentBinding': '1.0-c5ba6c95e3a4c1236a55f490cd67da82',
'RouterPort': '1.0-c8c8f499bcdd59186fcd83f323106908',
'RouterRoute': '1.0-07fc5337c801fb8c6ccfbcc5afb45907',
'SecurityGroup': '1.1-f712265418f154f7c080e02857ffe2ef',
'SecurityGroup': '1.2-7b63b834e511856f54a09282d6843ecc',
'SecurityGroupPortBinding': '1.0-6879d5c0af80396ef5a72934b6a6ef20',
'SecurityGroupRBAC': '1.0-192845c5ed0718e1c54fac36936fcd7d',
'SecurityGroupRule': '1.0-e9b8dace9d48b936c62ad40fe1f339d5',

View File

@ -80,6 +80,16 @@ class SecurityGroupDbObjTestCase(test_base.BaseDbObjectTestCase,
# generated; we picked the former here
rule['remote_group_id'] = None
def _create_test_security_group(self):
self.objs[0].create()
return self.objs[0]
def test_object_version_degradation_1_2_to_1_1_no_stateful(self):
sg_stateful_obj = self._create_test_security_group()
sg_no_stateful_obj = sg_stateful_obj.obj_to_primitive('1.1')
self.assertNotIn('stateful',
sg_no_stateful_obj['versioned_object.data'])
def test_is_default_True(self):
fields = self.obj_fields[0].copy()
sg_obj = self._make_object(fields)

View File

@ -56,7 +56,12 @@ class BaseTestEventHandler(object):
get_sec_group_port_patch = mock.patch(
'neutron.db.securitygroups_db.SecurityGroupDbMixin.'
'_get_security_groups_on_port')
get_sec_group_port_patch.start()
process_port_create_security_group_patch = mock.patch(
'neutron.db.securitygroups_db.SecurityGroupDbMixin.'
'_process_port_create_security_group')
process_port_create_security_group_patch.start()
handler_patch = mock.patch(
'neutron.quota.resource.TrackedResource._db_event_handler')
self.handler_mock = handler_patch.start()

View File

@ -0,0 +1,18 @@
---
prelude: >
Added support to create stateless security groups.
features:
- |
Added support for a new stateful-security-group api extension that
implements stateless security groups for the iptables drivers.
upgrade:
- |
Currently existing security groups will all be set to stateful during
the alembic migration.
security:
- |
The ``stateless security group`` feature does not work with
OVS nor OVN driver as the driver is not aware of the ``stateful`` attribute
in the security group. If ``stateful`` attribute is provided with a
``False`` value then the attribute value is ignored and the security
group would behave as stateful.