db: Replace use of backref

Per the SQLAlchemy docs [1]:

  The relationship.backref keyword should be considered legacy, and use
  of relationship.back_populates with explicit relationship() constructs
  should be preferred.

A number of the relationships defined here don't have foreign keys (long
live mordred?) so their conversion is slightly more difficult than would
otherwise be the case. A blog post is available to explain what's going
on [2] and might be worth a read. The learnings from that blog post do
have the benefit of allowing us to simplify some existing relationships
that had unnecessary arguments defined.

[1] https://docs.sqlalchemy.org/en/14/orm/backref.html
[2] https://that.guru/blog/sqlalchemy-relationships-without-foreign-keys/

Change-Id: I5a135b012dabdff7cf06204fc3c5438aaa0985c9
Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
This commit is contained in:
Stephen Finucane 2022-10-06 12:56:11 +01:00 committed by Stephen Finucane
parent 53012f1c55
commit 031eda5826
2 changed files with 160 additions and 50 deletions

View File

@ -147,11 +147,15 @@ class CellMapping(BASE):
transport_url = sa.Column(sa.Text()) transport_url = sa.Column(sa.Text())
database_connection = sa.Column(sa.Text()) database_connection = sa.Column(sa.Text())
disabled = sa.Column(sa.Boolean, default=False) disabled = sa.Column(sa.Boolean, default=False)
host_mapping = orm.relationship( host_mapping = orm.relationship(
'HostMapping', 'HostMapping',
backref=orm.backref('cell_mapping', uselist=False), back_populates='cell_mapping',
foreign_keys=id, )
primaryjoin='CellMapping.id == HostMapping.cell_id') instance_mapping = orm.relationship(
'InstanceMapping',
back_populates='cell_mapping',
)
class InstanceMapping(BASE): class InstanceMapping(BASE):
@ -178,11 +182,11 @@ class InstanceMapping(BASE):
# transition period first. # transition period first.
user_id = sa.Column(sa.String(255), nullable=True) user_id = sa.Column(sa.String(255), nullable=True)
queued_for_delete = sa.Column(sa.Boolean) queued_for_delete = sa.Column(sa.Boolean)
cell_mapping = orm.relationship( cell_mapping = orm.relationship(
'CellMapping', 'CellMapping',
backref=orm.backref('instance_mapping', uselist=False), back_populates='instance_mapping',
foreign_keys=cell_id, )
primaryjoin='InstanceMapping.cell_id == CellMapping.id')
class HostMapping(BASE): class HostMapping(BASE):
@ -198,6 +202,11 @@ class HostMapping(BASE):
sa.Integer, sa.ForeignKey('cell_mappings.id'), nullable=False) sa.Integer, sa.ForeignKey('cell_mappings.id'), nullable=False)
host = sa.Column(sa.String(255), nullable=False) host = sa.Column(sa.String(255), nullable=False)
cell_mapping = orm.relationship(
'CellMapping',
back_populates='host_mapping',
)
class RequestSpec(BASE): class RequestSpec(BASE):
"""Represents the information passed to the scheduler.""" """Represents the information passed to the scheduler."""
@ -235,6 +244,9 @@ class Flavors(BASE):
is_public = sa.Column(sa.Boolean, default=True) is_public = sa.Column(sa.Boolean, default=True)
description = sa.Column(sa.Text) description = sa.Column(sa.Text)
extra_specs = orm.relationship('FlavorExtraSpecs', back_populates='flavor')
projects = orm.relationship('FlavorProjects', back_populates='flavor')
class FlavorExtraSpecs(BASE): class FlavorExtraSpecs(BASE):
"""Represents additional specs as key/value pairs for a flavor""" """Represents additional specs as key/value pairs for a flavor"""
@ -251,10 +263,8 @@ class FlavorExtraSpecs(BASE):
value = sa.Column(sa.String(255)) value = sa.Column(sa.String(255))
flavor_id = sa.Column( flavor_id = sa.Column(
sa.Integer, sa.ForeignKey('flavors.id'), nullable=False) sa.Integer, sa.ForeignKey('flavors.id'), nullable=False)
flavor = orm.relationship(
Flavors, backref='extra_specs', flavor = orm.relationship(Flavors, back_populates='extra_specs')
foreign_keys=flavor_id,
primaryjoin='FlavorExtraSpecs.flavor_id == Flavors.id')
class FlavorProjects(BASE): class FlavorProjects(BASE):
@ -267,10 +277,8 @@ class FlavorProjects(BASE):
flavor_id = sa.Column( flavor_id = sa.Column(
sa.Integer, sa.ForeignKey('flavors.id'), nullable=False) sa.Integer, sa.ForeignKey('flavors.id'), nullable=False)
project_id = sa.Column(sa.String(255), nullable=False) project_id = sa.Column(sa.String(255), nullable=False)
flavor = orm.relationship(
Flavors, backref='projects', flavor = orm.relationship(Flavors, back_populates='projects')
foreign_keys=flavor_id,
primaryjoin='FlavorProjects.flavor_id == Flavors.id')
class BuildRequest(BASE): class BuildRequest(BASE):
@ -354,12 +362,9 @@ class InstanceGroup(BASE):
project_id = sa.Column(sa.String(255)) project_id = sa.Column(sa.String(255))
uuid = sa.Column(sa.String(36), nullable=False) uuid = sa.Column(sa.String(36), nullable=False)
name = sa.Column(sa.String(255)) name = sa.Column(sa.String(255))
_policies = orm.relationship(
InstanceGroupPolicy, _policies = orm.relationship(InstanceGroupPolicy)
primaryjoin='InstanceGroup.id == InstanceGroupPolicy.group_id') _members = orm.relationship(InstanceGroupMember)
_members = orm.relationship(
InstanceGroupMember,
primaryjoin='InstanceGroup.id == InstanceGroupMember.group_id')
@property @property
def policy(self): def policy(self):
@ -487,7 +492,5 @@ class Reservation(BASE):
resource = sa.Column(sa.String(255)) resource = sa.Column(sa.String(255))
delta = sa.Column(sa.Integer, nullable=False) delta = sa.Column(sa.Integer, nullable=False)
expire = sa.Column(sa.DateTime) expire = sa.Column(sa.DateTime)
usage = orm.relationship(
"QuotaUsage", usage = orm.relationship('QuotaUsage')
foreign_keys=usage_id,
primaryjoin='Reservation.usage_id == QuotaUsage.id')

View File

@ -161,8 +161,8 @@ class Service(BASE, NovaBase, models.SoftDeleteMixin):
version = sa.Column(sa.Integer, default=0) version = sa.Column(sa.Integer, default=0)
instance = orm.relationship( instance = orm.relationship(
"Instance", 'Instance',
backref='services', back_populates='services',
primaryjoin='and_(Service.host == Instance.host,' primaryjoin='and_(Service.host == Instance.host,'
'Service.binary == "nova-compute",' 'Service.binary == "nova-compute",'
'Instance.deleted == 0)', 'Instance.deleted == 0)',
@ -425,6 +425,96 @@ class Instance(BASE, NovaBase, models.SoftDeleteMixin):
hidden = sa.Column(sa.Boolean, default=False) hidden = sa.Column(sa.Boolean, default=False)
block_device_mapping = orm.relationship(
'BlockDeviceMapping',
back_populates='instance',
primaryjoin=(
'and_(BlockDeviceMapping.instance_uuid == Instance.uuid, '
'BlockDeviceMapping.deleted == 0)'
),
)
console_auth_tokens = orm.relationship(
'ConsoleAuthToken',
back_populates='instance',
foreign_keys='ConsoleAuthToken.instance_uuid',
primaryjoin=(
'and_(Instance.uuid == ConsoleAuthToken.instance_uuid,'
'Instance.deleted == 0)'
),
)
extra = orm.relationship(
'InstanceExtra',
back_populates='instance',
uselist=False,
)
info_cache = orm.relationship(
'InstanceInfoCache',
back_populates='instance',
uselist=False,
)
pci_devices = orm.relationship(
'PciDevice',
back_populates='instance',
foreign_keys='PciDevice.instance_uuid',
primaryjoin=(
'and_(Instance.uuid == PciDevice.instance_uuid,'
'PciDevice.deleted == 0)'
),
)
services = orm.relationship(
'Service',
back_populates='instance',
primaryjoin=(
'and_(Instance.host == Service.host,'
'Service.binary == "nova-compute",'
'Instance.deleted == 0)'
),
foreign_keys='Service.host',
)
security_groups = orm.relationship(
'SecurityGroup',
secondary='security_group_instance_association',
back_populates='instances',
primaryjoin=(
'and_('
'SecurityGroupInstanceAssociation.instance_uuid == Instance.uuid,'
# (anthony) the condition below shouldn't be necessary now that the
# association is being marked as deleted. However, removing this
# may cause existing deployments to choke, so I'm leaving it
'Instance.deleted == 0)'
),
secondaryjoin=(
'and_('
'SecurityGroup.id == SecurityGroupInstanceAssociation.security_group_id,' # noqa: E501
'SecurityGroupInstanceAssociation.deleted == 0,'
'SecurityGroup.deleted == 0)'
),
)
system_metadata = orm.relationship(
'InstanceSystemMetadata',
back_populates='instance',
)
tags = orm.relationship(
'Tag',
back_populates='instance',
primaryjoin=(
'and_(Instance.uuid == Tag.resource_id,Instance.deleted == 0)'
),
foreign_keys='Tag.resource_id',
)
# NOTE(stephenfin): https://github.com/sqlalchemy/sqlalchemy/discussions/8619
Instance.metadata = orm.relationship(
'InstanceMetadata',
back_populates='instance',
foreign_keys='InstanceMetadata.instance_uuid',
primaryjoin=(
'and_(Instance.uuid == InstanceMetadata.instance_uuid,'
'InstanceMetadata.deleted == 0)'
),
)
class InstanceInfoCache(BASE, NovaBase, models.SoftDeleteMixin): class InstanceInfoCache(BASE, NovaBase, models.SoftDeleteMixin):
"""Represents a cache of information about an instance """Represents a cache of information about an instance
@ -442,7 +532,7 @@ class InstanceInfoCache(BASE, NovaBase, models.SoftDeleteMixin):
instance_uuid = sa.Column(sa.String(36), sa.ForeignKey('instances.uuid'), instance_uuid = sa.Column(sa.String(36), sa.ForeignKey('instances.uuid'),
nullable=False) nullable=False)
instance = orm.relationship(Instance, instance = orm.relationship(Instance,
backref=orm.backref('info_cache', uselist=False), back_populates='info_cache',
foreign_keys=instance_uuid, foreign_keys=instance_uuid,
primaryjoin=instance_uuid == Instance.uuid) primaryjoin=instance_uuid == Instance.uuid)
@ -466,8 +556,7 @@ class InstanceExtra(BASE, NovaBase, models.SoftDeleteMixin):
# and can be removed in the future release. # and can be removed in the future release.
resources = orm.deferred(sa.Column(sa.Text)) resources = orm.deferred(sa.Column(sa.Text))
instance = orm.relationship(Instance, instance = orm.relationship(Instance,
backref=orm.backref('extra', back_populates='extra',
uselist=False),
foreign_keys=instance_uuid, foreign_keys=instance_uuid,
primaryjoin=instance_uuid == Instance.uuid) primaryjoin=instance_uuid == Instance.uuid)
@ -616,7 +705,7 @@ class BlockDeviceMapping(BASE, NovaBase, models.SoftDeleteMixin):
# transition period first. # transition period first.
uuid = sa.Column(sa.String(36)) uuid = sa.Column(sa.String(36))
instance = orm.relationship(Instance, instance = orm.relationship(Instance,
backref=orm.backref('block_device_mapping'), back_populates='block_device_mapping',
foreign_keys=instance_uuid, foreign_keys=instance_uuid,
primaryjoin='and_(BlockDeviceMapping.' primaryjoin='and_(BlockDeviceMapping.'
'instance_uuid==' 'instance_uuid=='
@ -664,10 +753,9 @@ class BlockDeviceMapping(BASE, NovaBase, models.SoftDeleteMixin):
encryption_format = sa.Column(sa.String(128)) encryption_format = sa.Column(sa.String(128))
encryption_options = sa.Column(sa.String(4096)) encryption_options = sa.Column(sa.String(4096))
# TODO(stephenfin): Remove once we drop the security_groups field from the # TODO(stephenfin): Remove once we drop the security_groups field from the
# Instance table. Until then, this is tied to the SecurityGroup table # Instance table. Until then, this is tied to the SecurityGroup table
class SecurityGroupInstanceAssociation(BASE, NovaBase, models.SoftDeleteMixin): class SecurityGroupInstanceAssociation(BASE, NovaBase, models.SoftDeleteMixin):
__tablename__ = 'security_group_instance_association' __tablename__ = 'security_group_instance_association'
__table_args__ = ( __table_args__ = (
@ -698,6 +786,7 @@ class SecurityGroup(BASE, NovaBase, models.SoftDeleteMixin):
project_id = sa.Column(sa.String(255)) project_id = sa.Column(sa.String(255))
instances = orm.relationship(Instance, instances = orm.relationship(Instance,
back_populates='security_groups',
secondary = "security_group_instance_association", secondary = "security_group_instance_association",
primaryjoin = 'and_(' primaryjoin = 'and_('
'SecurityGroup.id == ' 'SecurityGroup.id == '
@ -709,8 +798,17 @@ class SecurityGroup(BASE, NovaBase, models.SoftDeleteMixin):
# (anthony) the condition below shouldn't be necessary now that the # (anthony) the condition below shouldn't be necessary now that the
# association is being marked as deleted. However, removing this # association is being marked as deleted. However, removing this
# may cause existing deployments to choke, so I'm leaving it # may cause existing deployments to choke, so I'm leaving it
'Instance.deleted == 0)', 'Instance.deleted == 0)')
backref='security_groups')
rules = orm.relationship(
'SecurityGroupIngressRule',
primaryjoin=(
'and_('
'SecurityGroupIngressRule.parent_group_id == SecurityGroup.id,'
'SecurityGroupIngressRule.deleted == 0)'
),
back_populates='parent_group',
)
# TODO(stephenfin): Remove once we drop the security_groups field from the # TODO(stephenfin): Remove once we drop the security_groups field from the
@ -723,8 +821,9 @@ class SecurityGroupIngressRule(BASE, NovaBase, models.SoftDeleteMixin):
parent_group_id = sa.Column( parent_group_id = sa.Column(
sa.Integer, sa.ForeignKey('security_groups.id')) sa.Integer, sa.ForeignKey('security_groups.id'))
parent_group = orm.relationship("SecurityGroup", backref="rules", parent_group = orm.relationship("SecurityGroup",
foreign_keys=parent_group_id, back_populates="rules",
foreign_keys=[parent_group_id],
primaryjoin='and_(' primaryjoin='and_('
'SecurityGroupIngressRule.parent_group_id == SecurityGroup.id,' 'SecurityGroupIngressRule.parent_group_id == SecurityGroup.id,'
'SecurityGroupIngressRule.deleted == 0)') 'SecurityGroupIngressRule.deleted == 0)')
@ -738,7 +837,7 @@ class SecurityGroupIngressRule(BASE, NovaBase, models.SoftDeleteMixin):
# granting access for. # granting access for.
group_id = sa.Column(sa.Integer, sa.ForeignKey('security_groups.id')) group_id = sa.Column(sa.Integer, sa.ForeignKey('security_groups.id'))
grantee_group = orm.relationship("SecurityGroup", grantee_group = orm.relationship("SecurityGroup",
foreign_keys=group_id, foreign_keys=[group_id],
primaryjoin='and_(' primaryjoin='and_('
'SecurityGroupIngressRule.group_id == SecurityGroup.id,' 'SecurityGroupIngressRule.group_id == SecurityGroup.id,'
'SecurityGroupIngressRule.deleted == 0)') 'SecurityGroupIngressRule.deleted == 0)')
@ -824,12 +923,16 @@ class InstanceMetadata(BASE, NovaBase, models.SoftDeleteMixin):
key = sa.Column(sa.String(255)) key = sa.Column(sa.String(255))
value = sa.Column(sa.String(255)) value = sa.Column(sa.String(255))
instance_uuid = sa.Column(sa.String(36), sa.ForeignKey('instances.uuid')) instance_uuid = sa.Column(sa.String(36), sa.ForeignKey('instances.uuid'))
instance = orm.relationship(Instance, backref="metadata",
instance = orm.relationship(
Instance,
back_populates="metadata",
foreign_keys=instance_uuid, foreign_keys=instance_uuid,
primaryjoin='and_(' primaryjoin=(
'InstanceMetadata.instance_uuid == ' 'and_(InstanceMetadata.instance_uuid == Instance.uuid,'
'Instance.uuid,' 'InstanceMetadata.deleted == 0)'
'InstanceMetadata.deleted == 0)') ),
)
class InstanceSystemMetadata(BASE, NovaBase, models.SoftDeleteMixin): class InstanceSystemMetadata(BASE, NovaBase, models.SoftDeleteMixin):
@ -845,8 +948,11 @@ class InstanceSystemMetadata(BASE, NovaBase, models.SoftDeleteMixin):
sa.ForeignKey('instances.uuid'), sa.ForeignKey('instances.uuid'),
nullable=False) nullable=False)
instance = orm.relationship(Instance, backref="system_metadata", instance = orm.relationship(
foreign_keys=instance_uuid) Instance,
back_populates='system_metadata',
foreign_keys=instance_uuid,
)
class VolumeUsage(BASE, NovaBase, models.SoftDeleteMixin): class VolumeUsage(BASE, NovaBase, models.SoftDeleteMixin):
@ -1022,7 +1128,8 @@ class PciDevice(BASE, NovaBase, models.SoftDeleteMixin):
numa_node = sa.Column(sa.Integer, nullable=True) numa_node = sa.Column(sa.Integer, nullable=True)
parent_addr = sa.Column(sa.String(12), nullable=True) parent_addr = sa.Column(sa.String(12), nullable=True)
instance = orm.relationship(Instance, backref="pci_devices", instance = orm.relationship(Instance,
back_populates="pci_devices",
foreign_keys=instance_uuid, foreign_keys=instance_uuid,
primaryjoin='and_(' primaryjoin='and_('
'PciDevice.instance_uuid == Instance.uuid,' 'PciDevice.instance_uuid == Instance.uuid,'
@ -1040,11 +1147,11 @@ class Tag(BASE, models.ModelBase):
tag = sa.Column(sa.Unicode(80), primary_key=True, nullable=False) tag = sa.Column(sa.Unicode(80), primary_key=True, nullable=False)
instance = orm.relationship( instance = orm.relationship(
"Instance", 'Instance',
backref='tags', back_populates='tags',
foreign_keys=resource_id,
primaryjoin='and_(Tag.resource_id == Instance.uuid,' primaryjoin='and_(Tag.resource_id == Instance.uuid,'
'Instance.deleted == 0)', 'Instance.deleted == 0)',
foreign_keys=resource_id
) )
@ -1074,7 +1181,7 @@ class ConsoleAuthToken(BASE, NovaBase):
instance = orm.relationship( instance = orm.relationship(
"Instance", "Instance",
backref='console_auth_tokens', back_populates='console_auth_tokens',
primaryjoin='and_(ConsoleAuthToken.instance_uuid == Instance.uuid,' primaryjoin='and_(ConsoleAuthToken.instance_uuid == Instance.uuid,'
'Instance.deleted == 0)', 'Instance.deleted == 0)',
foreign_keys=instance_uuid foreign_keys=instance_uuid