~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/buildmaster/model/packagebuild.py

[r=allenap, bac, gmb, julian-edwards, wallyworld][bug=905853, 905855,
 906079] In buildmaster,
 always shift into a read-write database transaction access mode before
 updating PackageBuild statuses. Shift into read-write transactions in
 appropriate places in TranslationTemplatesBuildBehavior. Ensure that all
 lp.buildmaster tests to which it is relevant are running with
 BuilddManagerTestFixture.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright 2010 Canonical Ltd.  This software is licensed under the
 
1
# Copyright 2010-2011 Canonical Ltd.  This software is licensed under the
2
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
3
 
4
4
__metaclass__ = type
24
24
    Storm,
25
25
    Unicode,
26
26
    )
 
27
import transaction
27
28
from zope.component import getUtility
28
29
from zope.interface import (
29
30
    classProvides,
50
51
from lp.services.config import config
51
52
from lp.services.database.enumcol import DBEnum
52
53
from lp.services.database.lpstorm import IMasterStore
 
54
from lp.services.database.transaction_policy import DatabaseTransactionPolicy
53
55
from lp.services.helpers import filenameToContentType
54
56
from lp.services.librarian.browser import ProxiedLibraryFileAlias
55
57
from lp.services.librarian.interfaces import ILibraryFileAliasSet
177
179
    def storeBuildInfo(build, librarian, slave_status):
178
180
        """See `IPackageBuild`."""
179
181
        def got_log(lfa_id):
 
182
            dependencies = slave_status.get('dependencies')
 
183
            if dependencies is not None:
 
184
                dependencies = unicode(dependencies)
 
185
 
180
186
            # log, builder and date_finished are read-only, so we must
181
187
            # currently remove the security proxy to set them.
182
188
            naked_build = removeSecurityProxy(build)
183
 
            naked_build.log = lfa_id
184
 
            naked_build.builder = build.buildqueue_record.builder
185
 
            # XXX cprov 20060615 bug=120584: Currently buildduration includes
186
 
            # the scanner latency, it should really be asking the slave for
187
 
            # the duration spent building locally.
188
 
            naked_build.date_finished = datetime.datetime.now(pytz.UTC)
189
 
            if slave_status.get('dependencies') is not None:
190
 
                build.dependencies = unicode(slave_status.get('dependencies'))
191
 
            else:
192
 
                build.dependencies = None
 
189
 
 
190
            transaction.commit()
 
191
            with DatabaseTransactionPolicy(read_only=False):
 
192
                naked_build.log = lfa_id
 
193
                naked_build.builder = build.buildqueue_record.builder
 
194
                # XXX cprov 20060615 bug=120584: Currently buildduration
 
195
                # includes the scanner latency.  It should really be asking
 
196
                # the slave for the duration spent building locally.
 
197
                naked_build.date_finished = datetime.datetime.now(pytz.UTC)
 
198
                build.dependencies = dependencies
 
199
                transaction.commit()
193
200
 
194
201
        d = build.getLogFromSlave(build)
195
202
        return d.addCallback(got_log)
286
293
            job=job, processor=processor,
287
294
            virtualized=specific_job.virtualized)
288
295
        Store.of(self).add(queue_entry)
 
296
 
289
297
        return queue_entry
290
298
 
291
299
    def handleStatus(self, status, librarian, slave_status):
292
300
        """See `IPackageBuild`."""
 
301
        # Avoid circular imports.
293
302
        from lp.buildmaster.manager import BUILDD_MANAGER_LOG_NAME
 
303
 
294
304
        logger = logging.getLogger(BUILDD_MANAGER_LOG_NAME)
295
305
        send_notification = status in self.ALLOWED_STATUS_NOTIFICATIONS
296
306
        method = getattr(self, '_handleStatus_' + status, None)
297
307
        if method is None:
298
 
            logger.critical("Unknown BuildStatus '%s' for builder '%s'"
299
 
                            % (status, self.buildqueue_record.builder.url))
300
 
            return
 
308
            logger.critical(
 
309
                "Unknown BuildStatus '%s' for builder '%s'",
 
310
                status, self.buildqueue_record.builder.url)
 
311
            return None
 
312
 
301
313
        d = method(librarian, slave_status, logger, send_notification)
302
314
        return d
303
315
 
 
316
    def _destroy_buildqueue_record(self, unused_arg):
 
317
        """Destroy this build's `BuildQueue` record."""
 
318
        transaction.commit()
 
319
        with DatabaseTransactionPolicy(read_only=False):
 
320
            self.buildqueue_record.destroySelf()
 
321
            transaction.commit()
 
322
 
304
323
    def _release_builder_and_remove_queue_item(self):
305
324
        # Release the builder for another job.
306
325
        d = self.buildqueue_record.builder.cleanSlave()
307
326
        # Remove BuildQueue record.
308
 
        return d.addCallback(lambda x: self.buildqueue_record.destroySelf())
 
327
        return d.addCallback(self._destroy_buildqueue_record)
 
328
 
 
329
    def _notify_if_appropriate(self, appropriate=True, extra_info=None):
 
330
        """If `appropriate`, call `self.notify` in a write transaction."""
 
331
        if appropriate:
 
332
            transaction.commit()
 
333
            with DatabaseTransactionPolicy(read_only=False):
 
334
                self.notify(extra_info=extra_info)
 
335
                transaction.commit()
309
336
 
310
337
    def _handleStatus_OK(self, librarian, slave_status, logger,
311
338
                         send_notification):
321
348
            self.buildqueue_record.specific_job.build.title,
322
349
            self.buildqueue_record.builder.name))
323
350
 
324
 
        # If this is a binary package build, discard it if its source is
325
 
        # no longer published.
 
351
        # If this is a binary package build for a source that is no
 
352
        # longer published, discard it.
326
353
        if self.build_farm_job_type == BuildFarmJobType.PACKAGEBUILD:
327
354
            build = self.buildqueue_record.specific_job.build
328
355
            if not build.current_source_publication:
329
 
                build.status = BuildStatus.SUPERSEDED
 
356
                transaction.commit()
 
357
                with DatabaseTransactionPolicy(read_only=False):
 
358
                    build.status = BuildStatus.SUPERSEDED
 
359
                    transaction.commit()
330
360
                return self._release_builder_and_remove_queue_item()
331
361
 
332
 
        # Explode before collect a binary that is denied in this
333
 
        # distroseries/pocket
 
362
        # Explode rather than collect a binary that is denied in this
 
363
        # distroseries/pocket.
334
364
        if not self.archive.allowUpdatesToReleasePocket():
335
365
            assert self.distro_series.canUploadToPocket(self.pocket), (
336
366
                "%s (%s) can not be built for pocket %s: illegal status"
375
405
            # files from the slave.
376
406
            if successful_copy_from_slave:
377
407
                logger.info(
378
 
                    "Gathered %s %d completely. Moving %s to uploader queue."
379
 
                    % (self.__class__.__name__, self.id, upload_leaf))
 
408
                    "Gathered %s %d completely. "
 
409
                    "Moving %s to uploader queue.",
 
410
                    self.__class__.__name__, self.id, upload_leaf)
380
411
                target_dir = os.path.join(root, "incoming")
381
 
                self.status = BuildStatus.UPLOADING
 
412
                resulting_status = BuildStatus.UPLOADING
382
413
            else:
383
414
                logger.warning(
384
 
                    "Copy from slave for build %s was unsuccessful.", self.id)
385
 
                self.status = BuildStatus.FAILEDTOUPLOAD
386
 
                if send_notification:
387
 
                    self.notify(
388
 
                        extra_info='Copy from slave was unsuccessful.')
 
415
                    "Copy from slave for build %s was unsuccessful.",
 
416
                    self.id)
389
417
                target_dir = os.path.join(root, "failed")
 
418
                resulting_status = BuildStatus.FAILEDTOUPLOAD
 
419
 
 
420
            transaction.commit()
 
421
            with DatabaseTransactionPolicy(read_only=False):
 
422
                self.status = resulting_status
 
423
                transaction.commit()
 
424
 
 
425
            if not successful_copy_from_slave:
 
426
                self._notify_if_appropriate(
 
427
                    send_notification, "Copy from slave was unsuccessful.")
390
428
 
391
429
            if not os.path.exists(target_dir):
392
430
                os.mkdir(target_dir)
394
432
            # Release the builder for another job.
395
433
            d = self._release_builder_and_remove_queue_item()
396
434
 
397
 
            # Commit so there are no race conditions with archiveuploader
398
 
            # about self.status.
399
 
            Store.of(self).commit()
400
 
 
401
435
            # Move the directory used to grab the binaries into
402
436
            # the incoming directory so the upload processor never
403
437
            # sees half-finished uploads.
421
455
        set the job status as FAILEDTOBUILD, store available info and
422
456
        remove Buildqueue entry.
423
457
        """
424
 
        self.status = BuildStatus.FAILEDTOBUILD
 
458
        transaction.commit()
 
459
        with DatabaseTransactionPolicy(read_only=False):
 
460
            self.status = BuildStatus.FAILEDTOBUILD
 
461
            transaction.commit()
425
462
 
426
463
        def build_info_stored(ignored):
427
 
            if send_notification:
428
 
                self.notify()
 
464
            self._notify_if_appropriate(send_notification)
429
465
            d = self.buildqueue_record.builder.cleanSlave()
430
 
            return d.addCallback(
431
 
                lambda x: self.buildqueue_record.destroySelf())
 
466
            return d.addCallback(self._destroy_buildqueue_record)
432
467
 
433
468
        d = self.storeBuildInfo(self, librarian, slave_status)
434
469
        return d.addCallback(build_info_stored)
441
476
        MANUALDEPWAIT, store available information, remove BuildQueue
442
477
        entry and release builder slave for another job.
443
478
        """
444
 
        self.status = BuildStatus.MANUALDEPWAIT
 
479
        with DatabaseTransactionPolicy(read_only=False):
 
480
            self.status = BuildStatus.MANUALDEPWAIT
 
481
            transaction.commit()
445
482
 
446
483
        def build_info_stored(ignored):
447
484
            logger.critical("***** %s is MANUALDEPWAIT *****"
448
485
                            % self.buildqueue_record.builder.name)
449
 
            if send_notification:
450
 
                self.notify()
 
486
            self._notify_if_appropriate(send_notification)
451
487
            d = self.buildqueue_record.builder.cleanSlave()
452
 
            return d.addCallback(
453
 
                lambda x: self.buildqueue_record.destroySelf())
 
488
            return d.addCallback(self._destroy_buildqueue_record)
454
489
 
455
490
        d = self.storeBuildInfo(self, librarian, slave_status)
456
491
        return d.addCallback(build_info_stored)
463
498
        job as CHROOTFAIL, store available information, remove BuildQueue
464
499
        and release the builder.
465
500
        """
466
 
        self.status = BuildStatus.CHROOTWAIT
 
501
        with DatabaseTransactionPolicy(read_only=False):
 
502
            self.status = BuildStatus.CHROOTWAIT
 
503
            transaction.commit()
467
504
 
468
505
        def build_info_stored(ignored):
469
 
            logger.critical("***** %s is CHROOTWAIT *****" %
470
 
                            self.buildqueue_record.builder.name)
471
 
            if send_notification:
472
 
                self.notify()
 
506
            logger.critical(
 
507
                "***** %s is CHROOTWAIT *****",
 
508
                self.buildqueue_record.builder.name)
 
509
 
 
510
            self._notify_if_appropriate(send_notification)
473
511
            d = self.buildqueue_record.builder.cleanSlave()
474
 
            return d.addCallback(
475
 
                lambda x: self.buildqueue_record.destroySelf())
 
512
            return d.addCallback(self._destroy_buildqueue_record)
476
513
 
477
514
        d = self.storeBuildInfo(self, librarian, slave_status)
478
515
        return d.addCallback(build_info_stored)
479
516
 
 
517
    def _reset_buildqueue_record(self, ignored_arg=None):
 
518
        """Reset the `BuildQueue` record, in a write transaction."""
 
519
        transaction.commit()
 
520
        with DatabaseTransactionPolicy(read_only=False):
 
521
            self.buildqueue_record.reset()
 
522
            transaction.commit()
 
523
 
480
524
    def _handleStatus_BUILDERFAIL(self, librarian, slave_status, logger,
481
525
                                  send_notification):
482
526
        """Handle builder failures.
490
534
        self.buildqueue_record.builder.failBuilder(
491
535
            "Builder returned BUILDERFAIL when asked for its status")
492
536
 
493
 
        def build_info_stored(ignored):
494
 
            # simply reset job
495
 
            self.buildqueue_record.reset()
496
537
        d = self.storeBuildInfo(self, librarian, slave_status)
497
 
        return d.addCallback(build_info_stored)
 
538
        return d.addCallback(self._reset_buildqueue_record)
498
539
 
499
540
    def _handleStatus_GIVENBACK(self, librarian, slave_status, logger,
500
541
                                send_notification):
514
555
            # the next Paris Summit, infinity has some ideas about how
515
556
            # to use this content. For now we just ensure it's stored.
516
557
            d = self.buildqueue_record.builder.cleanSlave()
517
 
            self.buildqueue_record.reset()
 
558
            self._reset_buildqueue_record()
518
559
            return d
519
560
 
520
561
        d = self.storeBuildInfo(self, librarian, slave_status)