--- /dev/null
+# Copyright 2015 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+""" Add default security group table
+
+Revision ID: 14be42f3d0a5
+Revises: 41662e32bce2
+Create Date: 2014-12-12 14:54:11.123635
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '14be42f3d0a5'
+down_revision = '26b54cf9024d'
+
+from alembic import op
+import sqlalchemy as sa
+
+from neutron.common import exceptions
+
+# Models can change in time, but migration should rely only on exact
+# model state at the current moment, so a separate model is created
+# here.
+security_group = sa.Table('securitygroups', sa.MetaData(),
+ sa.Column('id', sa.String(length=36),
+ nullable=False),
+ sa.Column('name', sa.String(255)),
+ sa.Column('tenant_id', sa.String(255)))
+
+
+class DuplicateSecurityGroupsNamedDefault(exceptions.Conflict):
+ message = _("Some tenants have more than one security group named "
+ "'default': %(duplicates)s. All duplicate 'default' security "
+ "groups must be resolved before upgrading the database.")
+
+
+def upgrade():
+ table = op.create_table(
+ 'default_security_group',
+ sa.Column('tenant_id', sa.String(length=255), nullable=False),
+ sa.Column('security_group_id', sa.String(length=36), nullable=False),
+ sa.PrimaryKeyConstraint('tenant_id'),
+ sa.ForeignKeyConstraint(['security_group_id'],
+ ['securitygroups.id'],
+ ondelete="CASCADE"))
+ sel = (sa.select([security_group.c.tenant_id,
+ security_group.c.id])
+ .where(security_group.c.name == 'default'))
+ ins = table.insert(inline=True).from_select(['tenant_id',
+ 'security_group_id'], sel)
+ op.execute(ins)
+
+
+def downgrade():
+ op.drop_table('default_security_group')
+
+
+def check_sanity(connection):
+ res = get_duplicate_default_security_groups(connection)
+ if res:
+ raise DuplicateSecurityGroupsNamedDefault(
+ duplicates='; '.join('tenant %s: %s' %
+ (tenant_id, ', '.join(groups))
+ for tenant_id, groups in res.iteritems()))
+
+
+def get_duplicate_default_security_groups(connection):
+ insp = sa.engine.reflection.Inspector.from_engine(connection)
+ if 'securitygroups' not in insp.get_table_names():
+ return {}
+ session = sa.orm.Session(bind=connection.connect())
+ subq = (session.query(security_group.c.tenant_id)
+ .filter(security_group.c.name == 'default')
+ .group_by(security_group.c.tenant_id)
+ .having(sa.func.count() > 1)
+ .subquery())
+
+ sg = (session.query(security_group)
+ .join(subq, security_group.c.tenant_id == subq.c.tenant_id)
+ .filter(security_group.c.name == 'default')
+ .all())
+ res = {}
+ for s in sg:
+ res.setdefault(s.tenant_id, []).append(s.id)
+ return res
-26b54cf9024d
+14be42f3d0a5
\ No newline at end of file
from alembic import command as alembic_command
from alembic import config as alembic_config
+from alembic import environment
from alembic import script as alembic_script
from alembic import util as alembic_util
from oslo.config import cfg
revision = sign + str(CONF.command.delta)
else:
revision = CONF.command.revision
-
+ if CONF.command.name == 'upgrade' and not CONF.command.sql:
+ run_sanity_checks(config, revision)
do_alembic_command(config, cmd, revision, sql=CONF.command.sql)
return config
+def run_sanity_checks(config, revision):
+ script_dir = alembic_script.ScriptDirectory.from_config(config)
+
+ def check_sanity(rev, context):
+ for script in script_dir.iterate_revisions(revision, rev):
+ if hasattr(script.module, 'check_sanity'):
+ script.module.check_sanity(context.connection)
+ return []
+
+ with environment.EnvironmentContext(config, script_dir,
+ fn=check_sanity,
+ starting_rev=None,
+ destination_rev=revision):
+ script_dir.run_env()
+
+
def main():
CONF(project='neutron')
config = get_alembic_config()
description = sa.Column(sa.String(255))
+class DefaultSecurityGroup(model_base.BASEV2):
+ __tablename__ = 'default_security_group'
+
+ tenant_id = sa.Column(sa.String(255), primary_key=True, nullable=False)
+ security_group_id = sa.Column(sa.String(36),
+ sa.ForeignKey("securitygroups.id",
+ ondelete="CASCADE"),
+ nullable=False)
+ security_group = orm.relationship(
+ SecurityGroup, lazy='joined',
+ backref=orm.backref('default_security_group', cascade='all,delete'),
+ primaryjoin="SecurityGroup.id==DefaultSecurityGroup.security_group_id",
+ )
+
+
class SecurityGroupPortBinding(model_base.BASEV2):
"""Represents binding between neutron ports and security profiles."""
tenant_id=tenant_id,
name=s['name'])
context.session.add(security_group_db)
+ if default_sg:
+ context.session.add(DefaultSecurityGroup(
+ security_group=security_group_db,
+ tenant_id=security_group_db['tenant_id']))
for ethertype in ext_sg.sg_supported_ethertypes:
- if s.get('name') == 'default':
+ if default_sg:
# Allow intercommunication
ingress_rule = SecurityGroupRule(
id=uuidutils.generate_uuid(), tenant_id=tenant_id,
:returns: the default security group id.
"""
- filters = {'name': ['default'], 'tenant_id': [tenant_id]}
- default_group = self.get_security_groups(context, filters,
- default_sg=True)
- if not default_group:
+ query = self._model_query(context, DefaultSecurityGroup)
+ try:
+ default_group = query.filter(
+ DefaultSecurityGroup.tenant_id == tenant_id).one()
+ except exc.NoResultFound:
security_group = {
'security_group': {'name': 'default',
'tenant_id': tenant_id,
'description': _('Default security group')}
}
- ret = self.create_security_group(context, security_group, True)
+ ret = self.create_security_group(context, security_group,
+ default_sg=True)
return ret['id']
else:
- return default_group[0]['id']
+ return default_group['security_group_id']
def _get_security_groups_on_port(self, context, port):
"""Check that all security groups on port belong to tenant.
import alembic
import alembic.autogenerate
import alembic.migration
+from alembic import script as alembic_script
import mock
from oslo.config import cfg
from oslo.config import fixture as config_fixture
class TestModelsMigrationsPsql(_TestModelsMigrations,
test_base.PostgreSQLOpportunisticTestCase):
pass
+
+
+class TestSanityCheck(test_base.DbTestCase):
+
+ def setUp(self):
+ super(TestSanityCheck, self).setUp()
+ self.alembic_config = migration.get_alembic_config()
+ self.alembic_config.neutron_config = cfg.CONF
+
+ def test_check_sanity_14be42f3d0a5(self):
+ SecurityGroup = sqlalchemy.Table(
+ 'securitygroups', sqlalchemy.MetaData(),
+ sqlalchemy.Column('id', sqlalchemy.String(length=36),
+ nullable=False),
+ sqlalchemy.Column('name', sqlalchemy.String(255)),
+ sqlalchemy.Column('tenant_id', sqlalchemy.String(255)))
+
+ with self.engine.connect() as conn:
+ SecurityGroup.create(conn)
+ conn.execute(SecurityGroup.insert(), [
+ {'id': '123d4s', 'tenant_id': 'sssda1', 'name': 'default'},
+ {'id': '123d4', 'tenant_id': 'sssda1', 'name': 'default'}
+ ])
+ script_dir = alembic_script.ScriptDirectory.from_config(
+ self.alembic_config)
+ script = script_dir.get_revision("14be42f3d0a5").module
+ self.assertRaises(script.DuplicateSecurityGroupsNamedDefault,
+ script.check_sanity, conn)
self.mock_alembic_err.side_effect = SystemExit
def _main_test_helper(self, argv, func_name, exp_args=(), exp_kwargs={}):
- with mock.patch.object(sys, 'argv', argv):
+ with mock.patch.object(sys, 'argv', argv), mock.patch.object(
+ cli, 'run_sanity_checks'):
cli.main()
self.do_alembic_cmd.assert_has_calls(
[mock.call(mock.ANY, func_name, *exp_args, **exp_kwargs)]