diff --git a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD index e796b9d0fef..3a8182a1767 100644 --- a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD +++ b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD @@ -1 +1 @@ -45f8dd33480b +5abc0278ca73 diff --git a/neutron/db/migration/alembic_migrations/versions/newton/expand/5abc0278ca73_add_support_for_vlan_trunking.py b/neutron/db/migration/alembic_migrations/versions/newton/expand/5abc0278ca73_add_support_for_vlan_trunking.py new file mode 100644 index 00000000000..ace47bb2850 --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/newton/expand/5abc0278ca73_add_support_for_vlan_trunking.py @@ -0,0 +1,49 @@ +# 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. +# + +revision = '5abc0278ca73' +down_revision = '45f8dd33480b' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.create_table('trunks', + sa.Column('tenant_id', sa.String(length=255), nullable=True, + index=True), + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('port_id', sa.String(length=36), nullable=False), + sa.Column('standard_attr_id', sa.BigInteger(), nullable=False), + sa.ForeignKeyConstraint(['port_id'], ['ports.id'], + ondelete='CASCADE'), + sa.ForeignKeyConstraint(['standard_attr_id'], + ['standardattributes.id'], + ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('port_id'), + sa.UniqueConstraint('standard_attr_id') + ) + op.create_table('subports', + sa.Column('port_id', sa.String(length=36), nullable=False), + sa.Column('trunk_id', sa.String(length=36), nullable=False), + sa.Column('segmentation_type', sa.String(length=32), nullable=False), + sa.Column('segmentation_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['port_id'], ['ports.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['trunk_id'], ['trunks.id'], + ondelete='CASCADE'), + sa.PrimaryKeyConstraint('port_id', 'trunk_id'), + sa.UniqueConstraint('port_id'), + sa.UniqueConstraint('trunk_id', 'segmentation_type', 'segmentation_id', + name='uniq_subport0trunk_id0segmentation_type0segmentation_id') + ) diff --git a/neutron/db/migration/models/head.py b/neutron/db/migration/models/head.py index 98e7e42e409..a700cadc861 100644 --- a/neutron/db/migration/models/head.py +++ b/neutron/db/migration/models/head.py @@ -59,6 +59,7 @@ from neutron.plugins.ml2.drivers import type_vlan # noqa from neutron.plugins.ml2.drivers import type_vxlan # noqa from neutron.plugins.ml2 import models # noqa from neutron.services.auto_allocate import models # noqa +from neutron.services.trunk import models # noqa def get_metadata(): diff --git a/neutron/services/trunk/__init__.py b/neutron/services/trunk/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/services/trunk/db.py b/neutron/services/trunk/db.py new file mode 100644 index 00000000000..2da581083fd --- /dev/null +++ b/neutron/services/trunk/db.py @@ -0,0 +1,35 @@ +# Copyright 2016 Hewlett Packard Enterprise Development Company, LP +# +# 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 oslo_db import exception as db_exc +from oslo_utils import uuidutils + +from neutron.services.trunk import exceptions +from neutron.services.trunk import models + + +def create_trunk(context, port_id, description=None): + """Create a trunk (with description) given the parent port uuid.""" + try: + with context.session.begin(subtransactions=True): + context.session.add( + models.Trunk( + id=uuidutils.generate_uuid(), + tenant_id=context.tenant_id, + port_id=port_id, + description=description)) + except db_exc.DBDuplicateEntry: + raise exceptions.TrunkPortInUse(port_id=port_id) diff --git a/neutron/services/trunk/exceptions.py b/neutron/services/trunk/exceptions.py new file mode 100644 index 00000000000..830c0fb389b --- /dev/null +++ b/neutron/services/trunk/exceptions.py @@ -0,0 +1,22 @@ +# Copyright 2016 Hewlett Packard Enterprise Development Company, LP +# +# 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._i18n import _ +from neutron_lib import exceptions as n_exc + + +class TrunkPortInUse(n_exc.InUse): + message = _("Port %(port_id)s is in use by another trunk.") diff --git a/neutron/services/trunk/models.py b/neutron/services/trunk/models.py new file mode 100644 index 00000000000..89b45acaa0f --- /dev/null +++ b/neutron/services/trunk/models.py @@ -0,0 +1,73 @@ +# Copyright 2016 Red Hat Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import sqlalchemy as sa + +from neutron.db import model_base +from neutron.db import models_v2 + + +class Trunk(model_base.HasStandardAttributes, model_base.BASEV2, + model_base.HasId, model_base.HasTenant): + + port_id = sa.Column(sa.String(36), + sa.ForeignKey('ports.id', + ondelete='CASCADE'), + nullable=False, + unique=True) + port = sa.orm.relationship( + models_v2.Port, + backref=sa.orm.backref('trunk_port', lazy='joined', uselist=False, + cascade='delete')) + + +class SubPort(model_base.BASEV2): + + port_id = sa.Column(sa.String(36), + sa.ForeignKey('ports.id', + ondelete='CASCADE'), + nullable=False, + unique=True, + primary_key=True) + port = sa.orm.relationship( + models_v2.Port, + backref=sa.orm.backref('sub_port', lazy='joined', uselist=False, + cascade='delete')) + + trunk_id = sa.Column(sa.String(36), + sa.ForeignKey('trunks.id', + ondelete='CASCADE'), + nullable=False, + primary_key=True) + + segmentation_type = sa.Column(sa.String(32), nullable=False) + segmentation_id = sa.Column(sa.Integer, nullable=False) + + __table_args__ = ( + sa.UniqueConstraint( + 'trunk_id', + 'segmentation_type', + 'segmentation_id', + name='uniq_subport0trunk_id0segmentation_type0segmentation_id'), + model_base.BASEV2.__table_args__ + ) + +# NOTE(armax) constraints like the following are implemented via +# business logic rules: +# +# Deletion of a trunk automatically deletes all of its subports; +# Deletion of a (child) port referred by a subport is forbidden; +# Deletion of a (parent) port referred by a trunk is forbidden; +# A port cannot be a subport and a trunk port at the same time (nested). diff --git a/neutron/tests/unit/services/trunk/__init__.py b/neutron/tests/unit/services/trunk/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/tests/unit/services/trunk/test_db.py b/neutron/tests/unit/services/trunk/test_db.py new file mode 100644 index 00000000000..c9aac638b80 --- /dev/null +++ b/neutron/tests/unit/services/trunk/test_db.py @@ -0,0 +1,50 @@ +# Copyright 2016 Hewlett Packard Enterprise Development Company, LP +# +# 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 import context +from neutron.db import models_v2 +from neutron.services.trunk import db +from neutron.services.trunk import exceptions +from neutron.tests.unit import testlib_api + + +class TrunkDBTestCase(testlib_api.SqlTestCase): + + def setUp(self): + super(TrunkDBTestCase, self).setUp() + self.ctx = context.get_admin_context() + + def _add_network(self, net_id): + with self.ctx.session.begin(subtransactions=True): + self.ctx.session.add(models_v2.Network(id=net_id)) + + def _add_port(self, net_id, port_id): + with self.ctx.session.begin(subtransactions=True): + port = models_v2.Port(id=port_id, + network_id=net_id, + mac_address='foo_mac_%s' % port_id, + admin_state_up=True, + status='DOWN', + device_id='', + device_owner='') + self.ctx.session.add(port) + + def test_create_trunk_raise_port_in_use(self): + self._add_network('foo_net') + self._add_port('foo_net', 'foo_port') + db.create_trunk(self.ctx, 'foo_port') + self.assertRaises(exceptions.TrunkPortInUse, + db.create_trunk, + self.ctx, 'foo_port')