Change to use selectin for DB load strategy
During a mailing list discussion on some OOM issues neutron has been seeing [0], Mike Bayer recommended we should change from using subquery to selectin DB load strategy. A full description of this strategy can be found here [1], but in short: - “subquery” loading incurs additional performance / complexity issues when used on a many-levels-deep eager load, as subqueries will be nested repeatedly. - "The subqueryload() eager loader is mostly legacy at this point, superseded by selectinload() - "The only scenario in which selectin eager loading is not feasible is when the model is using composite primary keys, and the backend database does not support tuples with IN, which currently includes SQL Server." So that does not apply to us. The plan agreed to at the neutron drivers meeting [2] was to make this change early in the cycle so we would be able to see if there were any issues through the D cycle. Added hacking checks so new code using subquery loads is not added back. [0] https://lists.openstack.org/archives/list/openstack-discuss@lists.openstack.org/thread/EHLQQXNG3NLLZYPDGG2ES3DINIJ7YT3N/ [1] https://docs.sqlalchemy.org/en/20/orm/queryguide/relationships.html#selectin-eager-loading [2] https://meetings.opendev.org/meetings/neutron_drivers/2024/neutron_drivers.2024-05-31-14.00.log.html#l-67 Closes-bug: #2067770 Depends-on: https://review.opendev.org/c/openstack/neutron-lib/+/920936 Change-Id: I6e40a15284da392a3d48d45205a7a5770c14c297
This commit is contained in:
parent
abe8110f53
commit
05fcfef6ce
@ -42,5 +42,5 @@ class ExtraDhcpOpt(model_base.BASEV2, model_base.HasId):
|
|||||||
# eagerly load extra_dhcp_opts bindings
|
# eagerly load extra_dhcp_opts bindings
|
||||||
ports = orm.relationship(
|
ports = orm.relationship(
|
||||||
models_v2.Port, load_on_pending=True,
|
models_v2.Port, load_on_pending=True,
|
||||||
backref=orm.backref("dhcp_opts", lazy='subquery', cascade='delete'))
|
backref=orm.backref("dhcp_opts", lazy='selectin', cascade='delete'))
|
||||||
revises_on_change = ('ports', )
|
revises_on_change = ('ports', )
|
||||||
|
@ -42,7 +42,7 @@ class AddressGroup(standard_attr.HasStandardAttributes,
|
|||||||
addresses = orm.relationship(AddressAssociation,
|
addresses = orm.relationship(AddressAssociation,
|
||||||
backref=orm.backref('address_groups',
|
backref=orm.backref('address_groups',
|
||||||
load_on_pending=True),
|
load_on_pending=True),
|
||||||
lazy='subquery',
|
lazy='selectin',
|
||||||
cascade='all, delete-orphan')
|
cascade='all, delete-orphan')
|
||||||
rbac_entries = sa.orm.relationship(rbac_db_models.AddressGroupRBAC,
|
rbac_entries = sa.orm.relationship(rbac_db_models.AddressGroupRBAC,
|
||||||
backref='address_groups',
|
backref='address_groups',
|
||||||
|
@ -27,5 +27,5 @@ class AllowedAddressPair(model_base.BASEV2):
|
|||||||
port = orm.relationship(
|
port = orm.relationship(
|
||||||
models_v2.Port, load_on_pending=True,
|
models_v2.Port, load_on_pending=True,
|
||||||
backref=orm.backref("allowed_address_pairs",
|
backref=orm.backref("allowed_address_pairs",
|
||||||
lazy="subquery", cascade="delete"))
|
lazy="selectin", cascade="delete"))
|
||||||
revises_on_change = ('port', )
|
revises_on_change = ('port', )
|
||||||
|
@ -39,7 +39,7 @@ class ConntrackHelper(model_base.BASEV2, model_base.HasId):
|
|||||||
|
|
||||||
router = orm.relationship(l3.Router, load_on_pending=True,
|
router = orm.relationship(l3.Router, load_on_pending=True,
|
||||||
backref=orm.backref("conntrack_helpers",
|
backref=orm.backref("conntrack_helpers",
|
||||||
lazy='subquery',
|
lazy='selectin',
|
||||||
uselist=True,
|
uselist=True,
|
||||||
cascade='delete'))
|
cascade='delete'))
|
||||||
revises_on_change = ('router', )
|
revises_on_change = ('router', )
|
||||||
|
@ -41,7 +41,7 @@ class FlavorServiceProfileBinding(model_base.BASEV2):
|
|||||||
flavor = orm.relationship(Flavor,
|
flavor = orm.relationship(Flavor,
|
||||||
backref=orm.backref(
|
backref=orm.backref(
|
||||||
"service_profiles",
|
"service_profiles",
|
||||||
lazy='subquery',
|
lazy='selectin',
|
||||||
cascade="all, delete-orphan"))
|
cascade="all, delete-orphan"))
|
||||||
service_profile_id = sa.Column(sa.String(36),
|
service_profile_id = sa.Column(sa.String(36),
|
||||||
sa.ForeignKey("serviceprofiles.id",
|
sa.ForeignKey("serviceprofiles.id",
|
||||||
|
@ -58,9 +58,9 @@ class Router(standard_attr.HasStandardAttributes, model_base.BASEV2,
|
|||||||
attached_ports = orm.relationship(
|
attached_ports = orm.relationship(
|
||||||
RouterPort,
|
RouterPort,
|
||||||
backref=orm.backref('router', load_on_pending=True),
|
backref=orm.backref('router', load_on_pending=True),
|
||||||
lazy='subquery')
|
lazy='selectin')
|
||||||
l3_agents = orm.relationship(
|
l3_agents = orm.relationship(
|
||||||
'Agent', lazy='subquery', viewonly=True,
|
'Agent', lazy='selectin', viewonly=True,
|
||||||
secondary=rb_model.RouterL3AgentBinding.__table__)
|
secondary=rb_model.RouterL3AgentBinding.__table__)
|
||||||
api_collections = [l3_apidef.ROUTERS]
|
api_collections = [l3_apidef.ROUTERS]
|
||||||
collection_resource_map = {l3_apidef.ROUTERS: l3_apidef.ROUTER}
|
collection_resource_map = {l3_apidef.ROUTERS: l3_apidef.ROUTER}
|
||||||
@ -120,6 +120,6 @@ class RouterRoute(model_base.BASEV2, models_v2.Route):
|
|||||||
|
|
||||||
router = orm.relationship(Router, load_on_pending=True,
|
router = orm.relationship(Router, load_on_pending=True,
|
||||||
backref=orm.backref("route_list",
|
backref=orm.backref("route_list",
|
||||||
lazy='subquery',
|
lazy='selectin',
|
||||||
cascade='delete'))
|
cascade='delete'))
|
||||||
revises_on_change = ('router', )
|
revises_on_change = ('router', )
|
||||||
|
@ -38,11 +38,11 @@ class MeteringLabel(model_base.BASEV2,
|
|||||||
name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
|
name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
|
||||||
description = sa.Column(sa.String(db_const.LONG_DESCRIPTION_FIELD_SIZE))
|
description = sa.Column(sa.String(db_const.LONG_DESCRIPTION_FIELD_SIZE))
|
||||||
rules = orm.relationship(MeteringLabelRule, backref="label",
|
rules = orm.relationship(MeteringLabelRule, backref="label",
|
||||||
cascade="delete", lazy="subquery")
|
cascade="delete", lazy="selectin")
|
||||||
routers = orm.relationship(
|
routers = orm.relationship(
|
||||||
l3_models.Router,
|
l3_models.Router,
|
||||||
primaryjoin="MeteringLabel.project_id==Router.project_id",
|
primaryjoin="MeteringLabel.project_id==Router.project_id",
|
||||||
foreign_keys='MeteringLabel.project_id',
|
foreign_keys='MeteringLabel.project_id',
|
||||||
lazy='subquery',
|
lazy='selectin',
|
||||||
uselist=True)
|
uselist=True)
|
||||||
shared = sa.Column(sa.Boolean, default=False, server_default=sql.false())
|
shared = sa.Column(sa.Boolean, default=False, server_default=sql.false())
|
||||||
|
@ -63,6 +63,6 @@ class RouterNDPProxyState(model_base.BASEV2):
|
|||||||
router = orm.relationship(
|
router = orm.relationship(
|
||||||
l3.Router, load_on_pending=True,
|
l3.Router, load_on_pending=True,
|
||||||
backref=orm.backref("ndp_proxy_state",
|
backref=orm.backref("ndp_proxy_state",
|
||||||
lazy='subquery', uselist=False,
|
lazy='selectin', uselist=False,
|
||||||
cascade='delete')
|
cascade='delete')
|
||||||
)
|
)
|
||||||
|
@ -58,13 +58,13 @@ class PortForwarding(standard_attr.HasStandardAttributes,
|
|||||||
models_v2.Port, load_on_pending=True,
|
models_v2.Port, load_on_pending=True,
|
||||||
foreign_keys=internal_neutron_port_id,
|
foreign_keys=internal_neutron_port_id,
|
||||||
backref=orm.backref("port_forwardings",
|
backref=orm.backref("port_forwardings",
|
||||||
lazy='subquery', uselist=True,
|
lazy='selectin', uselist=True,
|
||||||
cascade='delete')
|
cascade='delete')
|
||||||
)
|
)
|
||||||
floating_ip = orm.relationship(
|
floating_ip = orm.relationship(
|
||||||
l3.FloatingIP, load_on_pending=True,
|
l3.FloatingIP, load_on_pending=True,
|
||||||
backref=orm.backref("port_forwardings",
|
backref=orm.backref("port_forwardings",
|
||||||
lazy='subquery', uselist=True,
|
lazy='selectin', uselist=True,
|
||||||
cascade='delete')
|
cascade='delete')
|
||||||
)
|
)
|
||||||
revises_on_change = ('floating_ip', 'port',)
|
revises_on_change = ('floating_ip', 'port',)
|
||||||
|
@ -49,7 +49,7 @@ class NetworkSegment(standard_attr.HasStandardAttributes,
|
|||||||
nullable=True)
|
nullable=True)
|
||||||
network = orm.relationship(models_v2.Network,
|
network = orm.relationship(models_v2.Network,
|
||||||
backref=orm.backref("segments",
|
backref=orm.backref("segments",
|
||||||
lazy='subquery',
|
lazy='selectin',
|
||||||
cascade='delete'))
|
cascade='delete'))
|
||||||
api_collections = [segment.COLLECTION_NAME]
|
api_collections = [segment.COLLECTION_NAME]
|
||||||
|
|
||||||
@ -81,6 +81,6 @@ class SegmentHostMapping(model_base.BASEV2):
|
|||||||
network_segment = orm.relationship(
|
network_segment = orm.relationship(
|
||||||
NetworkSegment, load_on_pending=True,
|
NetworkSegment, load_on_pending=True,
|
||||||
backref=orm.backref("segment_host_mapping",
|
backref=orm.backref("segment_host_mapping",
|
||||||
lazy='subquery',
|
lazy='selectin',
|
||||||
cascade='delete'))
|
cascade='delete'))
|
||||||
revises_on_change = ('network_segment', )
|
revises_on_change = ('network_segment', )
|
||||||
|
@ -33,7 +33,7 @@ class SubnetServiceType(model_base.BASEV2):
|
|||||||
length=db_const.DEVICE_OWNER_FIELD_SIZE))
|
length=db_const.DEVICE_OWNER_FIELD_SIZE))
|
||||||
subnet = orm.relationship(models_v2.Subnet, load_on_pending=True,
|
subnet = orm.relationship(models_v2.Subnet, load_on_pending=True,
|
||||||
backref=orm.backref('service_types',
|
backref=orm.backref('service_types',
|
||||||
lazy='subquery',
|
lazy='selectin',
|
||||||
cascade='all, delete-orphan',
|
cascade='all, delete-orphan',
|
||||||
uselist=True))
|
uselist=True))
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
|
@ -26,6 +26,6 @@ class Tag(model_base.BASEV2):
|
|||||||
tag = sa.Column(sa.String(255), nullable=False, primary_key=True)
|
tag = sa.Column(sa.String(255), nullable=False, primary_key=True)
|
||||||
standard_attr = orm.relationship(
|
standard_attr = orm.relationship(
|
||||||
'StandardAttribute', load_on_pending=True,
|
'StandardAttribute', load_on_pending=True,
|
||||||
backref=orm.backref('tags', lazy='subquery', viewonly=True),
|
backref=orm.backref('tags', lazy='selectin', viewonly=True),
|
||||||
sync_backref=False)
|
sync_backref=False)
|
||||||
revises_on_change = ('standard_attr', )
|
revises_on_change = ('standard_attr', )
|
||||||
|
@ -132,7 +132,7 @@ class Port(standard_attr.HasStandardAttributes, model_base.BASEV2,
|
|||||||
fixed_ips = orm.relationship(IPAllocation,
|
fixed_ips = orm.relationship(IPAllocation,
|
||||||
backref=orm.backref('port',
|
backref=orm.backref('port',
|
||||||
load_on_pending=True),
|
load_on_pending=True),
|
||||||
lazy='subquery',
|
lazy='selectin',
|
||||||
cascade='all, delete-orphan',
|
cascade='all, delete-orphan',
|
||||||
order_by=(IPAllocation.ip_address,
|
order_by=(IPAllocation.ip_address,
|
||||||
IPAllocation.subnet_id))
|
IPAllocation.subnet_id))
|
||||||
@ -217,24 +217,24 @@ class Subnet(standard_attr.HasStandardAttributes, model_base.BASEV2,
|
|||||||
cidr = sa.Column(sa.String(64), nullable=False)
|
cidr = sa.Column(sa.String(64), nullable=False)
|
||||||
gateway_ip = sa.Column(sa.String(64))
|
gateway_ip = sa.Column(sa.String(64))
|
||||||
network_standard_attr = orm.relationship(
|
network_standard_attr = orm.relationship(
|
||||||
'StandardAttribute', lazy='subquery', viewonly=True,
|
'StandardAttribute', lazy='selectin', viewonly=True,
|
||||||
secondary='networks', uselist=False,
|
secondary='networks', uselist=False,
|
||||||
load_on_pending=True)
|
load_on_pending=True)
|
||||||
revises_on_change = ('network_standard_attr', )
|
revises_on_change = ('network_standard_attr', )
|
||||||
allocation_pools = orm.relationship(IPAllocationPool,
|
allocation_pools = orm.relationship(IPAllocationPool,
|
||||||
backref='subnet',
|
backref='subnet',
|
||||||
lazy="subquery",
|
lazy="selectin",
|
||||||
cascade='delete')
|
cascade='delete')
|
||||||
enable_dhcp = sa.Column(sa.Boolean())
|
enable_dhcp = sa.Column(sa.Boolean())
|
||||||
dns_nameservers = orm.relationship(DNSNameServer,
|
dns_nameservers = orm.relationship(DNSNameServer,
|
||||||
backref='subnet',
|
backref='subnet',
|
||||||
cascade='all, delete, delete-orphan',
|
cascade='all, delete, delete-orphan',
|
||||||
order_by=DNSNameServer.order,
|
order_by=DNSNameServer.order,
|
||||||
lazy='subquery')
|
lazy='selectin')
|
||||||
routes = orm.relationship(SubnetRoute,
|
routes = orm.relationship(SubnetRoute,
|
||||||
backref='subnet',
|
backref='subnet',
|
||||||
cascade='all, delete, delete-orphan',
|
cascade='all, delete, delete-orphan',
|
||||||
lazy='subquery')
|
lazy='selectin')
|
||||||
ipv6_ra_mode = sa.Column(sa.Enum(constants.IPV6_SLAAC,
|
ipv6_ra_mode = sa.Column(sa.Enum(constants.IPV6_SLAAC,
|
||||||
constants.DHCPV6_STATEFUL,
|
constants.DHCPV6_STATEFUL,
|
||||||
constants.DHCPV6_STATELESS,
|
constants.DHCPV6_STATELESS,
|
||||||
@ -299,7 +299,7 @@ class SubnetPool(standard_attr.HasStandardAttributes, model_base.BASEV2,
|
|||||||
prefixes = orm.relationship(SubnetPoolPrefix,
|
prefixes = orm.relationship(SubnetPoolPrefix,
|
||||||
backref='subnetpools',
|
backref='subnetpools',
|
||||||
cascade='all, delete, delete-orphan',
|
cascade='all, delete, delete-orphan',
|
||||||
lazy='subquery')
|
lazy='selectin')
|
||||||
rbac_entries = sa.orm.relationship(rbac_db_models.SubnetPoolRBAC,
|
rbac_entries = sa.orm.relationship(rbac_db_models.SubnetPoolRBAC,
|
||||||
backref='subnetpools',
|
backref='subnetpools',
|
||||||
lazy='joined',
|
lazy='joined',
|
||||||
@ -317,7 +317,7 @@ class Network(standard_attr.HasStandardAttributes, model_base.BASEV2,
|
|||||||
name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
|
name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
|
||||||
subnets = orm.relationship(
|
subnets = orm.relationship(
|
||||||
Subnet,
|
Subnet,
|
||||||
lazy="subquery")
|
lazy="selectin")
|
||||||
status = sa.Column(sa.String(16))
|
status = sa.Column(sa.String(16))
|
||||||
admin_state_up = sa.Column(sa.Boolean)
|
admin_state_up = sa.Column(sa.Boolean)
|
||||||
vlan_transparent = sa.Column(sa.Boolean, nullable=True)
|
vlan_transparent = sa.Column(sa.Boolean, nullable=True)
|
||||||
@ -331,7 +331,7 @@ class Network(standard_attr.HasStandardAttributes, model_base.BASEV2,
|
|||||||
default=constants.DEFAULT_NETWORK_MTU,
|
default=constants.DEFAULT_NETWORK_MTU,
|
||||||
server_default=str(constants.DEFAULT_NETWORK_MTU))
|
server_default=str(constants.DEFAULT_NETWORK_MTU))
|
||||||
dhcp_agents = orm.relationship(
|
dhcp_agents = orm.relationship(
|
||||||
'Agent', lazy='subquery', viewonly=True,
|
'Agent', lazy='selectin', viewonly=True,
|
||||||
secondary=ndab_model.NetworkDhcpAgentBinding.__table__)
|
secondary=ndab_model.NetworkDhcpAgentBinding.__table__)
|
||||||
api_collections = [net_def.COLLECTION_NAME]
|
api_collections = [net_def.COLLECTION_NAME]
|
||||||
collection_resource_map = {net_def.COLLECTION_NAME: net_def.RESOURCE_NAME}
|
collection_resource_map = {net_def.COLLECTION_NAME: net_def.RESOURCE_NAME}
|
||||||
|
@ -31,7 +31,7 @@ class NetworkDhcpAgentBinding(model_base.BASEV2):
|
|||||||
network_id = sa.Column(sa.String(36),
|
network_id = sa.Column(sa.String(36),
|
||||||
sa.ForeignKey("networks.id", ondelete='CASCADE'),
|
sa.ForeignKey("networks.id", ondelete='CASCADE'),
|
||||||
primary_key=True)
|
primary_key=True)
|
||||||
dhcp_agent = orm.relationship(agent_model.Agent, lazy='subquery')
|
dhcp_agent = orm.relationship(agent_model.Agent, lazy='selectin')
|
||||||
dhcp_agent_id = sa.Column(sa.String(36),
|
dhcp_agent_id = sa.Column(sa.String(36),
|
||||||
sa.ForeignKey("agents.id",
|
sa.ForeignKey("agents.id",
|
||||||
ondelete='CASCADE'),
|
ondelete='CASCADE'),
|
||||||
|
@ -44,6 +44,9 @@ import_packaging = re.compile(r"\bimport[\s]+packaging\b")
|
|||||||
import_version_from_packaging = (
|
import_version_from_packaging = (
|
||||||
re.compile(r"\bfrom[\s]+packaging[\s]+import[\s]version\b"))
|
re.compile(r"\bfrom[\s]+packaging[\s]+import[\s]version\b"))
|
||||||
|
|
||||||
|
filter_lazy_subquery = re.compile(r".*lazy=.+subquery")
|
||||||
|
filter_subquery_load = re.compile(r".*subqueryload\(")
|
||||||
|
|
||||||
|
|
||||||
@core.flake8ext
|
@core.flake8ext
|
||||||
def check_assert_called_once_with(logical_line, filename):
|
def check_assert_called_once_with(logical_line, filename):
|
||||||
@ -263,3 +266,17 @@ def check_no_import_packaging(logical_line, filename, noqa):
|
|||||||
for regex in import_packaging, import_version_from_packaging:
|
for regex in import_packaging, import_version_from_packaging:
|
||||||
if re.match(regex, logical_line):
|
if re.match(regex, logical_line):
|
||||||
yield (0, msg)
|
yield (0, msg)
|
||||||
|
|
||||||
|
|
||||||
|
@core.flake8ext
|
||||||
|
def check_no_sqlalchemy_lazy_subquery(logical_line):
|
||||||
|
"""N350 - Use selectin DB load strategy instead of subquery."""
|
||||||
|
|
||||||
|
msg = ("N350: Use selectin DB load strategy instead of "
|
||||||
|
"subquery with sqlalchemy.")
|
||||||
|
|
||||||
|
if filter_lazy_subquery.match(logical_line):
|
||||||
|
yield (0, msg)
|
||||||
|
|
||||||
|
if filter_subquery_load.match(logical_line):
|
||||||
|
yield (0, msg)
|
||||||
|
@ -79,7 +79,7 @@ def _get_active_network_ports(context, network_id):
|
|||||||
agent_model.Agent,
|
agent_model.Agent,
|
||||||
agent_model.Agent.host == ml2_models.PortBinding.host)
|
agent_model.Agent.host == ml2_models.PortBinding.host)
|
||||||
query = query.join(models_v2.Port)
|
query = query.join(models_v2.Port)
|
||||||
query = query.options(orm.subqueryload(ml2_models.PortBinding.port))
|
query = query.options(orm.selectinload(ml2_models.PortBinding.port))
|
||||||
query = query.filter(models_v2.Port.network_id == network_id,
|
query = query.filter(models_v2.Port.network_id == network_id,
|
||||||
models_v2.Port.status == const.PORT_STATUS_ACTIVE)
|
models_v2.Port.status == const.PORT_STATUS_ACTIVE)
|
||||||
return query
|
return query
|
||||||
@ -126,7 +126,7 @@ def _get_dvr_active_network_ports(context, network_id):
|
|||||||
ml2_models.DistributedPortBinding.host)
|
ml2_models.DistributedPortBinding.host)
|
||||||
query = query.join(models_v2.Port)
|
query = query.join(models_v2.Port)
|
||||||
query = query.options(
|
query = query.options(
|
||||||
orm.subqueryload(ml2_models.DistributedPortBinding.port))
|
orm.selectinload(ml2_models.DistributedPortBinding.port))
|
||||||
query = query.filter(models_v2.Port.network_id == network_id,
|
query = query.filter(models_v2.Port.network_id == network_id,
|
||||||
models_v2.Port.status == const.PORT_STATUS_ACTIVE,
|
models_v2.Port.status == const.PORT_STATUS_ACTIVE,
|
||||||
models_v2.Port.device_owner ==
|
models_v2.Port.device_owner ==
|
||||||
|
@ -89,7 +89,7 @@ class PortBindingLevel(model_base.BASEV2):
|
|||||||
port = orm.relationship(
|
port = orm.relationship(
|
||||||
models_v2.Port,
|
models_v2.Port,
|
||||||
load_on_pending=True,
|
load_on_pending=True,
|
||||||
backref=orm.backref("binding_levels", lazy='subquery',
|
backref=orm.backref("binding_levels", lazy='selectin',
|
||||||
cascade='delete'))
|
cascade='delete'))
|
||||||
segment = orm.relationship(
|
segment = orm.relationship(
|
||||||
segment_models.NetworkSegment,
|
segment_models.NetworkSegment,
|
||||||
|
@ -46,7 +46,7 @@ class Trunk(standard_attr.HasStandardAttributes, model_base.BASEV2,
|
|||||||
cascade='delete'))
|
cascade='delete'))
|
||||||
|
|
||||||
sub_ports = sa.orm.relationship(
|
sub_ports = sa.orm.relationship(
|
||||||
'SubPort', lazy='subquery', uselist=True, cascade="all, delete-orphan")
|
'SubPort', lazy='selectin', uselist=True, cascade="all, delete-orphan")
|
||||||
api_collections = ['trunks']
|
api_collections = ['trunks']
|
||||||
collection_resource_map = {'trunks': 'trunk'}
|
collection_resource_map = {'trunks': 'trunk'}
|
||||||
tag_support = True
|
tag_support = True
|
||||||
|
@ -279,3 +279,14 @@ class HackingTestCase(base.BaseTestCase):
|
|||||||
_pass(["_('foo')"], "neutron/_i18n.py")
|
_pass(["_('foo')"], "neutron/_i18n.py")
|
||||||
_pass(["_('foo')"], "neutron/i18n.py")
|
_pass(["_('foo')"], "neutron/i18n.py")
|
||||||
_pass(["_('foo')"], "neutron/foo.py", noqa=True)
|
_pass(["_('foo')"], "neutron/foo.py", noqa=True)
|
||||||
|
|
||||||
|
def test_check_no_sqlalchemy_lazy_subquery(self):
|
||||||
|
f = checks.check_no_sqlalchemy_lazy_subquery
|
||||||
|
self.assertLineFails('N350', f,
|
||||||
|
"backref=orm.backref('tags', lazy='subquery', viewonly=True),")
|
||||||
|
self.assertLineFails('N350', f,
|
||||||
|
"query.options(orm.subqueryload(ml2_models.PortBinding.port))")
|
||||||
|
self.assertLinePasses(f,
|
||||||
|
"backref=orm.backref('tags', lazy='selectin', viewonly=True),")
|
||||||
|
self.assertLinePasses(f,
|
||||||
|
"query.options(orm.selectinload(ml2_models.PortBinding.port))")
|
||||||
|
1
tox.ini
1
tox.ini
@ -239,6 +239,7 @@ extension =
|
|||||||
N346 = neutron.hacking.checks:check_no_sqlalchemy_event_import
|
N346 = neutron.hacking.checks:check_no_sqlalchemy_event_import
|
||||||
N348 = neutron.hacking.checks:check_no_import_six
|
N348 = neutron.hacking.checks:check_no_import_six
|
||||||
N349 = neutron.hacking.checks:check_no_import_packaging
|
N349 = neutron.hacking.checks:check_no_import_packaging
|
||||||
|
N350 = neutron.hacking.checks:check_no_sqlalchemy_lazy_subquery
|
||||||
# Checks from neutron-lib
|
# Checks from neutron-lib
|
||||||
N521 = neutron_lib.hacking.checks:use_jsonutils
|
N521 = neutron_lib.hacking.checks:use_jsonutils
|
||||||
N524 = neutron_lib.hacking.checks:check_no_contextlib_nested
|
N524 = neutron_lib.hacking.checks:check_no_contextlib_nested
|
||||||
|
Loading…
x
Reference in New Issue
Block a user