Merge lp:~allenap/launchpad/bugwatch-bugtasks-as-a-list into lp:launchpad
- bugwatch-bugtasks-as-a-list
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Brad Crittenden |
Approved revision: | not available |
Merged at revision: | not available |
Proposed branch: | lp:~allenap/launchpad/bugwatch-bugtasks-as-a-list |
Merge into: | lp:launchpad |
Diff against target: |
164 lines (+38/-20) 4 files modified
lib/lp/bugs/browser/bugwatch.py (+1/-3) lib/lp/bugs/model/bugtask.py (+0/-1) lib/lp/bugs/model/bugwatch.py (+17/-13) lib/lp/bugs/tests/test_bugwatch.py (+20/-3) |
To merge this branch: | bzr merge lp:~allenap/launchpad/bugwatch-bugtasks-as-a-list |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Данило Шеган (community) | release-critical | Approve | |
Brad Crittenden (community) | code | Approve | |
Review via email: mp+16200@code.launchpad.net |
Commit message
Make BugWatch.bugtasks a list instead of a result set. This may help us narrow down the causes behind the 2009-12-14 outage.
Description of the change
Gavin Panella (allenap) wrote : | # |
Brad Crittenden (bac) wrote : | # |
Gavin your code changes look good. I'm glad to see the conversion to storm and I hope the change helps to isolate the real problem.
Gavin Panella (allenap) wrote : | # |
<bac> so your code changes look good and innocent enough
<bac> can you explain, slowly and using short words as if talking to a child, how this will help? :)
<allenap> The query that seems to hang around a long time in the db is the one - afaict - that is fulfilling BugWatch.bugtasks.
<allenap> That's iterated over in BugWatch.
<allenap> My hunch is that something is happening in one or more of those iterations that is causing the results from the database to not be pulled.
<allenap> I'm hoping that by sucking the results in immediately we can narrow down the issue. Maybe.
<bac> ok. i just don't understand why this query would now be causing problems. we use similar queries everywhere.
<bac> like i said, the changes are innocuous and i like the fact they have been stormified so i'm ok with trying the change
<bac> you may want to update the MP with more information if you seek an RC
<allenap> I don't think this query is a problem. I think it's that nothing is sucking the results back, so the database keeps it around as an in progress query.
Gavin Panella (allenap) wrote : | # |
I think this branch is worthy of release-critical because we need it to be in production to help us in narrow down issues in the checkwatches cron job; edge is not enough.
Данило Шеган (danilo) wrote : | # |
Looks good, just use the shortlist() instead of list() to prevent abuse and DoS vector.
Gavin Panella (allenap) wrote : | # |
> Review: Approve release-critical
> Looks good, just use the shortlist() instead of list() to prevent abuse and DoS vector.
Thanks Danilo. Diff attached.
1 | === modified file 'lib/lp/bugs/model/bugwatch.py' | |||
2 | --- lib/lp/bugs/model/bugwatch.py 2009-12-15 13:47:52 +0000 | |||
3 | +++ lib/lp/bugs/model/bugwatch.py 2009-12-15 17:28:51 +0000 | |||
4 | @@ -29,6 +29,7 @@ | |||
5 | 29 | from canonical.database.enumcol import EnumCol | 29 | from canonical.database.enumcol import EnumCol |
6 | 30 | from canonical.database.sqlbase import SQLBase | 30 | from canonical.database.sqlbase import SQLBase |
7 | 31 | from canonical.launchpad.database.message import Message | 31 | from canonical.launchpad.database.message import Message |
8 | 32 | from canonical.launchpad.helpers import shortlist | ||
9 | 32 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities | 33 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
10 | 33 | from canonical.launchpad.validators.email import valid_email | 34 | from canonical.launchpad.validators.email import valid_email |
11 | 34 | from canonical.launchpad.webapp import urlappend, urlsplit | 35 | from canonical.launchpad.webapp import urlappend, urlsplit |
12 | @@ -80,7 +81,8 @@ | |||
13 | 80 | @property | 81 | @property |
14 | 81 | def bugtasks(self): | 82 | def bugtasks(self): |
15 | 82 | tasks = Store.of(self).find(BugTask, BugTask.bugwatch == self.id) | 83 | tasks = Store.of(self).find(BugTask, BugTask.bugwatch == self.id) |
17 | 83 | return list(tasks.order_by(Desc(BugTask.datecreated))) | 84 | tasks = tasks.order_by(Desc(BugTask.datecreated)) |
18 | 85 | return shortlist(tasks, 10, 100) | ||
19 | 84 | 86 | ||
20 | 85 | @property | 87 | @property |
21 | 86 | def title(self): | 88 | def title(self): |
22 | 87 | 89 | ||
23 | === modified file 'lib/lp/bugs/tests/test_bugwatch.py' | |||
24 | --- lib/lp/bugs/tests/test_bugwatch.py 2009-06-25 00:40:31 +0000 | |||
25 | +++ lib/lp/bugs/tests/test_bugwatch.py 2009-12-15 17:28:51 +0000 | |||
26 | @@ -11,13 +11,15 @@ | |||
27 | 11 | 11 | ||
28 | 12 | from zope.component import getUtility | 12 | from zope.component import getUtility |
29 | 13 | 13 | ||
31 | 14 | from canonical.launchpad.ftests import login, ANONYMOUS | 14 | from canonical.launchpad.webapp import urlsplit |
32 | 15 | from canonical.testing import ( | ||
33 | 16 | DatabaseFunctionalLayer, LaunchpadFunctionalLayer) | ||
34 | 17 | |||
35 | 15 | from lp.bugs.interfaces.bugtracker import BugTrackerType, IBugTrackerSet | 18 | from lp.bugs.interfaces.bugtracker import BugTrackerType, IBugTrackerSet |
36 | 16 | from lp.bugs.interfaces.bugwatch import ( | 19 | from lp.bugs.interfaces.bugwatch import ( |
37 | 17 | IBugWatchSet, NoBugTrackerFound, UnrecognizedBugTrackerURL) | 20 | IBugWatchSet, NoBugTrackerFound, UnrecognizedBugTrackerURL) |
38 | 18 | from lp.registry.interfaces.person import IPersonSet | 21 | from lp.registry.interfaces.person import IPersonSet |
41 | 19 | from canonical.launchpad.webapp import urlsplit | 22 | from lp.testing import ANONYMOUS, TestCaseWithFactory, login |
40 | 20 | from canonical.testing import LaunchpadFunctionalLayer | ||
42 | 21 | 23 | ||
43 | 22 | 24 | ||
44 | 23 | class ExtractBugTrackerAndBugTestBase(unittest.TestCase): | 25 | class ExtractBugTrackerAndBugTestBase(unittest.TestCase): |
45 | @@ -309,6 +311,20 @@ | |||
46 | 309 | bug_id = '12345' | 311 | bug_id = '12345' |
47 | 310 | 312 | ||
48 | 311 | 313 | ||
49 | 314 | class TestBugWatchBugTasks(TestCaseWithFactory): | ||
50 | 315 | |||
51 | 316 | layer = DatabaseFunctionalLayer | ||
52 | 317 | |||
53 | 318 | def setUp(self): | ||
54 | 319 | super(TestBugWatchBugTasks, self).setUp('test@canonical.com') | ||
55 | 320 | self.bug_watch = self.factory.makeBugWatch() | ||
56 | 321 | |||
57 | 322 | def test_bugtasks(self): | ||
58 | 323 | # BugWatch.bugtasks is always a list. | ||
59 | 324 | self.assertIsInstance( | ||
60 | 325 | self.bug_watch.bugtasks, list) | ||
61 | 326 | |||
62 | 327 | |||
63 | 312 | def test_suite(): | 328 | def test_suite(): |
64 | 313 | suite = unittest.TestSuite() | 329 | suite = unittest.TestSuite() |
65 | 314 | suite.addTest(unittest.makeSuite(BugzillaExtractBugTrackerAndBugTest)) | 330 | suite.addTest(unittest.makeSuite(BugzillaExtractBugTrackerAndBugTest)) |
66 | @@ -331,6 +347,7 @@ | |||
67 | 331 | PHPProjectBugTrackerExtractBugTrackerAndBugTest)) | 347 | PHPProjectBugTrackerExtractBugTrackerAndBugTest)) |
68 | 332 | suite.addTest(unittest.makeSuite( | 348 | suite.addTest(unittest.makeSuite( |
69 | 333 | GoogleCodeBugTrackerExtractBugTrackerAndBugTest)) | 349 | GoogleCodeBugTrackerExtractBugTrackerAndBugTest)) |
70 | 350 | suite.addTest(unittest.makeSuite(TestBugWatchBugTasks)) | ||
71 | 334 | return suite | 351 | return suite |
72 | 335 | 352 | ||
73 | 336 | 353 |
Preview Diff
1 | === modified file 'lib/lp/bugs/browser/bugwatch.py' | |||
2 | --- lib/lp/bugs/browser/bugwatch.py 2009-09-03 15:47:41 +0000 | |||
3 | +++ lib/lp/bugs/browser/bugwatch.py 2009-12-15 17:34:15 +0000 | |||
4 | @@ -19,7 +19,6 @@ | |||
5 | 19 | from lp.bugs.browser.bugcomment import ( | 19 | from lp.bugs.browser.bugcomment import ( |
6 | 20 | should_display_remote_comments) | 20 | should_display_remote_comments) |
7 | 21 | from canonical.launchpad.fields import URIField | 21 | from canonical.launchpad.fields import URIField |
8 | 22 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities | ||
9 | 23 | from canonical.launchpad.webapp.interfaces import ILaunchBag | 22 | from canonical.launchpad.webapp.interfaces import ILaunchBag |
10 | 24 | from lp.bugs.interfaces.bug import IBugWatch | 23 | from lp.bugs.interfaces.bug import IBugWatch |
11 | 25 | from lp.bugs.interfaces.bugwatch import ( | 24 | from lp.bugs.interfaces.bugwatch import ( |
12 | @@ -54,7 +53,6 @@ | |||
13 | 54 | team, no comments will be returned. | 53 | team, no comments will be returned. |
14 | 55 | """ | 54 | """ |
15 | 56 | user = getUtility(ILaunchBag).user | 55 | user = getUtility(ILaunchBag).user |
16 | 57 | lp_developers = getUtility(ILaunchpadCelebrities).launchpad_developers | ||
17 | 58 | if not should_display_remote_comments(user): | 56 | if not should_display_remote_comments(user): |
18 | 59 | return [] | 57 | return [] |
19 | 60 | 58 | ||
20 | @@ -121,7 +119,7 @@ | |||
21 | 121 | 119 | ||
22 | 122 | def bugWatchIsUnlinked(self, action): | 120 | def bugWatchIsUnlinked(self, action): |
23 | 123 | """Return whether the bug watch is unlinked.""" | 121 | """Return whether the bug watch is unlinked.""" |
25 | 124 | return self.context.bugtasks.count() == 0 | 122 | return len(self.context.bugtasks) == 0 |
26 | 125 | 123 | ||
27 | 126 | @action('Delete Bug Watch', name='delete', condition=bugWatchIsUnlinked) | 124 | @action('Delete Bug Watch', name='delete', condition=bugWatchIsUnlinked) |
28 | 127 | def delete_action(self, action, data): | 125 | def delete_action(self, action, data): |
29 | 128 | 126 | ||
30 | === modified file 'lib/lp/bugs/model/bugtask.py' | |||
31 | --- lib/lp/bugs/model/bugtask.py 2009-10-15 04:00:34 +0000 | |||
32 | +++ lib/lp/bugs/model/bugtask.py 2009-12-15 17:34:15 +0000 | |||
33 | @@ -950,7 +950,6 @@ | |||
34 | 950 | 950 | ||
35 | 951 | def getPackageComponent(self): | 951 | def getPackageComponent(self): |
36 | 952 | """See `IBugTask`.""" | 952 | """See `IBugTask`.""" |
37 | 953 | sourcepackage = None | ||
38 | 954 | if ISourcePackage.providedBy(self.target): | 953 | if ISourcePackage.providedBy(self.target): |
39 | 955 | return self.target.latest_published_component | 954 | return self.target.latest_published_component |
40 | 956 | if IDistributionSourcePackage.providedBy(self.target): | 955 | if IDistributionSourcePackage.providedBy(self.target): |
41 | 957 | 956 | ||
42 | === modified file 'lib/lp/bugs/model/bugwatch.py' | |||
43 | --- lib/lp/bugs/model/bugwatch.py 2009-09-04 09:48:04 +0000 | |||
44 | +++ lib/lp/bugs/model/bugwatch.py 2009-12-15 17:34:15 +0000 | |||
45 | @@ -15,34 +15,36 @@ | |||
46 | 15 | from zope.component import getUtility | 15 | from zope.component import getUtility |
47 | 16 | 16 | ||
48 | 17 | # SQL imports | 17 | # SQL imports |
51 | 18 | from sqlobject import (ForeignKey, StringCol, SQLObjectNotFound, | 18 | from sqlobject import ForeignKey, SQLObjectNotFound, StringCol |
50 | 19 | SQLMultipleJoin) | ||
52 | 20 | 19 | ||
54 | 21 | from storm.expr import Not | 20 | from storm.expr import Desc, Not |
55 | 22 | from storm.store import Store | 21 | from storm.store import Store |
56 | 23 | 22 | ||
57 | 24 | from lazr.lifecycle.event import ObjectModifiedEvent | 23 | from lazr.lifecycle.event import ObjectModifiedEvent |
58 | 25 | from lazr.lifecycle.snapshot import Snapshot | 24 | from lazr.lifecycle.snapshot import Snapshot |
59 | 26 | from lazr.uri import find_uris_in_text | 25 | from lazr.uri import find_uris_in_text |
60 | 27 | 26 | ||
61 | 28 | from canonical.database.sqlbase import SQLBase | ||
62 | 29 | from canonical.database.constants import UTC_NOW | 27 | from canonical.database.constants import UTC_NOW |
63 | 30 | from canonical.database.datetimecol import UtcDateTimeCol | 28 | from canonical.database.datetimecol import UtcDateTimeCol |
64 | 31 | from canonical.database.enumcol import EnumCol | 29 | from canonical.database.enumcol import EnumCol |
68 | 32 | 30 | from canonical.database.sqlbase import SQLBase | |
66 | 33 | from lp.bugs.model.bugmessage import BugMessage | ||
67 | 34 | from lp.bugs.model.bugset import BugSetBase | ||
69 | 35 | from canonical.launchpad.database.message import Message | 31 | from canonical.launchpad.database.message import Message |
70 | 32 | from canonical.launchpad.helpers import shortlist | ||
71 | 36 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities | 33 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
72 | 34 | from canonical.launchpad.validators.email import valid_email | ||
73 | 35 | from canonical.launchpad.webapp import urlappend, urlsplit | ||
74 | 37 | from canonical.launchpad.webapp.interfaces import NotFoundError | 36 | from canonical.launchpad.webapp.interfaces import NotFoundError |
75 | 37 | |||
76 | 38 | from lp.bugs.interfaces.bug import IBugWatch | 38 | from lp.bugs.interfaces.bug import IBugWatch |
77 | 39 | from lp.bugs.interfaces.bugtracker import BugTrackerType, IBugTrackerSet | 39 | from lp.bugs.interfaces.bugtracker import BugTrackerType, IBugTrackerSet |
78 | 40 | from lp.bugs.interfaces.bugwatch import ( | 40 | from lp.bugs.interfaces.bugwatch import ( |
79 | 41 | BugWatchErrorType, IBugWatchSet, NoBugTrackerFound, | 41 | BugWatchErrorType, IBugWatchSet, NoBugTrackerFound, |
80 | 42 | UnrecognizedBugTrackerURL) | 42 | UnrecognizedBugTrackerURL) |
82 | 43 | from canonical.launchpad.validators.email import valid_email | 43 | from lp.bugs.model.bugmessage import BugMessage |
83 | 44 | from lp.bugs.model.bugset import BugSetBase | ||
84 | 45 | from lp.bugs.model.bugtask import BugTask | ||
85 | 44 | from lp.registry.interfaces.person import validate_public_person | 46 | from lp.registry.interfaces.person import validate_public_person |
87 | 45 | from canonical.launchpad.webapp import urlappend, urlsplit | 47 | |
88 | 46 | 48 | ||
89 | 47 | BUG_TRACKER_URL_FORMATS = { | 49 | BUG_TRACKER_URL_FORMATS = { |
90 | 48 | BugTrackerType.BUGZILLA: 'show_bug.cgi?id=%s', | 50 | BugTrackerType.BUGZILLA: 'show_bug.cgi?id=%s', |
91 | @@ -76,9 +78,11 @@ | |||
92 | 76 | dbName='owner', foreignKey='Person', | 78 | dbName='owner', foreignKey='Person', |
93 | 77 | storm_validator=validate_public_person, notNull=True) | 79 | storm_validator=validate_public_person, notNull=True) |
94 | 78 | 80 | ||
98 | 79 | # useful joins | 81 | @property |
99 | 80 | bugtasks = SQLMultipleJoin('BugTask', joinColumn='bugwatch', | 82 | def bugtasks(self): |
100 | 81 | orderBy=['-datecreated']) | 83 | tasks = Store.of(self).find(BugTask, BugTask.bugwatch == self.id) |
101 | 84 | tasks = tasks.order_by(Desc(BugTask.datecreated)) | ||
102 | 85 | return shortlist(tasks, 10, 100) | ||
103 | 82 | 86 | ||
104 | 83 | @property | 87 | @property |
105 | 84 | def title(self): | 88 | def title(self): |
106 | @@ -162,7 +166,7 @@ | |||
107 | 162 | 166 | ||
108 | 163 | def destroySelf(self): | 167 | def destroySelf(self): |
109 | 164 | """See `IBugWatch`.""" | 168 | """See `IBugWatch`.""" |
111 | 165 | assert self.bugtasks.count() == 0, "Can't delete linked bug watches" | 169 | assert len(self.bugtasks) == 0, "Can't delete linked bug watches" |
112 | 166 | SQLBase.destroySelf(self) | 170 | SQLBase.destroySelf(self) |
113 | 167 | 171 | ||
114 | 168 | def getLastErrorMessage(self): | 172 | def getLastErrorMessage(self): |
115 | 169 | 173 | ||
116 | === modified file 'lib/lp/bugs/tests/test_bugwatch.py' | |||
117 | --- lib/lp/bugs/tests/test_bugwatch.py 2009-06-25 00:40:31 +0000 | |||
118 | +++ lib/lp/bugs/tests/test_bugwatch.py 2009-12-15 17:34:15 +0000 | |||
119 | @@ -11,13 +11,15 @@ | |||
120 | 11 | 11 | ||
121 | 12 | from zope.component import getUtility | 12 | from zope.component import getUtility |
122 | 13 | 13 | ||
124 | 14 | from canonical.launchpad.ftests import login, ANONYMOUS | 14 | from canonical.launchpad.webapp import urlsplit |
125 | 15 | from canonical.testing import ( | ||
126 | 16 | DatabaseFunctionalLayer, LaunchpadFunctionalLayer) | ||
127 | 17 | |||
128 | 15 | from lp.bugs.interfaces.bugtracker import BugTrackerType, IBugTrackerSet | 18 | from lp.bugs.interfaces.bugtracker import BugTrackerType, IBugTrackerSet |
129 | 16 | from lp.bugs.interfaces.bugwatch import ( | 19 | from lp.bugs.interfaces.bugwatch import ( |
130 | 17 | IBugWatchSet, NoBugTrackerFound, UnrecognizedBugTrackerURL) | 20 | IBugWatchSet, NoBugTrackerFound, UnrecognizedBugTrackerURL) |
131 | 18 | from lp.registry.interfaces.person import IPersonSet | 21 | from lp.registry.interfaces.person import IPersonSet |
134 | 19 | from canonical.launchpad.webapp import urlsplit | 22 | from lp.testing import ANONYMOUS, TestCaseWithFactory, login |
133 | 20 | from canonical.testing import LaunchpadFunctionalLayer | ||
135 | 21 | 23 | ||
136 | 22 | 24 | ||
137 | 23 | class ExtractBugTrackerAndBugTestBase(unittest.TestCase): | 25 | class ExtractBugTrackerAndBugTestBase(unittest.TestCase): |
138 | @@ -309,6 +311,20 @@ | |||
139 | 309 | bug_id = '12345' | 311 | bug_id = '12345' |
140 | 310 | 312 | ||
141 | 311 | 313 | ||
142 | 314 | class TestBugWatchBugTasks(TestCaseWithFactory): | ||
143 | 315 | |||
144 | 316 | layer = DatabaseFunctionalLayer | ||
145 | 317 | |||
146 | 318 | def setUp(self): | ||
147 | 319 | super(TestBugWatchBugTasks, self).setUp('test@canonical.com') | ||
148 | 320 | self.bug_watch = self.factory.makeBugWatch() | ||
149 | 321 | |||
150 | 322 | def test_bugtasks(self): | ||
151 | 323 | # BugWatch.bugtasks is always a list. | ||
152 | 324 | self.assertIsInstance( | ||
153 | 325 | self.bug_watch.bugtasks, list) | ||
154 | 326 | |||
155 | 327 | |||
156 | 312 | def test_suite(): | 328 | def test_suite(): |
157 | 313 | suite = unittest.TestSuite() | 329 | suite = unittest.TestSuite() |
158 | 314 | suite.addTest(unittest.makeSuite(BugzillaExtractBugTrackerAndBugTest)) | 330 | suite.addTest(unittest.makeSuite(BugzillaExtractBugTrackerAndBugTest)) |
159 | @@ -331,6 +347,7 @@ | |||
160 | 331 | PHPProjectBugTrackerExtractBugTrackerAndBugTest)) | 347 | PHPProjectBugTrackerExtractBugTrackerAndBugTest)) |
161 | 332 | suite.addTest(unittest.makeSuite( | 348 | suite.addTest(unittest.makeSuite( |
162 | 333 | GoogleCodeBugTrackerExtractBugTrackerAndBugTest)) | 349 | GoogleCodeBugTrackerExtractBugTrackerAndBugTest)) |
163 | 350 | suite.addTest(unittest.makeSuite(TestBugWatchBugTasks)) | ||
164 | 334 | return suite | 351 | return suite |
165 | 335 | 352 | ||
166 | 336 | 353 |
Change BugWatch.bugtasks into a property that returns a list, instead of being a SQLMultipleJoin which returns a result set. This relates to:
https:/ /wiki.canonical .com/IncidentRe ports/2009- 12-14-Launchpad -Appserver- Outage
The long-lived query in the paste linked to from the report seems to have come from this bugtasks property. By changing it to immediately suck the results back from the DB we (gmb and I) hope to try and isolate the real issue, or at least narrow it down.