From 4b6c2246c09eccbb193b4a59a8dfee9e24f97bed Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Mon, 19 Aug 2019 13:09:20 +0000 Subject: [PATCH] Force "network_id" in "subnet" DB registers The "subnet" OVO does not allow to have an empty (None) "network_id" but the DB "subnet" table allows to have this parameter empty (NULL) in the database. In order to avoid any problem like the one described in the bug, this patch ensures the database "subnet" register does always have a "network_id" value and if a "network" register is being deleted, all related "subnet" registers are checked first. Change-Id: Iad210f0585b4201fdb87187b44a9b42267b58db4 Closes-Bug: #1839658 --- .../alembic_migrations/versions/EXPAND_HEAD | 2 +- .../c613d0b82681_subnet_force_network_id.py | 35 +++++++++++++++++++ neutron/db/models_v2.py | 3 +- ..._3b935b28e7a0_migrate_to_pluggable_ipam.py | 3 +- .../unit/db/test_ipam_pluggable_backend.py | 11 ++++-- 5 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 neutron/db/migration/alembic_migrations/versions/train/expand/c613d0b82681_subnet_force_network_id.py diff --git a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD index f256dedc907..ffa2bbaaf66 100644 --- a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD +++ b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD @@ -1 +1 @@ -63fd95af7dcd +c613d0b82681 diff --git a/neutron/db/migration/alembic_migrations/versions/train/expand/c613d0b82681_subnet_force_network_id.py b/neutron/db/migration/alembic_migrations/versions/train/expand/c613d0b82681_subnet_force_network_id.py new file mode 100644 index 00000000000..0af8097cc5a --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/train/expand/c613d0b82681_subnet_force_network_id.py @@ -0,0 +1,35 @@ +# Copyright 2019 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. +# + +from alembic import op +import sqlalchemy as sa + + +"""subnet force network id + +Revision ID: c613d0b82681 +Revises: 63fd95af7dcd +Create Date: 2019-08-19 11:15:14.443244 + +""" + +# revision identifiers, used by Alembic. +revision = 'c613d0b82681' +down_revision = '63fd95af7dcd' + + +def upgrade(): + op.alter_column('subnets', 'network_id', nullable=False, + existing_type=sa.String(36)) diff --git a/neutron/db/models_v2.py b/neutron/db/models_v2.py index 6863cc3b267..cd82965c087 100644 --- a/neutron/db/models_v2.py +++ b/neutron/db/models_v2.py @@ -155,7 +155,8 @@ class Subnet(standard_attr.HasStandardAttributes, model_base.BASEV2, """ name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE)) - network_id = sa.Column(sa.String(36), sa.ForeignKey('networks.id')) + network_id = sa.Column(sa.String(36), sa.ForeignKey('networks.id'), + nullable=False) # Added by the segments service plugin segment_id = sa.Column(sa.String(36), sa.ForeignKey('networksegments.id')) subnetpool_id = sa.Column(sa.String(36), index=True) diff --git a/neutron/tests/functional/db/migrations/test_3b935b28e7a0_migrate_to_pluggable_ipam.py b/neutron/tests/functional/db/migrations/test_3b935b28e7a0_migrate_to_pluggable_ipam.py index ba066f972ee..c9a53f449e1 100644 --- a/neutron/tests/functional/db/migrations/test_3b935b28e7a0_migrate_to_pluggable_ipam.py +++ b/neutron/tests/functional/db/migrations/test_3b935b28e7a0_migrate_to_pluggable_ipam.py @@ -54,7 +54,8 @@ class MigrationToPluggableIpamMixin(object): cidr=cidr, ip_version=ip_version, standard_attr_id=self._gen_attr_id(engine, - 'subnets')) + 'subnets'), + network_id=network_id) engine.execute(subnets.insert().values(subnet_dict)) if data[cidr].get('pools'): diff --git a/neutron/tests/unit/db/test_ipam_pluggable_backend.py b/neutron/tests/unit/db/test_ipam_pluggable_backend.py index c55d2162ad5..9269852b6be 100644 --- a/neutron/tests/unit/db/test_ipam_pluggable_backend.py +++ b/neutron/tests/unit/db/test_ipam_pluggable_backend.py @@ -29,6 +29,7 @@ import webob.exc from neutron.db import ipam_backend_mixin from neutron.db import ipam_pluggable_backend from neutron.ipam import requests as ipam_req +from neutron.objects import network as network_obj from neutron.objects import ports as port_obj from neutron.objects import subnet as obj_subnet from neutron.tests.unit.db import test_db_base_plugin_v2 as test_db_base @@ -764,11 +765,14 @@ class TestDbBasePluginIpam(test_db_base.NeutronDbPluginV2TestCase): def test_update_db_subnet_unchanged_pools(self, pool_mock): old_pools = [{'start': '192.1.1.2', 'end': '192.1.1.254'}] context = self.admin_context + network_id = uuidutils.generate_uuid() + network_obj.Network(context, id=network_id).create() subnet = {'id': uuidutils.generate_uuid(), 'ip_version': constants.IP_VERSION_4, 'cidr': netaddr.IPNetwork('192.1.1.0/24'), 'ipv6_address_mode': None, - 'ipv6_ra_mode': None} + 'ipv6_ra_mode': None, + 'network_id': network_id} subnet_with_pools = subnet.copy() subnet_obj = obj_subnet.Subnet(context, **subnet_with_pools) subnet_obj.create() @@ -782,11 +786,14 @@ class TestDbBasePluginIpam(test_db_base.NeutronDbPluginV2TestCase): def test_update_db_subnet_new_pools(self, pool_mock): old_pools = [{'start': '192.1.1.2', 'end': '192.1.1.254'}] context = self.admin_context + network_id = uuidutils.generate_uuid() + network_obj.Network(context, id=network_id).create() subnet = {'id': uuidutils.generate_uuid(), 'ip_version': constants.IP_VERSION_4, 'cidr': netaddr.IPNetwork('192.1.1.0/24'), 'ipv6_address_mode': None, - 'ipv6_ra_mode': None} + 'ipv6_ra_mode': None, + 'network_id': network_id} # make a copy of subnet for validation, since update_subnet changes # incoming subnet dict expected_subnet = subnet.copy()