From df9411dc11a579d5ed2d86f340cf97ae841aaec4 Mon Sep 17 00:00:00 2001 From: Dariusz Smigiel Date: Thu, 30 Jun 2016 02:04:57 +0000 Subject: [PATCH] Rename DB columns: tenant -> project All occurences of ``tenant_id`` across the database are renamed to ``project_id``. Both options are equally valid, but ``project_id`` is preferred. To inform external users about the change, HasTenant class was deprecated. UpgradeImpact Partially-Implements: blueprint keystone-v3 Change-Id: I87a8ef342ccea004731ba0192b23a8e79bc382dc --- neutron/db/address_scope_db.py | 2 +- neutron/db/l3_db.py | 4 +- neutron/db/l3_hamode_db.py | 4 +- neutron/db/metering/metering_db.py | 4 +- .../alembic_migrations/versions/CONTRACT_HEAD | 2 +- .../7d9d8eeec6ad_rename_tenant_to_project.py | 161 ++++++++++++++++++ neutron/db/model_base.py | 60 ++++++- neutron/db/qos/models.py | 2 +- neutron/db/quota/models.py | 11 +- neutron/db/rbac_db_models.py | 2 +- neutron/db/securitygroups_db.py | 8 +- neutron/services/auto_allocate/models.py | 5 +- neutron/services/trunk/models.py | 2 +- .../tests/unit/db/test_db_base_plugin_v2.py | 119 ++++++++----- neutron/tests/unit/quota/__init__.py | 4 +- ...me-tenant-to-project-b19a4068f8625969.yaml | 5 + 16 files changed, 324 insertions(+), 71 deletions(-) create mode 100644 neutron/db/migration/alembic_migrations/versions/newton/contract/7d9d8eeec6ad_rename_tenant_to_project.py create mode 100644 releasenotes/notes/rename-tenant-to-project-b19a4068f8625969.yaml diff --git a/neutron/db/address_scope_db.py b/neutron/db/address_scope_db.py index 21645b92e17..9e2afdbf0c9 100644 --- a/neutron/db/address_scope_db.py +++ b/neutron/db/address_scope_db.py @@ -25,7 +25,7 @@ from neutron.extensions import address_scope as ext_address_scope from neutron.objects import subnetpool as subnetpool_obj -class AddressScope(model_base.BASEV2, model_base.HasId, model_base.HasTenant): +class AddressScope(model_base.BASEV2, model_base.HasId, model_base.HasProject): """Represents a neutron address scope.""" __tablename__ = "address_scopes" diff --git a/neutron/db/l3_db.py b/neutron/db/l3_db.py index 3e7b2935e16..1724e5a0f7a 100644 --- a/neutron/db/l3_db.py +++ b/neutron/db/l3_db.py @@ -88,7 +88,7 @@ class RouterPort(model_base.BASEV2): class Router(model_base.HasStandardAttributes, model_base.BASEV2, - model_base.HasId, model_base.HasTenant): + model_base.HasId, model_base.HasProject): """Represents a v2 neutron router.""" name = sa.Column(sa.String(attributes.NAME_MAX_LEN)) @@ -108,7 +108,7 @@ class Router(model_base.HasStandardAttributes, model_base.BASEV2, class FloatingIP(model_base.HasStandardAttributes, model_base.BASEV2, - model_base.HasId, model_base.HasTenant): + model_base.HasId, model_base.HasProject): """Represents a floating IP address. This IP address may or may not be allocated to a tenant, and may or diff --git a/neutron/db/l3_hamode_db.py b/neutron/db/l3_hamode_db.py index dd74d6f002f..411249a0156 100644 --- a/neutron/db/l3_hamode_db.py +++ b/neutron/db/l3_hamode_db.py @@ -124,7 +124,7 @@ class L3HARouterAgentPortBinding(model_base.BASEV2): server_default=n_const.HA_ROUTER_STATE_STANDBY) -class L3HARouterNetwork(model_base.BASEV2): +class L3HARouterNetwork(model_base.BASEV2, model_base.HasProjectPrimaryKey): """Host HA network for a tenant. One HA Network is used per tenant, all HA router ports are created @@ -133,8 +133,6 @@ class L3HARouterNetwork(model_base.BASEV2): __tablename__ = 'ha_router_networks' - tenant_id = sa.Column(sa.String(attributes.TENANT_ID_MAX_LEN), - primary_key=True, nullable=False) network_id = sa.Column(sa.String(36), sa.ForeignKey('networks.id', ondelete="CASCADE"), nullable=False, primary_key=True) diff --git a/neutron/db/metering/metering_db.py b/neutron/db/metering/metering_db.py index ebed45b40c5..605043a8772 100644 --- a/neutron/db/metering/metering_db.py +++ b/neutron/db/metering/metering_db.py @@ -38,7 +38,9 @@ class MeteringLabelRule(model_base.BASEV2, model_base.HasId): excluded = sa.Column(sa.Boolean, default=False, server_default=sql.false()) -class MeteringLabel(model_base.BASEV2, model_base.HasId, model_base.HasTenant): +class MeteringLabel(model_base.BASEV2, + model_base.HasId, + model_base.HasProject): name = sa.Column(sa.String(attr.NAME_MAX_LEN)) description = sa.Column(sa.String(attr.LONG_DESCRIPTION_MAX_LEN)) rules = orm.relationship(MeteringLabelRule, backref="label", diff --git a/neutron/db/migration/alembic_migrations/versions/CONTRACT_HEAD b/neutron/db/migration/alembic_migrations/versions/CONTRACT_HEAD index db10d331ed5..6f734b7ec8d 100644 --- a/neutron/db/migration/alembic_migrations/versions/CONTRACT_HEAD +++ b/neutron/db/migration/alembic_migrations/versions/CONTRACT_HEAD @@ -1 +1 @@ -a84ccf28f06a +7d9d8eeec6ad diff --git a/neutron/db/migration/alembic_migrations/versions/newton/contract/7d9d8eeec6ad_rename_tenant_to_project.py b/neutron/db/migration/alembic_migrations/versions/newton/contract/7d9d8eeec6ad_rename_tenant_to_project.py new file mode 100644 index 00000000000..298fa25be99 --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/newton/contract/7d9d8eeec6ad_rename_tenant_to_project.py @@ -0,0 +1,161 @@ +# Copyright 2016 Intel Corporation +# +# 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. +# + +"""rename tenant to project + +Revision ID: 7d9d8eeec6ad +Create Date: 2016-06-29 19:42:17.862721 + +""" + +# revision identifiers, used by Alembic. +revision = '7d9d8eeec6ad' +down_revision = 'a84ccf28f06a' +depends_on = ('5abc0278ca73',) + +from alembic import op +import sqlalchemy as sa + + +_INSPECTOR = None + + +def get_inspector(): + """Reuse inspector""" + + global _INSPECTOR + + if _INSPECTOR: + return _INSPECTOR + + else: + bind = op.get_bind() + _INSPECTOR = sa.engine.reflection.Inspector.from_engine(bind) + + return _INSPECTOR + + +def get_tables(): + """ + Returns hardcoded list of tables which have ``tenant_id`` column. + + DB head can be changed. To prevent possible problems, when models will be + updated, return hardcoded list of tables, up-to-date for this day. + + Output retrieved by using: + + >>> metadata = head.get_metadata() + >>> all_tables = metadata.sorted_tables + >>> tenant_tables = [] + >>> for table in all_tables: + ... for column in table.columns: + ... if column.name == 'tenant_id': + ... tenant_tables.append((table, column)) + + """ + + tables = [ + 'address_scopes', + 'floatingips', + 'meteringlabels', + 'networkrbacs', + 'networks', + 'ports', + 'qos_policies', + 'qospolicyrbacs', + 'quotas', + 'reservations', + 'routers', + 'securitygrouprules', + 'securitygroups', + 'subnetpools', + 'subnets', + 'trunks', + 'auto_allocated_topologies', + 'default_security_group', + 'ha_router_networks', + 'quotausages', + ] + + return tables + + +def get_columns(table): + """Returns list of columns for given table.""" + inspector = get_inspector() + return inspector.get_columns(table) + + +def get_data(): + """Returns combined list of tuples: [(table, column)]. + + List is built, based on retrieved tables, where column with name + ``tenant_id`` exists. + """ + + output = [] + tables = get_tables() + for table in tables: + columns = get_columns(table) + + for column in columns: + if column['name'] == 'tenant_id': + output.append((table, column)) + + return output + + +def alter_column(table, column): + old_name = 'tenant_id' + new_name = 'project_id' + + op.alter_column( + table_name=table, + column_name=old_name, + new_column_name=new_name, + existing_type=column['type'], + existing_nullable=column['nullable'] + ) + + +def recreate_index(index, table_name): + old_name = index['name'] + new_name = old_name.replace('tenant', 'project') + + op.drop_index(op.f(old_name), table_name) + op.create_index(new_name, table_name, ['project_id']) + + +def upgrade(): + inspector = get_inspector() + + data = get_data() + for table, column in data: + alter_column(table, column) + + indexes = inspector.get_indexes(table) + for index in indexes: + if 'tenant_id' in index['name']: + recreate_index(index, table) + + +def contract_creation_exceptions(): + """Special migration for the blueprint to support Keystone V3. + We drop all tenant_id columns and create project_id columns instead. + """ + return { + sa.Column: ['.'.join([table, 'project_id']) for table in get_tables()], + sa.Index: get_tables() + } diff --git a/neutron/db/model_base.py b/neutron/db/model_base.py index 02df3b13277..fd19cd15099 100644 --- a/neutron/db/model_base.py +++ b/neutron/db/model_base.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import debtcollector from oslo_db.sqlalchemy import models from oslo_utils import uuidutils import sqlalchemy as sa @@ -23,16 +24,63 @@ from sqlalchemy import orm from neutron.api.v2 import attributes as attr -class HasTenant(object): - """Tenant mixin, add to subclasses that have a tenant.""" +class HasProject(object): + """Project mixin, add to subclasses that have a user.""" - # NOTE(jkoelker) tenant_id is just a free form string ;( - tenant_id = sa.Column(sa.String(attr.TENANT_ID_MAX_LEN), index=True) + # NOTE(jkoelker) project_id is just a free form string ;( + project_id = sa.Column(sa.String(attr.TENANT_ID_MAX_LEN), index=True) + + def __init__(self, *args, **kwargs): + # NOTE(dasm): debtcollector requires init in class + super(HasProject, self).__init__(*args, **kwargs) + + def get_tenant_id(self): + return self.project_id + + def set_tenant_id(self, value): + self.project_id = value + + @declarative.declared_attr + @debtcollector.moves.moved_property('project_id') + def tenant_id(cls): + return orm.synonym( + 'project_id', + descriptor=property(cls.get_tenant_id, cls.set_tenant_id)) + + +HasTenant = debtcollector.moves.moved_class(HasProject, "HasTenant", __name__) + + +class HasProjectNoIndex(HasProject): + """Project mixin, add to subclasses that have a user.""" + + # NOTE(jkoelker) project_id is just a free form string ;( + project_id = sa.Column(sa.String(attr.TENANT_ID_MAX_LEN)) + + +class HasProjectPrimaryKeyIndex(HasProject): + """Project mixin, add to subclasses that have a user.""" + + # NOTE(jkoelker) project_id is just a free form string ;( + project_id = sa.Column(sa.String(attr.TENANT_ID_MAX_LEN), nullable=False, + primary_key=True, index=True) + + +class HasProjectPrimaryKey(HasProject): + """Project mixin, add to subclasses that have a user.""" + + # NOTE(jkoelker) project_id is just a free form string ;( + project_id = sa.Column(sa.String(attr.TENANT_ID_MAX_LEN), nullable=False, + primary_key=True) class HasId(object): """id mixin, add to subclasses that have an id.""" + def __init__(self, *args, **kwargs): + # NOTE(dasm): debtcollector requires init in class + super(HasId, self).__init__(*args, **kwargs) + id = sa.Column(sa.String(36), primary_key=True, default=uuidutils.generate_uuid) @@ -41,6 +89,10 @@ class HasId(object): class HasStatusDescription(object): """Status with description mixin.""" + def __init__(self, *args, **kwargs): + # NOTE(dasm): debtcollector requires init in class + super(HasStatusDescription, self).__init__(*args, **kwargs) + status = sa.Column(sa.String(16), nullable=False) status_description = sa.Column(sa.String(attr.DESCRIPTION_MAX_LEN)) diff --git a/neutron/db/qos/models.py b/neutron/db/qos/models.py index 1ec3351ad69..137f13ddd82 100644 --- a/neutron/db/qos/models.py +++ b/neutron/db/qos/models.py @@ -21,7 +21,7 @@ from neutron.db import models_v2 from neutron.db import rbac_db_models -class QosPolicy(model_base.BASEV2, model_base.HasId, model_base.HasTenant): +class QosPolicy(model_base.BASEV2, model_base.HasId, model_base.HasProject): __tablename__ = 'qos_policies' name = sa.Column(sa.String(attrs.NAME_MAX_LEN)) description = sa.Column(sa.String(attrs.DESCRIPTION_MAX_LEN)) diff --git a/neutron/db/quota/models.py b/neutron/db/quota/models.py index db7965809fb..9562cec6951 100644 --- a/neutron/db/quota/models.py +++ b/neutron/db/quota/models.py @@ -16,7 +16,6 @@ import sqlalchemy as sa from sqlalchemy import orm from sqlalchemy import sql -from neutron.api.v2 import attributes as attr from neutron.db import model_base @@ -31,8 +30,8 @@ class ResourceDelta(model_base.BASEV2): amount = sa.Column(sa.Integer) -class Reservation(model_base.BASEV2, model_base.HasId): - tenant_id = sa.Column(sa.String(attr.TENANT_ID_MAX_LEN)) +class Reservation(model_base.BASEV2, model_base.HasId, + model_base.HasProjectNoIndex): expiration = sa.Column(sa.DateTime()) resource_deltas = orm.relationship(ResourceDelta, backref='reservation', @@ -40,7 +39,7 @@ class Reservation(model_base.BASEV2, model_base.HasId): cascade='all, delete-orphan') -class Quota(model_base.BASEV2, model_base.HasId, model_base.HasTenant): +class Quota(model_base.BASEV2, model_base.HasId, model_base.HasProject): """Represent a single quota override for a tenant. If there is no row for a given tenant id and resource, then the @@ -50,13 +49,11 @@ class Quota(model_base.BASEV2, model_base.HasId, model_base.HasTenant): limit = sa.Column(sa.Integer) -class QuotaUsage(model_base.BASEV2): +class QuotaUsage(model_base.BASEV2, model_base.HasProjectPrimaryKeyIndex): """Represents the current usage for a given resource.""" resource = sa.Column(sa.String(255), nullable=False, primary_key=True, index=True) - tenant_id = sa.Column(sa.String(attr.TENANT_ID_MAX_LEN), nullable=False, - primary_key=True, index=True) dirty = sa.Column(sa.Boolean, nullable=False, server_default=sql.false()) in_use = sa.Column(sa.Integer, nullable=False, diff --git a/neutron/db/rbac_db_models.py b/neutron/db/rbac_db_models.py index f4d8a9c45ba..09c9dd95fdb 100644 --- a/neutron/db/rbac_db_models.py +++ b/neutron/db/rbac_db_models.py @@ -35,7 +35,7 @@ class InvalidActionForType(n_exc.InvalidInput): "'%(object_type)s'. Valid actions: %(valid_actions)s") -class RBACColumns(model_base.HasId, model_base.HasTenant): +class RBACColumns(model_base.HasId, model_base.HasProject): """Mixin that object-specific RBAC tables should inherit. All RBAC tables should inherit directly from this one because diff --git a/neutron/db/securitygroups_db.py b/neutron/db/securitygroups_db.py index 1abb655b4a5..9f30eca279e 100644 --- a/neutron/db/securitygroups_db.py +++ b/neutron/db/securitygroups_db.py @@ -41,17 +41,15 @@ LOG = logging.getLogger(__name__) class SecurityGroup(model_base.HasStandardAttributes, model_base.BASEV2, - model_base.HasId, model_base.HasTenant): + model_base.HasId, model_base.HasProject): """Represents a v2 neutron security group.""" name = sa.Column(sa.String(attributes.NAME_MAX_LEN)) -class DefaultSecurityGroup(model_base.BASEV2): +class DefaultSecurityGroup(model_base.BASEV2, model_base.HasProjectPrimaryKey): __tablename__ = 'default_security_group' - tenant_id = sa.Column(sa.String(attributes.TENANT_ID_MAX_LEN), - primary_key=True, nullable=False) security_group_id = sa.Column(sa.String(36), sa.ForeignKey("securitygroups.id", ondelete="CASCADE"), @@ -83,7 +81,7 @@ class SecurityGroupPortBinding(model_base.BASEV2): class SecurityGroupRule(model_base.HasStandardAttributes, model_base.BASEV2, - model_base.HasId, model_base.HasTenant): + model_base.HasId, model_base.HasProject): """Represents a v2 neutron security group rule.""" security_group_id = sa.Column(sa.String(36), diff --git a/neutron/services/auto_allocate/models.py b/neutron/services/auto_allocate/models.py index 27630ab0bf3..9163d04ef7e 100644 --- a/neutron/services/auto_allocate/models.py +++ b/neutron/services/auto_allocate/models.py @@ -18,12 +18,11 @@ import sqlalchemy as sa from neutron.db import model_base -class AutoAllocatedTopology(model_base.BASEV2): +class AutoAllocatedTopology(model_base.BASEV2, + model_base.HasProjectPrimaryKey): __tablename__ = 'auto_allocated_topologies' - tenant_id = sa.Column(sa.String(255), primary_key=True) - network_id = sa.Column(sa.String(36), sa.ForeignKey('networks.id', ondelete='CASCADE'), diff --git a/neutron/services/trunk/models.py b/neutron/services/trunk/models.py index 4e4e3ac43ea..6f8bdaaa905 100644 --- a/neutron/services/trunk/models.py +++ b/neutron/services/trunk/models.py @@ -23,7 +23,7 @@ from neutron.services.trunk import constants class Trunk(model_base.HasStandardAttributes, model_base.BASEV2, - model_base.HasId, model_base.HasTenant): + model_base.HasId, model_base.HasProject): admin_state_up = sa.Column( sa.Boolean(), nullable=False, server_default=sql.true()) diff --git a/neutron/tests/unit/db/test_db_base_plugin_v2.py b/neutron/tests/unit/db/test_db_base_plugin_v2.py index bfed743c4f3..9497ef34f4a 100644 --- a/neutron/tests/unit/db/test_db_base_plugin_v2.py +++ b/neutron/tests/unit/db/test_db_base_plugin_v2.py @@ -6007,7 +6007,7 @@ class TestSubnetPoolsV2(NeutronDbPluginV2TestCase): self.assertEqual(400, res.status_int) -class DbModelTestCase(testlib_api.SqlTestCase): +class DbModelMixin(object): """DB model tests.""" def test_repr(self): """testing the string representation of 'model' classes.""" @@ -6016,7 +6016,7 @@ class DbModelTestCase(testlib_api.SqlTestCase): actual_repr_output = repr(network) exp_start_with = "