diff --git a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD index 2df5d8a3747..765e9656280 100644 --- a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD +++ b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD @@ -1 +1 @@ -a964d94b4677 +26d1e9f5c766 diff --git a/neutron/db/migration/alembic_migrations/versions/wallaby/expand/26d1e9f5c766_add_standard_attributes_to_address_group.py b/neutron/db/migration/alembic_migrations/versions/wallaby/expand/26d1e9f5c766_add_standard_attributes_to_address_group.py new file mode 100644 index 00000000000..d712a04d128 --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/wallaby/expand/26d1e9f5c766_add_standard_attributes_to_address_group.py @@ -0,0 +1,95 @@ +# Copyright 2020 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 + + +"""Add standard attributes to address group + +Revision ID: 26d1e9f5c766 +Revises: a964d94b4677 +Create Date: 2020-12-02 17:38:45.331048 + +""" + +# revision identifiers, used by Alembic. +revision = '26d1e9f5c766' +down_revision = 'a964d94b4677' + + +TABLE = 'address_groups' + +TABLE_MODEL = sa.Table(TABLE, sa.MetaData(), + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('description', sa.String(length=255), + nullable=True), + sa.Column('standard_attr_id', sa.BigInteger(), + nullable=True)) + +standardattrs = sa.Table( + 'standardattributes', sa.MetaData(), + sa.Column('id', sa.BigInteger(), primary_key=True, autoincrement=True), + sa.Column('resource_type', sa.String(length=255), nullable=False), + sa.Column('description', sa.String(length=255), nullable=True)) + + +def generate_records_for_existing(): + session = sa.orm.Session(bind=op.get_bind()) + with session.begin(subtransactions=True): + for row in session.query(TABLE_MODEL): + res = session.execute( + standardattrs.insert().values(resource_type=TABLE, + description=row[1]) + ) + session.execute( + TABLE_MODEL.update().values( + standard_attr_id=res.inserted_primary_key[0]).where( + TABLE_MODEL.c.id == row[0]) + ) + # this commit is necessary to allow further operations + session.commit() + + +def upgrade(): + op.add_column(TABLE, sa.Column('standard_attr_id', sa.BigInteger(), + nullable=True)) + generate_records_for_existing() + + # add the constraint now that everything is populated on that table + op.create_foreign_key( + constraint_name=None, source_table=TABLE, + referent_table='standardattributes', + local_cols=['standard_attr_id'], remote_cols=['id'], + ondelete='CASCADE') + op.alter_column(TABLE, 'standard_attr_id', nullable=False, + existing_type=sa.BigInteger(), existing_nullable=True, + existing_server_default=False) + op.create_unique_constraint( + constraint_name='uniq_%s0standard_attr_id' % TABLE, + table_name=TABLE, columns=['standard_attr_id']) + op.drop_column(TABLE, 'description') + + +def expand_drop_exceptions(): + """Drop the description column for table address_groups + + Drop the existing description column in address_groups table since + address_groups are now associated with standard_attributes. + """ + + return { + sa.Column: ['%s.description' % TABLE] + } diff --git a/neutron/db/models/address_group.py b/neutron/db/models/address_group.py index cec2e218560..e0e17f3751a 100644 --- a/neutron/db/models/address_group.py +++ b/neutron/db/models/address_group.py @@ -10,11 +10,14 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron_lib.api.definitions import address_group as ag 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 neutron.db import standard_attr + class AddressAssociation(model_base.BASEV2): """Represents a neutron address group's address association.""" @@ -26,16 +29,18 @@ class AddressAssociation(model_base.BASEV2): sa.ForeignKey("address_groups.id", ondelete="CASCADE"), nullable=False, primary_key=True) + revises_on_change = ('address_groups',) -class AddressGroup(model_base.BASEV2, model_base.HasId, model_base.HasProject): +class AddressGroup(standard_attr.HasStandardAttributes, + model_base.BASEV2, model_base.HasId, model_base.HasProject): """Represents a neutron address group.""" __tablename__ = "address_groups" name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE)) - description = sa.Column(sa.String(db_const.LONG_DESCRIPTION_FIELD_SIZE)) addresses = orm.relationship(AddressAssociation, backref=orm.backref('address_groups', load_on_pending=True), lazy='subquery', cascade='all, delete-orphan') + api_collections = [ag.ALIAS] diff --git a/neutron/objects/address_group.py b/neutron/objects/address_group.py index c063f130831..c709b82e683 100644 --- a/neutron/objects/address_group.py +++ b/neutron/objects/address_group.py @@ -12,6 +12,7 @@ import netaddr from neutron_lib.objects import common_types +from oslo_utils import versionutils from oslo_versionedobjects import fields as obj_fields from neutron.db.models import address_group as models @@ -22,19 +23,27 @@ from neutron.objects import base class AddressGroup(base.NeutronDbObject): # Version 1.0: Initial version VERSION = '1.0' + # Version 1.1: Added standard attributes + VERSION = '1.1' db_model = models.AddressGroup fields = { 'id': common_types.UUIDField(), 'name': obj_fields.StringField(nullable=True), - 'description': obj_fields.StringField(nullable=True), 'project_id': obj_fields.StringField(), 'addresses': obj_fields.ListOfObjectsField('AddressAssociation', nullable=True) } synthetic_fields = ['addresses'] + def obj_make_compatible(self, primitive, target_version): + _target_version = versionutils.convert_version_to_tuple(target_version) + if _target_version < (1, 1): + standard_fields = ['revision_number', 'created_at', 'updated_at'] + for f in standard_fields: + primitive.pop(f, None) + @base.NeutronObjectRegistry.register class AddressAssociation(base.NeutronDbObject): diff --git a/neutron/tests/functional/db/test_migrations.py b/neutron/tests/functional/db/test_migrations.py index fee035da2fa..d48c8b222ed 100644 --- a/neutron/tests/functional/db/test_migrations.py +++ b/neutron/tests/functional/db/test_migrations.py @@ -272,7 +272,7 @@ class _TestModelsMigrations(test_migrations.ModelsMigrationsSync): """Identify excepted operations that are allowed for the branch.""" # For alembic the clause is AddColumn or DropColumn column = clauseelement.column.name - table = clauseelement.column.table.name + table = clauseelement.table_name element_name = '.'.join([table, column]) for alembic_type, excepted_names in exceptions.items(): if alembic_type == sqlalchemy.Column: diff --git a/neutron/tests/unit/objects/test_address_group.py b/neutron/tests/unit/objects/test_address_group.py index 798d10f77c0..a744a55e90f 100644 --- a/neutron/tests/unit/objects/test_address_group.py +++ b/neutron/tests/unit/objects/test_address_group.py @@ -29,6 +29,23 @@ class AddressGroupDbObjectTestCase( def setUp(self): super(AddressGroupDbObjectTestCase, self).setUp() + def _create_test_address_group(self): + self.objs[0].create() + return self.objs[0] + + def test_object_version_degradation_1_1_to_1_0_no_standard_attrs(self): + ag_obj = self._create_test_address_group() + ag_obj_1_0 = ag_obj.obj_to_primitive('1.0') + self.assertNotIn('revision_number', + ag_obj_1_0['versioned_object.data']) + self.assertNotIn('created_at', + ag_obj_1_0['versioned_object.data']) + self.assertNotIn('updated_at', + ag_obj_1_0['versioned_object.data']) + # description filed was added to initial version separately + self.assertIn('description', + ag_obj_1_0['versioned_object.data']) + class AddressAssociationIfaceObjectTestCase( obj_test_base.BaseObjectIfaceTestCase): diff --git a/neutron/tests/unit/objects/test_objects.py b/neutron/tests/unit/objects/test_objects.py index a6022d39fb6..9ec85540bae 100644 --- a/neutron/tests/unit/objects/test_objects.py +++ b/neutron/tests/unit/objects/test_objects.py @@ -27,7 +27,7 @@ from neutron.tests import base as test_base # alphabetic order. object_data = { 'AddressAssociation': '1.0-b92160a3dd2fb7b951adcd2e6ae1665a', - 'AddressGroup': '1.0-a402a66e35d25e9381eab40e1e709907', + 'AddressGroup': '1.1-78c35b6ac495407be56b8fcdbeda4d67', 'AddressScope': '1.1-dd0dfdb67775892d3adc090e28e43bd8', 'AddressScopeRBAC': '1.0-192845c5ed0718e1c54fac36936fcd7d', 'Agent': '1.1-64b670752d57b3c7602cb136e0338507', diff --git a/releasenotes/notes/add-standard-attrs-to-address-groups-f5a8565fd2ed91c6.yaml b/releasenotes/notes/add-standard-attrs-to-address-groups-f5a8565fd2ed91c6.yaml new file mode 100644 index 00000000000..50d8faa5524 --- /dev/null +++ b/releasenotes/notes/add-standard-attrs-to-address-groups-f5a8565fd2ed91c6.yaml @@ -0,0 +1,10 @@ +--- +upgrade: + - | + Address group now has standard attributes. In the alembic migration, + the original ``description`` column of ``address_groups`` is dropped after + data migrated to the ``standardattributes`` table. The ``description`` + field is also removed from the address group object and DB model. This + change requires a restart of ``neutron-server`` service after the DB + migration otherwise users will get server errors when making calls to + address group APIs.