Merge lp:~mwhudson/launchpad/back-off-failing-imports-bug-413637 into lp:launchpad/db-devel
- back-off-failing-imports-bug-413637
- Merge into db-devel
Status: | Rejected | ||||
---|---|---|---|---|---|
Rejected by: | Michael Hudson-Doyle | ||||
Proposed branch: | lp:~mwhudson/launchpad/back-off-failing-imports-bug-413637 | ||||
Merge into: | lp:launchpad/db-devel | ||||
Diff against target: |
873 lines (+431/-102) 13 files modified
lib/canonical/buildd/debian/launchpad-buildd.init (+1/-1) lib/lp/buildmaster/interfaces/buildfarmjobbehavior.py (+1/-1) lib/lp/buildmaster/model/buildfarmjobbehavior.py (+94/-10) lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py (+80/-8) lib/lp/code/interfaces/codeimportjob.py (+7/-3) lib/lp/code/model/codeimportjob.py (+26/-21) lib/lp/code/model/recipebuilder.py (+11/-2) lib/lp/code/model/tests/test_codeimportjob.py (+25/-0) lib/lp/soyuz/doc/buildd-slavescanner.txt (+4/-3) lib/lp/testing/factory.py (+87/-40) lib/lp/translations/doc/translationtemplatesbuildbehavior.txt (+41/-12) lib/lp/translations/model/translationtemplatesbuildbehavior.py (+17/-1) lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py (+37/-0) |
||||
To merge this branch: | bzr merge lp:~mwhudson/launchpad/back-off-failing-imports-bug-413637 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Canonical Launchpad Engineering | Pending | ||
Review via email: mp+21408@code.launchpad.net |
Commit message
Description of the change
Hi there,
This small branch changes the code import system to exponentially back off on retrying code imports -- see the linked bug for more.
Currently it backs off so that it tries imports at 6, 12, 24 and 48 hours (for subversion) for a total of three and a bit days of trying before marking an import failed -- is that enough? It seems OK, though perhaps a bit on the low side, to me.
Cheers,
mwh
Jonathan Lange (jml) wrote : | # |
Michael Hudson-Doyle (mwhudson) wrote : | # |
Jonathan Lange wrote:
> On Mon, Mar 15, 2010 at 10:22 PM, Michael Hudson
> <email address hidden> wrote:
>> Michael Hudson has proposed merging lp:~mwhudson/launchpad/back-off-failing-imports-bug-413637 into lp:launchpad.
>>
>> Requested reviews:
>> Canonical Launchpad Engineering (launchpad)
>> Related bugs:
>> #413637 imports disabled too rapidly
>> https:/
>>
>>
>> Hi there,
>>
>> This small branch changes the code import system to exponentially back off on retrying code imports -- see the linked bug for more.
>>
>
> I think the branch is bigger than you intended. It's got a bunch of
> build farm job stuff in the diff.
Grar grar, it's proposed into db-devel of course.
I proposed again:
https:/
>> Currently it backs off so that it tries imports at 6, 12, 24 and 48 hours (for subversion) for a total of three and a bit days of trying before marking an import failed -- is that enough? It seems OK, though perhaps a bit on the low side, to me.
>>
>
> Sounds fine to me.
>
> ...
>> === modified file 'lib/lp/
>> --- lib/lp/
>> +++ lib/lp/
>> @@ -10,6 +10,8 @@
>> 'CodeImportJobW
>> ]
>>
>> +import datetime
>> +
>> from sqlobject import ForeignKey, IntCol, SQLObjectNotFound, StringCol
>>
>> from zope.component import getUtility
>> @@ -140,7 +142,7 @@
>>
>> implements(
>>
>> - def newJob(self, code_import, date_due=None):
>> + def newJob(self, code_import, interval=None):
>> """See `ICodeImportJob
>> assert code_import.
>> "Review status of %s is not REVIEWED: %s" % (
>> @@ -149,23 +151,22 @@
>> "Already associated to a CodeImportJob: %s" % (
>> code_import.
>>
>> + if interval is None:
>> + interval = code_import.
>> +
>> job = CodeImportJob(
>>
>> - if date_due is None:
>> - # Find the most recent CodeImportResult for this CodeImport. We
>> - # sort by date_created because we do not have an index on
>> - # date_job_started in the database, and that should give the same
>> - # sort order.
>> - most_recent_
>> - code_import=
>> + # Find the most recent CodeImportResult for this CodeImport. We
>> + # sort by date_created because we do not have an index on
>> + # date_job_started in the database, and that should give the same
>> + # sort order.
>> + most_recent_
>> + code_import=
>>
>
> Why aren't you using Storm, with its 'one()' notation for this?
Because all I did to this code was dedent it. I ...
Preview Diff
1 | === modified file 'lib/canonical/buildd/debian/launchpad-buildd.init' | |||
2 | --- lib/canonical/buildd/debian/launchpad-buildd.init 2009-12-16 00:16:24 +0000 | |||
3 | +++ lib/canonical/buildd/debian/launchpad-buildd.init 2010-03-15 22:22:20 +0000 | |||
4 | @@ -29,7 +29,7 @@ | |||
5 | 29 | CONF=$1 | 29 | CONF=$1 |
6 | 30 | PIDFILE="$PIDROOT"/"$CONF".pid | 30 | PIDFILE="$PIDROOT"/"$CONF".pid |
7 | 31 | LOGFILE="$LOGROOT"/"$CONF".log | 31 | LOGFILE="$LOGROOT"/"$CONF".log |
9 | 32 | su - buildd -c "BUILDD_SLAVE_CONFIG=$CONFROOT/$CONF PYTHONPATH=/usr/share/launchpad-buildd twistd --no_save --pidfile $PIDFILE --python $TACFILE --logfile $LOGFILE" | 32 | su - buildd -c "BUILDD_SLAVE_CONFIG=$CONFROOT/$CONF PYTHONPATH=/usr/share/launchpad-buildd twistd --no_save --pidfile $PIDFILE --python $TACFILE --logfile $LOGFILE --umask 022" |
10 | 33 | } | 33 | } |
11 | 34 | 34 | ||
12 | 35 | # | 35 | # |
13 | 36 | 36 | ||
14 | === modified file 'lib/lp/buildmaster/interfaces/buildfarmjobbehavior.py' | |||
15 | --- lib/lp/buildmaster/interfaces/buildfarmjobbehavior.py 2010-01-21 05:03:16 +0000 | |||
16 | +++ lib/lp/buildmaster/interfaces/buildfarmjobbehavior.py 2010-03-15 22:22:20 +0000 | |||
17 | @@ -1,4 +1,4 @@ | |||
19 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
20 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
21 | 3 | 3 | ||
22 | 4 | # pylint: disable-msg=E0211,E0213 | 4 | # pylint: disable-msg=E0211,E0213 |
23 | 5 | 5 | ||
24 | === modified file 'lib/lp/buildmaster/model/buildfarmjobbehavior.py' | |||
25 | --- lib/lp/buildmaster/model/buildfarmjobbehavior.py 2010-03-05 13:52:32 +0000 | |||
26 | +++ lib/lp/buildmaster/model/buildfarmjobbehavior.py 2010-03-15 22:22:20 +0000 | |||
27 | @@ -1,4 +1,4 @@ | |||
29 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
30 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
31 | 3 | 3 | ||
32 | 4 | # pylint: disable-msg=E0211,E0213 | 4 | # pylint: disable-msg=E0211,E0213 |
33 | @@ -17,17 +17,22 @@ | |||
34 | 17 | import xmlrpclib | 17 | import xmlrpclib |
35 | 18 | 18 | ||
36 | 19 | from sqlobject import SQLObjectNotFound | 19 | from sqlobject import SQLObjectNotFound |
37 | 20 | |||
38 | 20 | from zope.component import getUtility | 21 | from zope.component import getUtility |
39 | 21 | from zope.interface import implements | 22 | from zope.interface import implements |
41 | 22 | from zope.security.proxy import removeSecurityProxy | 23 | from zope.security.proxy import removeSecurityProxy, isinstance as zisinstance |
42 | 23 | 24 | ||
43 | 24 | from canonical import encoding | 25 | from canonical import encoding |
44 | 25 | from canonical.librarian.interfaces import ILibrarianClient | 26 | from canonical.librarian.interfaces import ILibrarianClient |
45 | 27 | |||
46 | 28 | from canonical.launchpad.webapp.interfaces import NotFoundError | ||
47 | 26 | from lp.buildmaster.interfaces.builder import CorruptBuildID | 29 | from lp.buildmaster.interfaces.builder import CorruptBuildID |
48 | 27 | from lp.buildmaster.interfaces.buildfarmjobbehavior import ( | 30 | from lp.buildmaster.interfaces.buildfarmjobbehavior import ( |
49 | 28 | BuildBehaviorMismatch, IBuildFarmJobBehavior) | 31 | BuildBehaviorMismatch, IBuildFarmJobBehavior) |
50 | 29 | from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet | 32 | from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet |
51 | 33 | from lp.buildmaster.model.buildqueue import BuildQueue | ||
52 | 30 | from lp.services.job.interfaces.job import JobStatus | 34 | from lp.services.job.interfaces.job import JobStatus |
53 | 35 | from lp.soyuz.interfaces.build import IBuildSet | ||
54 | 31 | 36 | ||
55 | 32 | 37 | ||
56 | 33 | class BuildFarmJobBehaviorBase: | 38 | class BuildFarmJobBehaviorBase: |
57 | @@ -59,22 +64,101 @@ | |||
58 | 59 | The default behavior is that we don't add any extra values.""" | 64 | The default behavior is that we don't add any extra values.""" |
59 | 60 | return {} | 65 | return {} |
60 | 61 | 66 | ||
61 | 67 | def _helpVerifyBuildIDComponent(self, raw_id, item_type, finder): | ||
62 | 68 | """Helper for verifying parts of a `BuildFarmJob` name. | ||
63 | 69 | |||
64 | 70 | Different `IBuildFarmJob` implementations can have different | ||
65 | 71 | ways of constructing their identifying names. The names are | ||
66 | 72 | produced by `IBuildFarmJob.getName` and verified by | ||
67 | 73 | `IBuildFarmJobBehavior.verifySlaveBuildID`. | ||
68 | 74 | |||
69 | 75 | This little helper makes it easier to verify an object id | ||
70 | 76 | embedded in that name, check that it's a valid number, and | ||
71 | 77 | retrieve the associated database object. | ||
72 | 78 | |||
73 | 79 | :param raw_id: An unverified id string as extracted from the | ||
74 | 80 | build name. The method will verify that it is a number, and | ||
75 | 81 | try to retrieve the associated object. | ||
76 | 82 | :param item_type: The type of object this id represents. Should | ||
77 | 83 | be a class. | ||
78 | 84 | :param finder: A function that, given an integral id, finds the | ||
79 | 85 | associated object of type `item_type`. | ||
80 | 86 | :raise CorruptBuildID: If `raw_id` is malformed in some way or | ||
81 | 87 | the associated `item_type` object is not found. | ||
82 | 88 | :return: An object that is an instance of `item_type`. | ||
83 | 89 | """ | ||
84 | 90 | type_name = item_type.__name__ | ||
85 | 91 | try: | ||
86 | 92 | numeric_id = int(raw_id) | ||
87 | 93 | except ValueError: | ||
88 | 94 | raise CorruptBuildID( | ||
89 | 95 | "%s ID is not a number: '%s'" % (type_name, raw_id)) | ||
90 | 96 | |||
91 | 97 | try: | ||
92 | 98 | item = finder(numeric_id) | ||
93 | 99 | except (NotFoundError, SQLObjectNotFound), reason: | ||
94 | 100 | raise CorruptBuildID( | ||
95 | 101 | "%s %d is not available: %s" % ( | ||
96 | 102 | type_name, numeric_id, reason)) | ||
97 | 103 | except Exception, reason: | ||
98 | 104 | raise CorruptBuildID( | ||
99 | 105 | "Error while looking up %s %d: %s" % ( | ||
100 | 106 | type_name, numeric_id, reason)) | ||
101 | 107 | |||
102 | 108 | if item is None: | ||
103 | 109 | raise CorruptBuildID("There is no %s with id %d." % ( | ||
104 | 110 | type_name, numeric_id)) | ||
105 | 111 | |||
106 | 112 | assert zisinstance(item, item_type), ( | ||
107 | 113 | "Looked for %s, but found %s." % (type_name, repr(item))) | ||
108 | 114 | |||
109 | 115 | return item | ||
110 | 116 | |||
111 | 117 | def getVerifiedBuild(self, raw_id): | ||
112 | 118 | """Verify and retrieve the `Build` component of a slave build id. | ||
113 | 119 | |||
114 | 120 | This does part of the verification for `verifySlaveBuildID`. | ||
115 | 121 | |||
116 | 122 | By default, a `BuildFarmJob` has an identifying name of the form | ||
117 | 123 | "b-q", where b is the id of its `Build` and q is the id of its | ||
118 | 124 | `BuildQueue` record. | ||
119 | 125 | |||
120 | 126 | Use `getVerifiedBuild` to verify the "b" part, and retrieve the | ||
121 | 127 | associated `Build`. | ||
122 | 128 | """ | ||
123 | 129 | # Avoid circular import. | ||
124 | 130 | from lp.soyuz.model.build import Build | ||
125 | 131 | |||
126 | 132 | return self._helpVerifyBuildIDComponent( | ||
127 | 133 | raw_id, Build, getUtility(IBuildSet).getByBuildID) | ||
128 | 134 | |||
129 | 135 | def getVerifiedBuildQueue(self, raw_id): | ||
130 | 136 | """Verify and retrieve the `BuildQueue` component of a slave build id. | ||
131 | 137 | |||
132 | 138 | This does part of the verification for `verifySlaveBuildID`. | ||
133 | 139 | |||
134 | 140 | By default, a `BuildFarmJob` has an identifying name of the form | ||
135 | 141 | "b-q", where b is the id of its `Build` and q is the id of its | ||
136 | 142 | `BuildQueue` record. | ||
137 | 143 | |||
138 | 144 | Use `getVerifiedBuildQueue` to verify the "q" part, and retrieve | ||
139 | 145 | the associated `BuildQueue` object. | ||
140 | 146 | """ | ||
141 | 147 | return self._helpVerifyBuildIDComponent( | ||
142 | 148 | raw_id, BuildQueue, getUtility(IBuildQueueSet).get) | ||
143 | 149 | |||
144 | 62 | def verifySlaveBuildID(self, slave_build_id): | 150 | def verifySlaveBuildID(self, slave_build_id): |
145 | 63 | """See `IBuildFarmJobBehavior`.""" | 151 | """See `IBuildFarmJobBehavior`.""" |
146 | 64 | # Extract information from the identifier. | 152 | # Extract information from the identifier. |
147 | 65 | try: | 153 | try: |
148 | 66 | build_id, queue_item_id = slave_build_id.split('-') | 154 | build_id, queue_item_id = slave_build_id.split('-') |
149 | 67 | build_id = int(build_id) | ||
150 | 68 | queue_item_id = int(queue_item_id) | ||
151 | 69 | except ValueError: | 155 | except ValueError: |
152 | 70 | raise CorruptBuildID('Malformed build ID') | 156 | raise CorruptBuildID('Malformed build ID') |
153 | 157 | |||
154 | 158 | build = self.getVerifiedBuild(build_id) | ||
155 | 159 | queue_item = self.getVerifiedBuildQueue(queue_item_id) | ||
156 | 71 | 160 | ||
163 | 72 | try: | 161 | if build != queue_item.specific_job.build: |
158 | 73 | queue_item = getUtility(IBuildQueueSet).get(queue_item_id) | ||
159 | 74 | # Check whether build and buildqueue are properly related. | ||
160 | 75 | except SQLObjectNotFound, reason: | ||
161 | 76 | raise CorruptBuildID(str(reason)) | ||
162 | 77 | if queue_item.specific_job.build.id != build_id: | ||
164 | 78 | raise CorruptBuildID('Job build entry mismatch') | 162 | raise CorruptBuildID('Job build entry mismatch') |
165 | 79 | 163 | ||
166 | 80 | def updateBuild(self, queueItem): | 164 | def updateBuild(self, queueItem): |
167 | 81 | 165 | ||
168 | === modified file 'lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py' | |||
169 | --- lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py 2010-03-06 04:57:40 +0000 | |||
170 | +++ lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py 2010-03-15 22:22:20 +0000 | |||
171 | @@ -3,10 +3,18 @@ | |||
172 | 3 | 3 | ||
173 | 4 | """Unit tests for BuildFarmJobBehaviorBase.""" | 4 | """Unit tests for BuildFarmJobBehaviorBase.""" |
174 | 5 | 5 | ||
176 | 6 | from unittest import TestCase, TestLoader | 6 | from unittest import TestLoader |
177 | 7 | |||
178 | 8 | from zope.component import getUtility | ||
179 | 9 | |||
180 | 10 | from canonical.testing.layers import ZopelessDatabaseLayer | ||
181 | 11 | from lp.testing import TestCaseWithFactory | ||
182 | 7 | 12 | ||
183 | 8 | from lp.buildmaster.interfaces.buildbase import BuildStatus | 13 | from lp.buildmaster.interfaces.buildbase import BuildStatus |
184 | 14 | from lp.buildmaster.interfaces.builder import CorruptBuildID | ||
185 | 9 | from lp.buildmaster.model.buildfarmjobbehavior import BuildFarmJobBehaviorBase | 15 | from lp.buildmaster.model.buildfarmjobbehavior import BuildFarmJobBehaviorBase |
186 | 16 | from lp.registry.interfaces.pocket import PackagePublishingPocket | ||
187 | 17 | from lp.soyuz.interfaces.processor import IProcessorFamilySet | ||
188 | 10 | 18 | ||
189 | 11 | 19 | ||
190 | 12 | class FakeBuildFarmJob: | 20 | class FakeBuildFarmJob: |
191 | @@ -14,27 +22,91 @@ | |||
192 | 14 | pass | 22 | pass |
193 | 15 | 23 | ||
194 | 16 | 24 | ||
196 | 17 | class TestBuildFarmJobBehaviorBase(TestCase): | 25 | class TestBuildFarmJobBehaviorBase(TestCaseWithFactory): |
197 | 18 | """Test very small, basic bits of BuildFarmJobBehaviorBase.""" | 26 | """Test very small, basic bits of BuildFarmJobBehaviorBase.""" |
198 | 19 | 27 | ||
201 | 20 | def setUp(self): | 28 | layer = ZopelessDatabaseLayer |
202 | 21 | self.behavior = BuildFarmJobBehaviorBase(FakeBuildFarmJob()) | 29 | |
203 | 30 | def _makeBehavior(self, buildfarmjob=None): | ||
204 | 31 | """Create a `BuildFarmJobBehaviorBase`.""" | ||
205 | 32 | if buildfarmjob is None: | ||
206 | 33 | buildfarmjob = FakeBuildFarmJob() | ||
207 | 34 | return BuildFarmJobBehaviorBase(buildfarmjob) | ||
208 | 35 | |||
209 | 36 | def _makeBuild(self): | ||
210 | 37 | """Create a `Build` object.""" | ||
211 | 38 | x86 = getUtility(IProcessorFamilySet).getByName('x86') | ||
212 | 39 | distroarchseries = self.factory.makeDistroArchSeries( | ||
213 | 40 | architecturetag='x86', processorfamily=x86) | ||
214 | 41 | distroseries = distroarchseries.distroseries | ||
215 | 42 | archive = self.factory.makeArchive( | ||
216 | 43 | distribution=distroseries.distribution) | ||
217 | 44 | pocket = PackagePublishingPocket.RELEASE | ||
218 | 45 | spr = self.factory.makeSourcePackageRelease( | ||
219 | 46 | distroseries=distroseries, archive=archive) | ||
220 | 47 | |||
221 | 48 | return spr.createBuild( | ||
222 | 49 | distroarchseries=distroarchseries, pocket=pocket, archive=archive) | ||
223 | 50 | |||
224 | 51 | def _makeBuildQueue(self): | ||
225 | 52 | """Create a `BuildQueue` object.""" | ||
226 | 53 | return self.factory.makeSourcePackageRecipeBuildJob() | ||
227 | 22 | 54 | ||
228 | 23 | def test_extractBuildStatus_baseline(self): | 55 | def test_extractBuildStatus_baseline(self): |
229 | 24 | # extractBuildStatus picks the name of the build status out of a | 56 | # extractBuildStatus picks the name of the build status out of a |
230 | 25 | # dict describing the slave's status. | 57 | # dict describing the slave's status. |
231 | 26 | slave_status = {'build_status': 'BuildStatus.BUILDING'} | 58 | slave_status = {'build_status': 'BuildStatus.BUILDING'} |
232 | 59 | behavior = self._makeBehavior() | ||
233 | 27 | self.assertEqual( | 60 | self.assertEqual( |
234 | 28 | BuildStatus.BUILDING.name, | 61 | BuildStatus.BUILDING.name, |
236 | 29 | self.behavior.extractBuildStatus(slave_status)) | 62 | behavior.extractBuildStatus(slave_status)) |
237 | 30 | 63 | ||
238 | 31 | def test_extractBuildStatus_malformed(self): | 64 | def test_extractBuildStatus_malformed(self): |
239 | 32 | # extractBuildStatus errors out when the status string is not | 65 | # extractBuildStatus errors out when the status string is not |
240 | 33 | # of the form it expects. | 66 | # of the form it expects. |
241 | 34 | slave_status = {'build_status': 'BUILDING'} | 67 | slave_status = {'build_status': 'BUILDING'} |
245 | 35 | self.assertRaises( | 68 | behavior = self._makeBehavior() |
246 | 36 | AssertionError, | 69 | self.assertRaises( |
247 | 37 | self.behavior.extractBuildStatus, slave_status) | 70 | AssertionError, behavior.extractBuildStatus, slave_status) |
248 | 71 | |||
249 | 72 | def test_getVerifiedBuild_success(self): | ||
250 | 73 | build = self._makeBuild() | ||
251 | 74 | behavior = self._makeBehavior() | ||
252 | 75 | raw_id = str(build.id) | ||
253 | 76 | |||
254 | 77 | self.assertEqual(build, behavior.getVerifiedBuild(raw_id)) | ||
255 | 78 | |||
256 | 79 | def test_getVerifiedBuild_malformed(self): | ||
257 | 80 | behavior = self._makeBehavior() | ||
258 | 81 | self.assertRaises(CorruptBuildID, behavior.getVerifiedBuild, 'hi!') | ||
259 | 82 | |||
260 | 83 | def test_getVerifiedBuild_notfound(self): | ||
261 | 84 | build = self._makeBuild() | ||
262 | 85 | behavior = self._makeBehavior() | ||
263 | 86 | nonexistent_id = str(build.id + 99) | ||
264 | 87 | |||
265 | 88 | self.assertRaises( | ||
266 | 89 | CorruptBuildID, behavior.getVerifiedBuild, nonexistent_id) | ||
267 | 90 | |||
268 | 91 | def test_getVerifiedBuildQueue_success(self): | ||
269 | 92 | buildqueue = self._makeBuildQueue() | ||
270 | 93 | behavior = self._makeBehavior() | ||
271 | 94 | raw_id = str(buildqueue.id) | ||
272 | 95 | |||
273 | 96 | self.assertEqual(buildqueue, behavior.getVerifiedBuildQueue(raw_id)) | ||
274 | 97 | |||
275 | 98 | def test_getVerifiedBuildQueue_malformed(self): | ||
276 | 99 | behavior = self._makeBehavior() | ||
277 | 100 | self.assertRaises( | ||
278 | 101 | CorruptBuildID, behavior.getVerifiedBuildQueue, 'bye!') | ||
279 | 102 | |||
280 | 103 | def test_getVerifiedBuildQueue_notfound(self): | ||
281 | 104 | buildqueue = self._makeBuildQueue() | ||
282 | 105 | behavior = self._makeBehavior() | ||
283 | 106 | nonexistent_id = str(buildqueue.id + 99) | ||
284 | 107 | |||
285 | 108 | self.assertRaises( | ||
286 | 109 | CorruptBuildID, behavior.getVerifiedBuildQueue, nonexistent_id) | ||
287 | 38 | 110 | ||
288 | 39 | 111 | ||
289 | 40 | def test_suite(): | 112 | def test_suite(): |
290 | 41 | 113 | ||
291 | === modified file 'lib/lp/code/interfaces/codeimportjob.py' | |||
292 | --- lib/lp/code/interfaces/codeimportjob.py 2010-02-22 08:10:03 +0000 | |||
293 | +++ lib/lp/code/interfaces/codeimportjob.py 2010-03-15 22:22:20 +0000 | |||
294 | @@ -16,6 +16,8 @@ | |||
295 | 16 | 'ICodeImportJobWorkflow', | 16 | 'ICodeImportJobWorkflow', |
296 | 17 | ] | 17 | ] |
297 | 18 | 18 | ||
298 | 19 | import datetime | ||
299 | 20 | |||
300 | 19 | from zope.interface import Interface | 21 | from zope.interface import Interface |
301 | 20 | from zope.schema import Choice, Datetime, Int, Object, Text | 22 | from zope.schema import Choice, Datetime, Int, Object, Text |
302 | 21 | 23 | ||
303 | @@ -137,7 +139,7 @@ | |||
304 | 137 | class ICodeImportJobWorkflow(Interface): | 139 | class ICodeImportJobWorkflow(Interface): |
305 | 138 | """Utility to manage `CodeImportJob` objects through their life cycle.""" | 140 | """Utility to manage `CodeImportJob` objects through their life cycle.""" |
306 | 139 | 141 | ||
308 | 140 | def newJob(code_import): | 142 | def newJob(code_import, interval=datetime.timedelta(0)): |
309 | 141 | """Create a `CodeImportJob` associated with a reviewed `CodeImport`. | 143 | """Create a `CodeImportJob` associated with a reviewed `CodeImport`. |
310 | 142 | 144 | ||
311 | 143 | Call this method from `CodeImport.updateFromData` when the | 145 | Call this method from `CodeImport.updateFromData` when the |
312 | @@ -218,9 +220,11 @@ | |||
313 | 218 | display for diagnostics. May be None. | 220 | display for diagnostics. May be None. |
314 | 219 | :precondition: `import_job`.state == RUNNING. | 221 | :precondition: `import_job`.state == RUNNING. |
315 | 220 | :postcondition: `import_job` is deleted. | 222 | :postcondition: `import_job` is deleted. |
317 | 221 | :postcondition: `code_import.import_job` is not None. | 223 | :postcondition: `code_import.import_job` is not None unless the job |
318 | 224 | has failed more than consecutive_failure_limit times in a row. | ||
319 | 222 | :postcondition: `code_import.import_job.date_due` is | 225 | :postcondition: `code_import.import_job.date_due` is |
321 | 223 | import_job.date_due + code_import.effective_update_interval`. | 226 | import_job.date_due + code_import.effective_update_interval`, with |
322 | 227 | scaling to retry failing imports less often. | ||
323 | 224 | :postcondition: A `CodeImportResult` was created. | 228 | :postcondition: A `CodeImportResult` was created. |
324 | 225 | :postcondition: A FINISH `CodeImportEvent` was created. | 229 | :postcondition: A FINISH `CodeImportEvent` was created. |
325 | 226 | """ | 230 | """ |
326 | 227 | 231 | ||
327 | === modified file 'lib/lp/code/model/codeimportjob.py' | |||
328 | --- lib/lp/code/model/codeimportjob.py 2010-02-24 10:18:16 +0000 | |||
329 | +++ lib/lp/code/model/codeimportjob.py 2010-03-15 22:22:20 +0000 | |||
330 | @@ -10,6 +10,8 @@ | |||
331 | 10 | 'CodeImportJobWorkflow', | 10 | 'CodeImportJobWorkflow', |
332 | 11 | ] | 11 | ] |
333 | 12 | 12 | ||
334 | 13 | import datetime | ||
335 | 14 | |||
336 | 13 | from sqlobject import ForeignKey, IntCol, SQLObjectNotFound, StringCol | 15 | from sqlobject import ForeignKey, IntCol, SQLObjectNotFound, StringCol |
337 | 14 | 16 | ||
338 | 15 | from zope.component import getUtility | 17 | from zope.component import getUtility |
339 | @@ -140,7 +142,7 @@ | |||
340 | 140 | 142 | ||
341 | 141 | implements(ICodeImportJobWorkflow) | 143 | implements(ICodeImportJobWorkflow) |
342 | 142 | 144 | ||
344 | 143 | def newJob(self, code_import, date_due=None): | 145 | def newJob(self, code_import, interval=None): |
345 | 144 | """See `ICodeImportJobWorkflow`.""" | 146 | """See `ICodeImportJobWorkflow`.""" |
346 | 145 | assert code_import.review_status == CodeImportReviewStatus.REVIEWED, ( | 147 | assert code_import.review_status == CodeImportReviewStatus.REVIEWED, ( |
347 | 146 | "Review status of %s is not REVIEWED: %s" % ( | 148 | "Review status of %s is not REVIEWED: %s" % ( |
348 | @@ -149,23 +151,22 @@ | |||
349 | 149 | "Already associated to a CodeImportJob: %s" % ( | 151 | "Already associated to a CodeImportJob: %s" % ( |
350 | 150 | code_import.branch.unique_name)) | 152 | code_import.branch.unique_name)) |
351 | 151 | 153 | ||
352 | 154 | if interval is None: | ||
353 | 155 | interval = code_import.effective_update_interval | ||
354 | 156 | |||
355 | 152 | job = CodeImportJob(code_import=code_import, date_due=UTC_NOW) | 157 | job = CodeImportJob(code_import=code_import, date_due=UTC_NOW) |
356 | 153 | 158 | ||
364 | 154 | if date_due is None: | 159 | # Find the most recent CodeImportResult for this CodeImport. We |
365 | 155 | # Find the most recent CodeImportResult for this CodeImport. We | 160 | # sort by date_created because we do not have an index on |
366 | 156 | # sort by date_created because we do not have an index on | 161 | # date_job_started in the database, and that should give the same |
367 | 157 | # date_job_started in the database, and that should give the same | 162 | # sort order. |
368 | 158 | # sort order. | 163 | most_recent_result_list = list(CodeImportResult.selectBy( |
369 | 159 | most_recent_result_list = list(CodeImportResult.selectBy( | 164 | code_import=code_import).orderBy(['-date_created']).limit(1)) |
363 | 160 | code_import=code_import).orderBy(['-date_created']).limit(1)) | ||
370 | 161 | 165 | ||
378 | 162 | if len(most_recent_result_list) != 0: | 166 | if len(most_recent_result_list) != 0: |
379 | 163 | [most_recent_result] = most_recent_result_list | 167 | [most_recent_result] = most_recent_result_list |
380 | 164 | interval = code_import.effective_update_interval | 168 | date_due = most_recent_result.date_job_started + interval |
381 | 165 | date_due = most_recent_result.date_job_started + interval | 169 | job.date_due = max(job.date_due, date_due) |
375 | 166 | job.date_due = max(job.date_due, date_due) | ||
376 | 167 | else: | ||
377 | 168 | job.date_due = date_due | ||
382 | 169 | 170 | ||
383 | 170 | return job | 171 | return job |
384 | 171 | 172 | ||
385 | @@ -281,15 +282,19 @@ | |||
386 | 281 | # If the import has failed too many times in a row, mark it as | 282 | # If the import has failed too many times in a row, mark it as |
387 | 282 | # FAILING. | 283 | # FAILING. |
388 | 283 | failure_limit = config.codeimport.consecutive_failure_limit | 284 | failure_limit = config.codeimport.consecutive_failure_limit |
390 | 284 | if code_import.consecutive_failure_count >= failure_limit: | 285 | failure_count = code_import.consecutive_failure_count |
391 | 286 | if failure_count >= failure_limit: | ||
392 | 285 | code_import.updateFromData( | 287 | code_import.updateFromData( |
393 | 286 | dict(review_status=CodeImportReviewStatus.FAILING), None) | 288 | dict(review_status=CodeImportReviewStatus.FAILING), None) |
394 | 289 | elif status == CodeImportResultStatus.SUCCESS_PARTIAL: | ||
395 | 290 | interval = datetime.timedelta(0) | ||
396 | 291 | elif failure_count > 0: | ||
397 | 292 | interval = code_import.effective_update_interval * (2**(failure_count - 1)) | ||
398 | 293 | else: | ||
399 | 294 | interval = code_import.effective_update_interval | ||
400 | 287 | # Only start a new one if the import is still in the REVIEWED state. | 295 | # Only start a new one if the import is still in the REVIEWED state. |
401 | 288 | if code_import.review_status == CodeImportReviewStatus.REVIEWED: | 296 | if code_import.review_status == CodeImportReviewStatus.REVIEWED: |
406 | 289 | extra = {} | 297 | self.newJob(code_import, interval=interval) |
403 | 290 | if status == CodeImportResultStatus.SUCCESS_PARTIAL: | ||
404 | 291 | extra['date_due'] = UTC_NOW | ||
405 | 292 | self.newJob(code_import, **extra) | ||
407 | 293 | # If the status was successful, update date_last_successful. | 298 | # If the status was successful, update date_last_successful. |
408 | 294 | if status in [CodeImportResultStatus.SUCCESS, | 299 | if status in [CodeImportResultStatus.SUCCESS, |
409 | 295 | CodeImportResultStatus.SUCCESS_NOCHANGE]: | 300 | CodeImportResultStatus.SUCCESS_NOCHANGE]: |
410 | @@ -321,7 +326,7 @@ | |||
411 | 321 | import_job, CodeImportResultStatus.RECLAIMED, None) | 326 | import_job, CodeImportResultStatus.RECLAIMED, None) |
412 | 322 | # 3) | 327 | # 3) |
413 | 323 | if code_import.review_status == CodeImportReviewStatus.REVIEWED: | 328 | if code_import.review_status == CodeImportReviewStatus.REVIEWED: |
415 | 324 | self.newJob(code_import, UTC_NOW) | 329 | self.newJob(code_import, datetime.timedelta(0)) |
416 | 325 | # 4) | 330 | # 4) |
417 | 326 | getUtility(ICodeImportEventSet).newReclaim( | 331 | getUtility(ICodeImportEventSet).newReclaim( |
418 | 327 | code_import, machine, job_id) | 332 | code_import, machine, job_id) |
419 | 328 | 333 | ||
420 | === modified file 'lib/lp/code/model/recipebuilder.py' | |||
421 | --- lib/lp/code/model/recipebuilder.py 2010-02-05 15:06:28 +0000 | |||
422 | +++ lib/lp/code/model/recipebuilder.py 2010-03-15 22:22:20 +0000 | |||
423 | @@ -8,7 +8,7 @@ | |||
424 | 8 | 'RecipeBuildBehavior', | 8 | 'RecipeBuildBehavior', |
425 | 9 | ] | 9 | ] |
426 | 10 | 10 | ||
428 | 11 | from zope.component import adapts | 11 | from zope.component import adapts, getUtility |
429 | 12 | from zope.interface import implements | 12 | from zope.interface import implements |
430 | 13 | 13 | ||
431 | 14 | from lp.archiveuploader.permission import check_upload_to_pocket | 14 | from lp.archiveuploader.permission import check_upload_to_pocket |
432 | @@ -18,7 +18,8 @@ | |||
433 | 18 | from lp.buildmaster.model.buildfarmjobbehavior import ( | 18 | from lp.buildmaster.model.buildfarmjobbehavior import ( |
434 | 19 | BuildFarmJobBehaviorBase) | 19 | BuildFarmJobBehaviorBase) |
435 | 20 | from lp.code.interfaces.sourcepackagerecipebuild import ( | 20 | from lp.code.interfaces.sourcepackagerecipebuild import ( |
437 | 21 | ISourcePackageRecipeBuildJob) | 21 | ISourcePackageRecipeBuildJob, ISourcePackageRecipeBuildSource) |
438 | 22 | from lp.code.model.sourcepackagerecipebuild import SourcePackageRecipeBuild | ||
439 | 22 | from lp.registry.interfaces.pocket import PackagePublishingPocket | 23 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
440 | 23 | from lp.soyuz.adapters.archivedependencies import ( | 24 | from lp.soyuz.adapters.archivedependencies import ( |
441 | 24 | get_primary_current_component, get_sources_list_for_building) | 25 | get_primary_current_component, get_sources_list_for_building) |
442 | @@ -168,3 +169,11 @@ | |||
443 | 168 | extra_info['logtail'] = raw_slave_status[2] | 169 | extra_info['logtail'] = raw_slave_status[2] |
444 | 169 | 170 | ||
445 | 170 | return extra_info | 171 | return extra_info |
446 | 172 | |||
447 | 173 | def getVerifiedBuild(self, raw_id): | ||
448 | 174 | """See `IBuildFarmJobBehavior`.""" | ||
449 | 175 | # This type of job has a build that is of type BuildBase but not | ||
450 | 176 | # actually a Build. | ||
451 | 177 | return self._helpVerifyBuildIDComponent( | ||
452 | 178 | raw_id, SourcePackageRecipeBuild, | ||
453 | 179 | getUtility(ISourcePackageRecipeBuildSource).getById) | ||
454 | 171 | 180 | ||
455 | === modified file 'lib/lp/code/model/tests/test_codeimportjob.py' | |||
456 | --- lib/lp/code/model/tests/test_codeimportjob.py 2010-03-14 20:15:34 +0000 | |||
457 | +++ lib/lp/code/model/tests/test_codeimportjob.py 2010-03-15 22:22:20 +0000 | |||
458 | @@ -778,6 +778,31 @@ | |||
459 | 778 | self.assertEqual(new_job.machine, None) | 778 | self.assertEqual(new_job.machine, None) |
460 | 779 | self.assertSqlAttributeEqualsDate(new_job, 'date_due', UTC_NOW) | 779 | self.assertSqlAttributeEqualsDate(new_job, 'date_due', UTC_NOW) |
461 | 780 | 780 | ||
462 | 781 | def test_failures_back_off(self): | ||
463 | 782 | # We wait for longer and longer between retrying failing imports, to | ||
464 | 783 | # make it less likely that an import is marked failing just because | ||
465 | 784 | # someone's DNS went down for a day. | ||
466 | 785 | running_job = self.makeRunningJob() | ||
467 | 786 | intervals = [] | ||
468 | 787 | interval = running_job.code_import.effective_update_interval | ||
469 | 788 | expected_intervals = [] | ||
470 | 789 | for i in range(config.codeimport.consecutive_failure_limit - 1): | ||
471 | 790 | expected_intervals.append(interval) | ||
472 | 791 | interval *= 2 | ||
473 | 792 | # Fail an import a bunch of times and record how far in the future the | ||
474 | 793 | # next job was scheduled. | ||
475 | 794 | for i in range(config.codeimport.consecutive_failure_limit - 1): | ||
476 | 795 | code_import = running_job.code_import | ||
477 | 796 | getUtility(ICodeImportJobWorkflow).finishJob( | ||
478 | 797 | running_job, CodeImportResultStatus.FAILURE, None) | ||
479 | 798 | intervals.append( | ||
480 | 799 | code_import.import_job.date_due - | ||
481 | 800 | code_import.results[-1].date_job_started) | ||
482 | 801 | running_job = code_import.import_job | ||
483 | 802 | getUtility(ICodeImportJobWorkflow).startJob( | ||
484 | 803 | running_job, self.machine) | ||
485 | 804 | self.assertEqual(expected_intervals, intervals) | ||
486 | 805 | |||
487 | 781 | def test_doesntCreateNewJobIfCodeImportNotReviewed(self): | 806 | def test_doesntCreateNewJobIfCodeImportNotReviewed(self): |
488 | 782 | # finishJob() creates a new CodeImportJob for the given CodeImport, | 807 | # finishJob() creates a new CodeImportJob for the given CodeImport, |
489 | 783 | # unless the CodeImport has been suspended or marked invalid. | 808 | # unless the CodeImport has been suspended or marked invalid. |
490 | 784 | 809 | ||
491 | === modified file 'lib/lp/soyuz/doc/buildd-slavescanner.txt' | |||
492 | --- lib/lp/soyuz/doc/buildd-slavescanner.txt 2010-03-10 12:50:18 +0000 | |||
493 | +++ lib/lp/soyuz/doc/buildd-slavescanner.txt 2010-03-15 22:22:20 +0000 | |||
494 | @@ -95,15 +95,16 @@ | |||
495 | 95 | >>> lostbuilding_builder = MockBuilder( | 95 | >>> lostbuilding_builder = MockBuilder( |
496 | 96 | ... 'Lost Building Slave', LostBuildingSlave()) | 96 | ... 'Lost Building Slave', LostBuildingSlave()) |
497 | 97 | >>> buildergroup.rescueBuilderIfLost(lostbuilding_builder) | 97 | >>> buildergroup.rescueBuilderIfLost(lostbuilding_builder) |
500 | 98 | WARNING:root:Builder 'Lost Building Slave' rescued from | 98 | WARNING:root:Builder 'Lost Building Slave' rescued from '1000-10000': |
501 | 99 | '1000-10000': 'Object not found' | 99 | 'Build 1000 is not available: 'Object not found'' |
502 | 100 | 100 | ||
503 | 101 | Then a lost slave in status 'WAITING': | 101 | Then a lost slave in status 'WAITING': |
504 | 102 | 102 | ||
505 | 103 | >>> lostwaiting_builder = MockBuilder( | 103 | >>> lostwaiting_builder = MockBuilder( |
506 | 104 | ... 'Lost Waiting Slave', LostWaitingSlave()) | 104 | ... 'Lost Waiting Slave', LostWaitingSlave()) |
507 | 105 | >>> buildergroup.rescueBuilderIfLost(lostwaiting_builder) | 105 | >>> buildergroup.rescueBuilderIfLost(lostwaiting_builder) |
509 | 106 | WARNING:root:Builder 'Lost Waiting Slave' rescued from '1000-10000': 'Object not found' | 106 | WARNING:root:Builder 'Lost Waiting Slave' rescued from '1000-10000': |
510 | 107 | 'Build 1000 is not available: 'Object not found'' | ||
511 | 107 | 108 | ||
512 | 108 | Both got rescued, as expected. | 109 | Both got rescued, as expected. |
513 | 109 | 110 | ||
514 | 110 | 111 | ||
515 | === modified file 'lib/lp/testing/factory.py' | |||
516 | --- lib/lp/testing/factory.py 2010-03-11 20:54:36 +0000 | |||
517 | +++ lib/lp/testing/factory.py 2010-03-15 22:22:20 +0000 | |||
518 | @@ -2008,6 +2008,81 @@ | |||
519 | 2008 | def getAnySourcePackageUrgency(self): | 2008 | def getAnySourcePackageUrgency(self): |
520 | 2009 | return SourcePackageUrgency.MEDIUM | 2009 | return SourcePackageUrgency.MEDIUM |
521 | 2010 | 2010 | ||
522 | 2011 | def makeSourcePackageRelease(self, archive=None, sourcepackagename=None, | ||
523 | 2012 | distroseries=None, maintainer=None, | ||
524 | 2013 | creator=None, component=None, section=None, | ||
525 | 2014 | urgency=None, version=None, | ||
526 | 2015 | builddepends=None, builddependsindep=None, | ||
527 | 2016 | build_conflicts=None, | ||
528 | 2017 | build_conflicts_indep=None, | ||
529 | 2018 | architecturehintlist='all', | ||
530 | 2019 | dsc_maintainer_rfc822=None, | ||
531 | 2020 | dsc_standards_version='3.6.2', | ||
532 | 2021 | dsc_format='1.0', dsc_binaries='foo-bin', | ||
533 | 2022 | date_uploaded=UTC_NOW): | ||
534 | 2023 | """Make a `SourcePackageRelease`.""" | ||
535 | 2024 | if distroseries is None: | ||
536 | 2025 | if archive is None: | ||
537 | 2026 | distribution = None | ||
538 | 2027 | else: | ||
539 | 2028 | distribution = archive.distribution | ||
540 | 2029 | distroseries = self.makeDistroRelease(distribution=distribution) | ||
541 | 2030 | |||
542 | 2031 | if archive is None: | ||
543 | 2032 | archive = self.makeArchive( | ||
544 | 2033 | distribution=distroseries.distribution, | ||
545 | 2034 | purpose=ArchivePurpose.PRIMARY) | ||
546 | 2035 | |||
547 | 2036 | if sourcepackagename is None: | ||
548 | 2037 | sourcepackagename = self.makeSourcePackageName() | ||
549 | 2038 | |||
550 | 2039 | if component is None: | ||
551 | 2040 | component = self.makeComponent() | ||
552 | 2041 | |||
553 | 2042 | if urgency is None: | ||
554 | 2043 | urgency = self.getAnySourcePackageUrgency() | ||
555 | 2044 | |||
556 | 2045 | if section is None: | ||
557 | 2046 | section = self.getUniqueString('section') | ||
558 | 2047 | section = getUtility(ISectionSet).ensure(section) | ||
559 | 2048 | |||
560 | 2049 | if maintainer is None: | ||
561 | 2050 | maintainer = self.makePerson() | ||
562 | 2051 | |||
563 | 2052 | maintainer_email = '%s <%s>' % ( | ||
564 | 2053 | maintainer.displayname, | ||
565 | 2054 | maintainer.preferredemail.email) | ||
566 | 2055 | |||
567 | 2056 | if creator is None: | ||
568 | 2057 | creator = self.makePerson() | ||
569 | 2058 | |||
570 | 2059 | if version is None: | ||
571 | 2060 | version = self.getUniqueString('version') | ||
572 | 2061 | |||
573 | 2062 | return distroseries.createUploadedSourcePackageRelease( | ||
574 | 2063 | sourcepackagename=sourcepackagename, | ||
575 | 2064 | maintainer=maintainer, | ||
576 | 2065 | creator=creator, | ||
577 | 2066 | component=component, | ||
578 | 2067 | section=section, | ||
579 | 2068 | urgency=urgency, | ||
580 | 2069 | version=version, | ||
581 | 2070 | builddepends=builddepends, | ||
582 | 2071 | builddependsindep=builddependsindep, | ||
583 | 2072 | build_conflicts=build_conflicts, | ||
584 | 2073 | build_conflicts_indep=build_conflicts_indep, | ||
585 | 2074 | architecturehintlist=architecturehintlist, | ||
586 | 2075 | changelog_entry=None, | ||
587 | 2076 | dsc=None, | ||
588 | 2077 | copyright=self.getUniqueString(), | ||
589 | 2078 | dscsigningkey=None, | ||
590 | 2079 | dsc_maintainer_rfc822=maintainer_email, | ||
591 | 2080 | dsc_standards_version=dsc_standards_version, | ||
592 | 2081 | dsc_format=dsc_format, | ||
593 | 2082 | dsc_binaries=dsc_binaries, | ||
594 | 2083 | archive=archive, | ||
595 | 2084 | dateuploaded=date_uploaded) | ||
596 | 2085 | |||
597 | 2011 | def makeSourcePackagePublishingHistory(self, sourcepackagename=None, | 2086 | def makeSourcePackagePublishingHistory(self, sourcepackagename=None, |
598 | 2012 | distroseries=None, maintainer=None, | 2087 | distroseries=None, maintainer=None, |
599 | 2013 | creator=None, component=None, | 2088 | creator=None, component=None, |
600 | @@ -2027,54 +2102,31 @@ | |||
601 | 2027 | dsc_format='1.0', | 2102 | dsc_format='1.0', |
602 | 2028 | dsc_binaries='foo-bin', | 2103 | dsc_binaries='foo-bin', |
603 | 2029 | ): | 2104 | ): |
608 | 2030 | if sourcepackagename is None: | 2105 | """Make a `SourcePackagePublishingHistory`.""" |
605 | 2031 | sourcepackagename = self.makeSourcePackageName() | ||
606 | 2032 | spn = sourcepackagename | ||
607 | 2033 | |||
609 | 2034 | if distroseries is None: | 2106 | if distroseries is None: |
611 | 2035 | distroseries = self.makeDistroRelease() | 2107 | if archive is None: |
612 | 2108 | distribution = None | ||
613 | 2109 | else: | ||
614 | 2110 | distribution = archive.distribution | ||
615 | 2111 | distroseries = self.makeDistroRelease(distribution=distribution) | ||
616 | 2036 | 2112 | ||
617 | 2037 | if archive is None: | 2113 | if archive is None: |
618 | 2038 | archive = self.makeArchive( | 2114 | archive = self.makeArchive( |
619 | 2039 | distribution=distroseries.distribution, | 2115 | distribution=distroseries.distribution, |
620 | 2040 | purpose=ArchivePurpose.PRIMARY) | 2116 | purpose=ArchivePurpose.PRIMARY) |
621 | 2041 | 2117 | ||
622 | 2042 | if component is None: | ||
623 | 2043 | component = self.makeComponent() | ||
624 | 2044 | |||
625 | 2045 | if pocket is None: | 2118 | if pocket is None: |
626 | 2046 | pocket = self.getAnyPocket() | 2119 | pocket = self.getAnyPocket() |
627 | 2047 | 2120 | ||
628 | 2048 | if status is None: | 2121 | if status is None: |
629 | 2049 | status = PackagePublishingStatus.PENDING | 2122 | status = PackagePublishingStatus.PENDING |
630 | 2050 | 2123 | ||
655 | 2051 | if urgency is None: | 2124 | spr = self.makeSourcePackageRelease( |
656 | 2052 | urgency = self.getAnySourcePackageUrgency() | 2125 | archive=archive, |
657 | 2053 | 2126 | sourcepackagename=sourcepackagename, | |
658 | 2054 | if section is None: | 2127 | distroseries=distroseries, |
635 | 2055 | section = self.getUniqueString('section') | ||
636 | 2056 | section = getUtility(ISectionSet).ensure(section) | ||
637 | 2057 | |||
638 | 2058 | if maintainer is None: | ||
639 | 2059 | maintainer = self.makePerson() | ||
640 | 2060 | |||
641 | 2061 | maintainer_email = '%s <%s>' % ( | ||
642 | 2062 | maintainer.displayname, | ||
643 | 2063 | maintainer.preferredemail.email) | ||
644 | 2064 | |||
645 | 2065 | if creator is None: | ||
646 | 2066 | creator = self.makePerson() | ||
647 | 2067 | |||
648 | 2068 | if version is None: | ||
649 | 2069 | version = self.getUniqueString('version') | ||
650 | 2070 | |||
651 | 2071 | gpg_key = self.makeGPGKey(creator) | ||
652 | 2072 | |||
653 | 2073 | spr = distroseries.createUploadedSourcePackageRelease( | ||
654 | 2074 | sourcepackagename=spn, | ||
659 | 2075 | maintainer=maintainer, | 2128 | maintainer=maintainer, |
662 | 2076 | creator=creator, | 2129 | creator=creator, component=component, |
661 | 2077 | component=component, | ||
663 | 2078 | section=section, | 2130 | section=section, |
664 | 2079 | urgency=urgency, | 2131 | urgency=urgency, |
665 | 2080 | version=version, | 2132 | version=version, |
666 | @@ -2083,15 +2135,10 @@ | |||
667 | 2083 | build_conflicts=build_conflicts, | 2135 | build_conflicts=build_conflicts, |
668 | 2084 | build_conflicts_indep=build_conflicts_indep, | 2136 | build_conflicts_indep=build_conflicts_indep, |
669 | 2085 | architecturehintlist=architecturehintlist, | 2137 | architecturehintlist=architecturehintlist, |
670 | 2086 | changelog_entry=None, | ||
671 | 2087 | dsc=None, | ||
672 | 2088 | copyright=self.getUniqueString(), | ||
673 | 2089 | dscsigningkey=gpg_key, | ||
674 | 2090 | dsc_maintainer_rfc822=maintainer_email, | ||
675 | 2091 | dsc_standards_version=dsc_standards_version, | 2138 | dsc_standards_version=dsc_standards_version, |
676 | 2092 | dsc_format=dsc_format, | 2139 | dsc_format=dsc_format, |
677 | 2093 | dsc_binaries=dsc_binaries, | 2140 | dsc_binaries=dsc_binaries, |
679 | 2094 | archive=archive, dateuploaded=date_uploaded) | 2141 | date_uploaded=date_uploaded) |
680 | 2095 | 2142 | ||
681 | 2096 | sspph = SourcePackagePublishingHistory( | 2143 | sspph = SourcePackagePublishingHistory( |
682 | 2097 | distroseries=distroseries, | 2144 | distroseries=distroseries, |
683 | 2098 | 2145 | ||
684 | === modified file 'lib/lp/translations/doc/translationtemplatesbuildbehavior.txt' | |||
685 | --- lib/lp/translations/doc/translationtemplatesbuildbehavior.txt 2010-03-05 13:52:32 +0000 | |||
686 | +++ lib/lp/translations/doc/translationtemplatesbuildbehavior.txt 2010-03-15 22:22:20 +0000 | |||
687 | @@ -2,13 +2,21 @@ | |||
688 | 2 | 2 | ||
689 | 3 | == Setup == | 3 | == Setup == |
690 | 4 | 4 | ||
692 | 5 | Set up build environment. | 5 | Set up build environment. Clear out the build queue. |
693 | 6 | 6 | ||
694 | 7 | >>> import transaction | 7 | >>> import transaction |
695 | 8 | >>> import logging | 8 | >>> import logging |
696 | 9 | >>> logger = logging.getLogger() | 9 | >>> logger = logging.getLogger() |
697 | 10 | >>> logger.setLevel(logging.CRITICAL) | 10 | >>> logger.setLevel(logging.CRITICAL) |
698 | 11 | 11 | ||
699 | 12 | >>> from canonical.database.sqlbase import quote | ||
700 | 13 | >>> from canonical.launchpad.interfaces.lpstorm import IMasterStore | ||
701 | 14 | >>> from lp.services.job.interfaces.job import JobStatus | ||
702 | 15 | >>> from lp.services.job.model.job import Job | ||
703 | 16 | >>> store = IMasterStore(Job) | ||
704 | 17 | >>> query = store.execute("UPDATE Job SET status = %s" % quote( | ||
705 | 18 | ... JobStatus.FAILED)) | ||
706 | 19 | |||
707 | 12 | >>> from lp.buildmaster.master import BuilddMaster | 20 | >>> from lp.buildmaster.master import BuilddMaster |
708 | 13 | >>> from canonical.buildd.tests import BuilddSlaveTestSetup | 21 | >>> from canonical.buildd.tests import BuilddSlaveTestSetup |
709 | 14 | >>> bm = BuilddMaster(logger, transaction) | 22 | >>> bm = BuilddMaster(logger, transaction) |
710 | @@ -39,7 +47,21 @@ | |||
711 | 39 | 47 | ||
712 | 40 | Make a builder to process our build request. | 48 | Make a builder to process our build request. |
713 | 41 | 49 | ||
715 | 42 | >>> builder = factory.makeBuilder(virtualized=False, processor=processor) | 50 | >>> builder = factory.makeBuilder( |
716 | 51 | ... virtualized=True, processor=processor, vm_host='hostname') | ||
717 | 52 | |||
718 | 53 | The builder doesn't talk to a real slave. We don't have those in our | ||
719 | 54 | test suite. But we give it a fake one. | ||
720 | 55 | |||
721 | 56 | >>> from lp.testing.fakemethod import FakeMethod | ||
722 | 57 | >>> class FakeSlave: | ||
723 | 58 | ... build = FakeMethod() | ||
724 | 59 | ... cacheFile = FakeMethod() | ||
725 | 60 | ... resume = FakeMethod(result=('Output here', 'Errors here', 0)) | ||
726 | 61 | |||
727 | 62 | >>> from zope.security.proxy import removeSecurityProxy | ||
728 | 63 | >>> slave = FakeSlave() | ||
729 | 64 | >>> removeSecurityProxy(builder).slave = slave | ||
730 | 43 | 65 | ||
731 | 44 | 66 | ||
732 | 45 | == Get a job! == | 67 | == Get a job! == |
733 | @@ -60,20 +82,27 @@ | |||
734 | 60 | >>> print buildqueue.date_started | 82 | >>> print buildqueue.date_started |
735 | 61 | None | 83 | None |
736 | 62 | 84 | ||
747 | 63 | Dispatch the job to the build slave. The proper method to call here | 85 | Our job is now first in line to be executed. |
748 | 64 | would have been Builder.findAndStartJob, but it has not been generalised | 86 | |
749 | 65 | to handle new job types yet. | 87 | >>> removeSecurityProxy(builder)._findBuildCandidate() == buildqueue |
750 | 66 | 88 | True | |
751 | 67 | # XXX JeroenVermeulen bug=506617: call findAndStartJob instead. | 89 | |
752 | 68 | 90 | Dispatch the first job in line (ours!) to the build slave. | |
753 | 69 | >>> from zope.security.proxy import removeSecurityProxy | 91 | |
754 | 70 | >>> removeSecurityProxy(builder)._dispatchBuildCandidate(buildqueue) | 92 | >>> activated_job = builder.findAndStartJob() |
755 | 71 | 93 | >>> activated_job == buildqueue | |
756 | 72 | The build is now marked as started. | 94 | True |
757 | 95 | |||
758 | 96 | Our build is now marked as started. | ||
759 | 73 | 97 | ||
760 | 74 | >>> buildqueue.date_started is None | 98 | >>> buildqueue.date_started is None |
761 | 75 | False | 99 | False |
762 | 76 | 100 | ||
763 | 101 | The slave's build method has been called. | ||
764 | 102 | |||
765 | 103 | >>> slave.build.call_count | ||
766 | 104 | 1 | ||
767 | 105 | |||
768 | 77 | 106 | ||
769 | 78 | == Teardown == | 107 | == Teardown == |
770 | 79 | 108 | ||
771 | 80 | 109 | ||
772 | === modified file 'lib/lp/translations/model/translationtemplatesbuildbehavior.py' | |||
773 | --- lib/lp/translations/model/translationtemplatesbuildbehavior.py 2010-02-11 19:11:11 +0000 | |||
774 | +++ lib/lp/translations/model/translationtemplatesbuildbehavior.py 2010-03-15 22:22:20 +0000 | |||
775 | @@ -17,6 +17,7 @@ | |||
776 | 17 | 17 | ||
777 | 18 | from canonical.launchpad.interfaces import ILaunchpadCelebrities | 18 | from canonical.launchpad.interfaces import ILaunchpadCelebrities |
778 | 19 | 19 | ||
779 | 20 | from lp.buildmaster.interfaces.builder import CorruptBuildID | ||
780 | 20 | from lp.buildmaster.interfaces.buildfarmjobbehavior import ( | 21 | from lp.buildmaster.interfaces.buildfarmjobbehavior import ( |
781 | 21 | IBuildFarmJobBehavior) | 22 | IBuildFarmJobBehavior) |
782 | 22 | from lp.buildmaster.model.buildfarmjobbehavior import ( | 23 | from lp.buildmaster.model.buildfarmjobbehavior import ( |
783 | @@ -40,12 +41,27 @@ | |||
784 | 40 | self._builder.slave.cacheFile(logger, chroot) | 41 | self._builder.slave.cacheFile(logger, chroot) |
785 | 41 | buildid = self.buildfarmjob.getName() | 42 | buildid = self.buildfarmjob.getName() |
786 | 42 | 43 | ||
788 | 43 | args = { 'branch_url': self.buildfarmjob.branch.url } | 44 | args = self.buildfarmjob.metadata |
789 | 44 | filemap = {} | 45 | filemap = {} |
790 | 45 | 46 | ||
791 | 46 | self._builder.slave.build( | 47 | self._builder.slave.build( |
792 | 47 | buildid, self.build_type, chroot_sha1, filemap, args) | 48 | buildid, self.build_type, chroot_sha1, filemap, args) |
793 | 48 | 49 | ||
794 | 50 | def verifySlaveBuildID(self, slave_build_id): | ||
795 | 51 | """See `IBuildFarmJobBehavior`.""" | ||
796 | 52 | try: | ||
797 | 53 | branch_name, queue_item_id = slave_build_id.rsplit('-', 1) | ||
798 | 54 | except ValueError: | ||
799 | 55 | raise CorruptBuildID( | ||
800 | 56 | "Malformed translation templates build id: '%s'" % ( | ||
801 | 57 | slave_build_id)) | ||
802 | 58 | |||
803 | 59 | buildqueue = self.getVerifiedBuildQueue(queue_item_id) | ||
804 | 60 | if buildqueue.job != self.buildfarmjob.job: | ||
805 | 61 | raise CorruptBuildID( | ||
806 | 62 | "ID mismatch for translation templates build '%s'" % ( | ||
807 | 63 | slave_build_id)) | ||
808 | 64 | |||
809 | 49 | def _getChroot(self): | 65 | def _getChroot(self): |
810 | 50 | ubuntu = getUtility(ILaunchpadCelebrities).ubuntu | 66 | ubuntu = getUtility(ILaunchpadCelebrities).ubuntu |
811 | 51 | return ubuntu.currentseries.nominatedarchindep.getChroot() | 67 | return ubuntu.currentseries.nominatedarchindep.getChroot() |
812 | 52 | 68 | ||
813 | === modified file 'lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py' | |||
814 | --- lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py 2010-03-12 08:31:43 +0000 | |||
815 | +++ lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py 2010-03-15 22:22:20 +0000 | |||
816 | @@ -19,6 +19,7 @@ | |||
817 | 19 | from lp.buildmaster.interfaces.buildbase import BuildStatus | 19 | from lp.buildmaster.interfaces.buildbase import BuildStatus |
818 | 20 | from lp.buildmaster.interfaces.buildfarmjobbehavior import ( | 20 | from lp.buildmaster.interfaces.buildfarmjobbehavior import ( |
819 | 21 | IBuildFarmJobBehavior) | 21 | IBuildFarmJobBehavior) |
820 | 22 | from lp.buildmaster.interfaces.builder import CorruptBuildID | ||
821 | 22 | from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet | 23 | from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet |
822 | 23 | from lp.testing import TestCaseWithFactory | 24 | from lp.testing import TestCaseWithFactory |
823 | 24 | from lp.testing.fakemethod import FakeMethod | 25 | from lp.testing.fakemethod import FakeMethod |
824 | @@ -123,6 +124,10 @@ | |||
825 | 123 | self.assertEqual( | 124 | self.assertEqual( |
826 | 124 | 'translation-templates', slave_status['test_build_type']) | 125 | 'translation-templates', slave_status['test_build_type']) |
827 | 125 | self.assertIn('branch_url', slave_status['test_build_args']) | 126 | self.assertIn('branch_url', slave_status['test_build_args']) |
828 | 127 | # The slave receives the public http URL for the branch. | ||
829 | 128 | self.assertEqual( | ||
830 | 129 | behavior.buildfarmjob.branch.composePublicURL(), | ||
831 | 130 | slave_status['test_build_args']['branch_url']) | ||
832 | 126 | 131 | ||
833 | 127 | def test_getChroot(self): | 132 | def test_getChroot(self): |
834 | 128 | # _getChroot produces the current chroot for the current Ubuntu | 133 | # _getChroot produces the current chroot for the current Ubuntu |
835 | @@ -170,6 +175,38 @@ | |||
836 | 170 | self.assertEqual(1, builder.cleanSlave.call_count) | 175 | self.assertEqual(1, builder.cleanSlave.call_count) |
837 | 171 | self.assertEqual(0, behavior._uploadTarball.call_count) | 176 | self.assertEqual(0, behavior._uploadTarball.call_count) |
838 | 172 | 177 | ||
839 | 178 | def test_verifySlaveBuildID_success(self): | ||
840 | 179 | # TranslationTemplatesBuildJob.getName generates slave build ids | ||
841 | 180 | # that TranslationTemplatesBuildBehavior.verifySlaveBuildID | ||
842 | 181 | # accepts. | ||
843 | 182 | behavior = self._makeBehavior() | ||
844 | 183 | buildfarmjob = behavior.buildfarmjob | ||
845 | 184 | job = buildfarmjob.job | ||
846 | 185 | |||
847 | 186 | # The test is that this not raise CorruptBuildID (or anything | ||
848 | 187 | # else, for that matter). | ||
849 | 188 | behavior.verifySlaveBuildID(behavior.buildfarmjob.getName()) | ||
850 | 189 | |||
851 | 190 | def test_verifySlaveBuildID_handles_dashes(self): | ||
852 | 191 | # TranslationTemplatesBuildBehavior.verifySlaveBuildID can deal | ||
853 | 192 | # with dashes in branch names. | ||
854 | 193 | behavior = self._makeBehavior() | ||
855 | 194 | buildfarmjob = behavior.buildfarmjob | ||
856 | 195 | job = buildfarmjob.job | ||
857 | 196 | buildfarmjob.branch.name = 'x-y-z--' | ||
858 | 197 | |||
859 | 198 | # The test is that this not raise CorruptBuildID (or anything | ||
860 | 199 | # else, for that matter). | ||
861 | 200 | behavior.verifySlaveBuildID(behavior.buildfarmjob.getName()) | ||
862 | 201 | |||
863 | 202 | def test_verifySlaveBuildID_malformed(self): | ||
864 | 203 | behavior = self._makeBehavior() | ||
865 | 204 | self.assertRaises(CorruptBuildID, behavior.verifySlaveBuildID, 'huh?') | ||
866 | 205 | |||
867 | 206 | def test_verifySlaveBuildID_notfound(self): | ||
868 | 207 | behavior = self._makeBehavior() | ||
869 | 208 | self.assertRaises(CorruptBuildID, behavior.verifySlaveBuildID, '1-1') | ||
870 | 209 | |||
871 | 173 | 210 | ||
872 | 174 | def test_suite(): | 211 | def test_suite(): |
873 | 175 | return TestLoader().loadTestsFromName(__name__) | 212 | return TestLoader().loadTestsFromName(__name__) |
On Mon, Mar 15, 2010 at 10:22 PM, Michael Hudson /bugs.launchpad .net/bugs/ 413637
<email address hidden> wrote:
> Michael Hudson has proposed merging lp:~mwhudson/launchpad/back-off-failing-imports-bug-413637 into lp:launchpad.
>
> Requested reviews:
> Canonical Launchpad Engineering (launchpad)
> Related bugs:
> #413637 imports disabled too rapidly
> https:/
>
>
> Hi there,
>
> This small branch changes the code import system to exponentially back off on retrying code imports -- see the linked bug for more.
>
I think the branch is bigger than you intended. It's got a bunch of
build farm job stuff in the diff.
> Currently it backs off so that it tries imports at 6, 12, 24 and 48 hours (for subversion) for a total of three and a bit days of trying before marking an import failed -- is that enough? It seems OK, though perhaps a bit on the low side, to me.
>
Sounds fine to me.
... code/model/ codeimportjob. py' code/model/ codeimportjob. py 2010-02-24 10:18:16 +0000 code/model/ codeimportjob. py 2010-03-15 22:22:20 +0000 orkflow' , ICodeImportJobW orkflow) Workflow` .""" review_ status == CodeImportRevie wStatus. REVIEWED, ( branch. unique_ name)) effective_ update_ interval code_import= code_import, date_due=UTC_NOW) result_ list = list(CodeImport Result. selectBy( code_import) .orderBy( ['-date_ created' ]).limit( 1)) result_ list = list(CodeImport Result. selectBy( code_import) .orderBy( ['-date_ created' ]).limit( 1))
> === modified file 'lib/lp/
> --- lib/lp/
> +++ lib/lp/
> @@ -10,6 +10,8 @@
> 'CodeImportJobW
> ]
>
> +import datetime
> +
> from sqlobject import ForeignKey, IntCol, SQLObjectNotFound, StringCol
>
> from zope.component import getUtility
> @@ -140,7 +142,7 @@
>
> implements(
>
> - def newJob(self, code_import, date_due=None):
> + def newJob(self, code_import, interval=None):
> """See `ICodeImportJob
> assert code_import.
> "Review status of %s is not REVIEWED: %s" % (
> @@ -149,23 +151,22 @@
> "Already associated to a CodeImportJob: %s" % (
> code_import.
>
> + if interval is None:
> + interval = code_import.
> +
> job = CodeImportJob(
>
> - if date_due is None:
> - # Find the most recent CodeImportResult for this CodeImport. We
> - # sort by date_created because we do not have an index on
> - # date_job_started in the database, and that should give the same
> - # sort order.
> - most_recent_
> - code_import=
> + # Find the most recent CodeImportResult for this CodeImport. We
> + # sort by date_created because we do not have an index on
> + # date_job_started in the database, and that should give the same
> + # sort order.
> + most_recent_
> + code_import=
>
Why aren't you using Storm, with its 'one()' notation for this?
> @@ -281,15 +282,19 @@ codeimport. consecutive_ failure_ limit consecutive_ failure_ count >= failure_limit: consecutive_ failure_ count.. .
> # If the import has failed too many times in a row, mark it as
> # FAILING.
> failure_limit = config.
> - if code_import.
> + failure_count = code_import.