launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #14330
[Merge] lp:~abentley/launchpad/fix-bp-timeouts into lp:launchpad
Aaron Bentley has proposed merging lp:~abentley/launchpad/fix-bp-timeouts into lp:launchpad.
Commit message:
Fix timeouts checking blueprint visiblity
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1075569 in Launchpad itself: "Timeout loading milestone because of blueprints"
https://bugs.launchpad.net/launchpad/+bug/1075569
Bug #1077980 in Launchpad itself: "viewing project page leads to timeout when private blueprints exists"
https://bugs.launchpad.net/launchpad/+bug/1077980
Bug #1078239 in Launchpad itself: "loading team blueprints leads to timeout Person:+index"
https://bugs.launchpad.net/launchpad/+bug/1078239
Bug #1079390 in Launchpad itself: "Person:+upcomingwork timeout querying specifiations"
https://bugs.launchpad.net/launchpad/+bug/1079390
For more details, see:
https://code.launchpad.net/~abentley/launchpad/fix-bp-timeouts/+merge/134744
= Summary =
Fix the following bugs:
Bug #1075569: Timeout loading milestone because of blueprints
Bug #1077980: viewing project page leads to timeout when private blueprints exists
Bug #1078239: loading team blueprints leads to timeout Person:+index
Bug #1079390: Person:+upcomingwork timeout querying specifiations
== Proposed fix ==
Switch from get_specification_privacy_filter to visible_specification_query everywhere.
== Pre-implementation notes ==
None
== LOC Rationale ==
Part of private products.
== Implementation details ==
visible_specification_query does not guarantee distinct results, so added config(distinct=True) as needed.
Since get_specification_privacy_filter is no longer used, it is deleted, and its tests migrated to visible_specification_query.
TestPersonUpcomingWork.test_container_progressbar assumed that WorkItem containers were in a specific order, based on the id of their initial WorkItem. This work changed the ordering, but a consistent ordering makes sense, so I've made the ordering explicit.
All the call sites of get_specification_filters now use visible_specification_query, so its assume_product_active behaviour can now become mandatory.
_preload_specifications_people is used by both Product and Distribution, so tweaked Distribution to supply tables.
In sharingjob, replaced get_specification_privacy_filter by introducing a sub-select, because otherwise, negating visible_specification_query would be very tricky.
Fixed lint in SpecificationWorkItem.__init__.
Fixed long line by assigning LatestPersonSourcePackageReleaseCache.upload_distroseries_id to a local variable.
== Tests ==
bin/test -v
== Demo and Q/A ==
Go to the staging versions of all pages linked in the bugs and see whether they consistently time out.
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/blueprints/model/specificationworkitem.py
lib/lp/registry/model/sharingjob.py
lib/lp/blueprints/tests/test_specification.py
lib/lp/blueprints/model/specification.py
lib/lp/blueprints/model/sprint.py
lib/lp/registry/model/product.py
lib/lp/registry/model/person.py
lib/lp/registry/model/milestone.py
lib/lp/registry/model/distribution.py
--
https://code.launchpad.net/~abentley/launchpad/fix-bp-timeouts/+merge/134744
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~abentley/launchpad/fix-bp-timeouts into lp:launchpad.
=== modified file 'lib/lp/blueprints/model/specification.py'
--- lib/lp/blueprints/model/specification.py 2012-11-12 16:40:28 +0000
+++ lib/lp/blueprints/model/specification.py 2012-11-16 20:58:25 +0000
@@ -4,7 +4,6 @@
__metaclass__ = type
__all__ = [
'get_specification_filters',
- 'get_specification_privacy_filter',
'HasSpecificationsMixin',
'recursive_blocked_query',
'recursive_dependent_query',
@@ -960,7 +959,7 @@
elif sort == SpecificationSort.DATE:
return (Desc(Specification.datecreated), Specification.id)
- def _preload_specifications_people(self, clauses):
+ def _preload_specifications_people(self, tables, clauses):
"""Perform eager loading of people and their validity for query.
:param query: a string query generated in the 'specifications'
@@ -1001,7 +1000,7 @@
index += 1
decorator(person, column)
- results = Store.of(self).find(Specification, *clauses)
+ results = Store.of(self).using(*tables).find(Specification, *clauses)
return DecoratedResultSet(results, pre_iter_hook=cache_people)
@property
@@ -1230,58 +1229,6 @@
return Specification.get(spec_id)
-def get_specification_privacy_filter(user):
- """Return a Storm expression for filtering specifications by privacy.
-
- :param user: A Person ID or a column reference.
- :return: A Storm expression to check if a peron has access grants
- for a specification.
- """
- # Avoid circular imports.
- from lp.registry.model.accesspolicy import (
- AccessArtifact,
- AccessPolicy,
- AccessPolicyGrantFlat,
- )
- public_specification_filter = (
- Specification.information_type.is_in(PUBLIC_INFORMATION_TYPES))
- if user is None:
- return public_specification_filter
- return Or(
- public_specification_filter,
- Specification.id.is_in(
- Select(
- Specification.id,
- tables=(
- Specification,
- Join(
- AccessPolicy,
- And(
- Or(
- Specification.productID ==
- AccessPolicy.product_id,
- Specification.distributionID ==
- AccessPolicy.distribution_id),
- Specification.information_type ==
- AccessPolicy.type)),
- Join(
- AccessPolicyGrantFlat,
- AccessPolicy.id == AccessPolicyGrantFlat.policy_id),
- LeftJoin(
- AccessArtifact,
- AccessPolicyGrantFlat.abstract_artifact_id ==
- AccessArtifact.id),
- Join(
- TeamParticipation,
- And(
- TeamParticipation.team ==
- AccessPolicyGrantFlat.grantee_id,
- TeamParticipation.person == user))),
- where=Or(
- AccessPolicyGrantFlat.abstract_artifact_id == None,
- AccessArtifact.specification_id == Specification.id))))
-
-
def visible_specification_query(user):
"""Return a Storm expression and list of tables for filtering
specifications by privacy.
@@ -1324,21 +1271,13 @@
return tables, clauses
-def get_specification_filters(filter, assume_product_active=False):
+def get_specification_filters(filter):
"""Return a list of Storm expressions for filtering Specifications.
:param filters: A collection of SpecificationFilter and/or strings.
Strings are used for text searches.
- :param assume_product_active: If True, assume the Product is active,
- instead of ensuring it is active.
"""
- from lp.registry.model.product import Product
clauses = []
- # If Product is used, it must be active.
- if not assume_product_active:
- clauses.extend([Or(Specification.product == None,
- Not(Specification.productID.is_in(Select(Product.id,
- Product.active == False))))])
# ALL is the trump card.
if SpecificationFilter.ALL in filter:
return clauses
=== modified file 'lib/lp/blueprints/model/specificationworkitem.py'
--- lib/lp/blueprints/model/specificationworkitem.py 2012-04-05 13:05:04 +0000
+++ lib/lp/blueprints/model/specificationworkitem.py 2012-11-16 20:58:25 +0000
@@ -29,6 +29,7 @@
implements(ISpecificationWorkItem)
__storm_table__ = 'SpecificationWorkItem'
+ __storm_order__ = 'id'
id = Int(primary=True)
title = Unicode(allow_none=False)
@@ -53,12 +54,12 @@
def __init__(self, title, status, specification, assignee, milestone,
sequence):
- self.title=title
- self.status=status
+ self.title = title
+ self.status = status
self.specification = specification
- self.assignee=assignee
- self.milestone=milestone
- self.sequence=sequence
+ self.assignee = assignee
+ self.milestone = milestone
+ self.sequence = sequence
@property
def is_complete(self):
=== modified file 'lib/lp/blueprints/model/sprint.py'
--- lib/lp/blueprints/model/sprint.py 2012-11-12 16:40:28 +0000
+++ lib/lp/blueprints/model/sprint.py 2012-11-16 20:58:25 +0000
@@ -155,8 +155,7 @@
if len(statuses) > 0:
query.append(Or(*statuses))
# Filter for specification text
- query.extend(
- get_specification_filters(filter, assume_product_active=True))
+ query.extend(get_specification_filters(filter))
return tables, query
def all_specifications(self, user):
=== modified file 'lib/lp/blueprints/tests/test_specification.py'
--- lib/lp/blueprints/tests/test_specification.py 2012-10-16 14:47:54 +0000
+++ lib/lp/blueprints/tests/test_specification.py 2012-11-16 20:58:25 +0000
@@ -33,8 +33,8 @@
from lp.blueprints.errors import TargetAlreadyHasSpecification
from lp.blueprints.interfaces.specification import ISpecificationSet
from lp.blueprints.model.specification import (
- get_specification_privacy_filter,
Specification,
+ visible_specification_query,
)
from lp.registry.enums import (
SharingPermission,
@@ -397,8 +397,8 @@
specification.target.owner, specification,
error_expected=False, attribute='name', value='foo')
- def test_get_specification_privacy_filter(self):
- # get_specification_privacy_filter() returns a Storm expression
+ def test_visible_specification_query(self):
+ # visible_specification_query returns a Storm expression
# that can be used to filter specifications by their visibility-
owner = self.factory.makePerson()
product = self.factory.makeProduct(
@@ -413,30 +413,32 @@
all_specs = [
public_spec, proprietary_spec_1, proprietary_spec_2]
store = Store.of(product)
- specs_for_anon = store.find(
- Specification, get_specification_privacy_filter(None),
- Specification.productID == product.id)
- self.assertContentEqual([public_spec], specs_for_anon)
+ tables, query = visible_specification_query(None)
+ specs_for_anon = store.using(*tables).find(
+ Specification,
+ Specification.productID == product.id, *query)
+ self.assertContentEqual([public_spec],
+ specs_for_anon.config(distinct=True))
# Product owners havae grants on the product, the privacy
# filter returns thus all specifications for them.
- specs_for_owner = store.find(
- Specification, get_specification_privacy_filter(owner.id),
- Specification.productID == product.id)
+ tables, query = visible_specification_query(owner.id)
+ specs_for_owner = store.using(*tables).find(
+ Specification, Specification.productID == product.id, *query)
self.assertContentEqual(all_specs, specs_for_owner)
# The filter returns only public specs for ordinary users.
user = self.factory.makePerson()
- specs_for_other_user = store.find(
- Specification, get_specification_privacy_filter(user.id),
- Specification.productID == product.id)
+ tables, query = visible_specification_query(user.id)
+ specs_for_other_user = store.using(*tables).find(
+ Specification, Specification.productID == product.id, *query)
self.assertContentEqual([public_spec], specs_for_other_user)
# If the user has a grant for a specification, the filter returns
# this specification too.
with person_logged_in(owner):
getUtility(IService, 'sharing').ensureAccessGrants(
[user], owner, specifications=[proprietary_spec_1])
- specs_for_other_user = store.find(
- Specification, get_specification_privacy_filter(user.id),
- Specification.productID == product.id)
+ tables, query = visible_specification_query(user.id)
+ specs_for_other_user = store.using(*tables).find(
+ Specification, Specification.productID == product.id, *query)
self.assertContentEqual(
[public_spec, proprietary_spec_1], specs_for_other_user)
=== modified file 'lib/lp/registry/model/distribution.py'
--- lib/lp/registry/model/distribution.py 2012-11-02 00:53:58 +0000
+++ lib/lp/registry/model/distribution.py 2012-11-16 20:58:25 +0000
@@ -952,7 +952,8 @@
constraint)
if prejoin_people:
- results = self._preload_specifications_people(query)
+ results = self._preload_specifications_people([Specification],
+ query)
else:
results = Store.of(self).find(
Specification,
=== modified file 'lib/lp/registry/model/milestone.py'
--- lib/lp/registry/model/milestone.py 2012-10-26 08:59:24 +0000
+++ lib/lp/registry/model/milestone.py 2012-11-16 20:58:25 +0000
@@ -39,8 +39,8 @@
from lp.app.errors import NotFoundError
from lp.blueprints.model.specification import (
- get_specification_privacy_filter,
Specification,
+ visible_specification_query
)
from lp.blueprints.model.specificationworkitem import SpecificationWorkItem
from lp.bugs.interfaces.bugsummary import IBugSummaryDimension
@@ -155,10 +155,10 @@
"""See `IMilestoneData`"""
from lp.registry.model.person import Person
store = Store.of(self.target)
- origin = [
- Specification,
+ origin, clauses = visible_specification_query(user)
+ origin.extend([
LeftJoin(Person, Specification.assigneeID == Person.id),
- ]
+ ])
milestones = self._milestone_ids_expr(user)
results = store.using(*origin).find(
@@ -176,12 +176,12 @@
milestones),
SpecificationWorkItem.deleted == False)),
all=True)),
- get_specification_privacy_filter(user))
- results.config(distinct=True)
+ *clauses)
ordered_results = results.order_by(Desc(Specification.priority),
Specification.definition_status,
Specification.implementation_status,
Specification.title)
+ ordered_results.config(distinct=True)
mapper = lambda row: row[0]
return DecoratedResultSet(ordered_results, mapper)
=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py 2012-11-14 02:03:32 +0000
+++ lib/lp/registry/model/person.py 2012-11-16 20:58:25 +0000
@@ -130,10 +130,10 @@
)
from lp.blueprints.model.specification import (
get_specification_filters,
- get_specification_privacy_filter,
HasSpecificationsMixin,
Specification,
spec_started_clause,
+ visible_specification_query,
)
from lp.blueprints.model.specificationworkitem import SpecificationWorkItem
from lp.bugs.interfaces.bugtarget import IBugTarget
@@ -857,7 +857,8 @@
Select(SpecificationSubscription.specificationID,
[SpecificationSubscription.person == self]
)))
- clauses = [Or(*role_clauses), get_specification_privacy_filter(user)]
+ tables, clauses = visible_specification_query(user)
+ clauses.append(Or(*role_clauses))
# Defaults for completeness: if nothing is said about completeness
# then we want to show INCOMPLETE.
if SpecificationFilter.COMPLETE not in filter:
@@ -867,7 +868,7 @@
filter.add(SpecificationFilter.INCOMPLETE)
clauses.extend(get_specification_filters(filter))
- results = Store.of(self).find(Specification, *clauses)
+ results = Store.of(self).using(*tables).find(Specification, *clauses)
# The default sort is priority descending, so only explictly sort for
# DATE.
if sort == SpecificationSort.DATE:
@@ -876,6 +877,7 @@
sort = None
if sort is not None:
results = results.order_by(sort)
+ results.config(distinct=True)
if quantity is not None:
results = results[:quantity]
return results
@@ -1466,23 +1468,23 @@
from lp.registry.model.distribution import Distribution
store = Store.of(self)
WorkItem = SpecificationWorkItem
- origin = [
- WorkItem,
- Join(Specification, WorkItem.specification == Specification.id),
+ origin, query = visible_specification_query(user)
+ origin.extend([
+ Join(WorkItem, WorkItem.specification == Specification.id),
# WorkItems may not have a milestone and in that case they inherit
# the one from the spec.
Join(Milestone,
Coalesce(WorkItem.milestone_id,
Specification.milestoneID) == Milestone.id),
- ]
+ ])
today = datetime.today().date()
- query = And(
- get_specification_privacy_filter(user),
+ query.extend([
Milestone.dateexpected <= date, Milestone.dateexpected >= today,
WorkItem.deleted == False,
OR(WorkItem.assignee_id.is_in(self.participant_ids),
- Specification.assigneeID.is_in(self.participant_ids)))
- result = store.using(*origin).find(WorkItem, query)
+ Specification.assigneeID.is_in(self.participant_ids))])
+ result = store.using(*origin).find(WorkItem, *query)
+ result.config(distinct=True)
def eager_load(workitems):
specs = bulk.load_related(
@@ -2844,6 +2846,8 @@
pass
elif uploader_only:
lpspr = ClassAlias(LatestPersonSourcePackageReleaseCache, 'lpspr')
+ upload_distroseries_id = (
+ LatestPersonSourcePackageReleaseCache.upload_distroseries_id)
clauses.append(Not(Exists(Select(1,
where=And(
lpspr.sourcepackagename_id ==
@@ -2851,7 +2855,7 @@
lpspr.upload_archive_id ==
LatestPersonSourcePackageReleaseCache.upload_archive_id,
lpspr.upload_distroseries_id ==
- LatestPersonSourcePackageReleaseCache.upload_distroseries_id,
+ upload_distroseries_id,
lpspr.archive_purpose != ArchivePurpose.PPA,
lpspr.maintainer_id == self.id),
tables=lpspr))))
=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py 2012-11-15 23:05:42 +0000
+++ lib/lp/registry/model/product.py 2012-11-16 20:58:25 +0000
@@ -97,11 +97,11 @@
)
from lp.blueprints.model.specification import (
get_specification_filters,
- get_specification_privacy_filter,
HasSpecificationsMixin,
Specification,
SPECIFICATION_POLICY_ALLOWED_TYPES,
SPECIFICATION_POLICY_DEFAULT_TYPES,
+ visible_specification_query,
)
from lp.blueprints.model.sprint import HasSprintsMixin
from lp.bugs.interfaces.bugsummary import IBugSummaryDimension
@@ -1408,14 +1408,15 @@
# - completeness.
# - informational.
#
- clauses = [Specification.product == self,
- get_specification_privacy_filter(user)]
+ tables, clauses = visible_specification_query(user)
+ clauses.append(Specification.product == self)
clauses.extend(get_specification_filters(filter))
if prejoin_people:
- results = self._preload_specifications_people(clauses)
+ results = self._preload_specifications_people(tables, clauses)
else:
- results = Store.of(self).find(Specification, *clauses)
- results.order_by(order)
+ tableset = Store.of(self).using(*tables)
+ results = tableset.find(Specification, *clauses)
+ results.order_by(order).config(distinct=True)
if quantity is not None:
results = results[:quantity]
return results
=== modified file 'lib/lp/registry/model/sharingjob.py'
--- lib/lp/registry/model/sharingjob.py 2012-09-28 06:15:58 +0000
+++ lib/lp/registry/model/sharingjob.py 2012-11-16 20:58:25 +0000
@@ -44,8 +44,8 @@
from lp.app.enums import InformationType
from lp.blueprints.interfaces.specification import ISpecification
from lp.blueprints.model.specification import (
- get_specification_privacy_filter,
Specification,
+ visible_specification_query,
)
from lp.blueprints.model.specificationsubscription import (
SpecificationSubscription,
@@ -440,8 +440,7 @@
sub.person, self.requestor, ignore_permissions=True)
if specification_filters:
specification_filters.append(
- Not(get_specification_privacy_filter(
- SpecificationSubscription.personID)))
+ spec_not_visible(SpecificationSubscription.personID))
tables = (
SpecificationSubscription,
Join(
@@ -455,3 +454,10 @@
for sub in specifications_subscriptions:
sub.specification.unsubscribe(
sub.person, self.requestor, ignore_permissions=True)
+
+
+def spec_not_visible(person_id):
+ """Return an expression for finding specs not visible to the person."""
+ tables, clauses = visible_specification_query(person_id)
+ subselect = Select(Specification.id, tables=tables, where=And(clauses))
+ return Not(Specification.id.is_in(subselect))
Follow ups