launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #19125
Re: [Merge] lp:~cjwatson/launchpad/snapbuild-basic-model into lp:launchpad
Diff comments:
>
> === added file 'lib/lp/snappy/model/snapbuild.py'
> --- lib/lp/snappy/model/snapbuild.py 1970-01-01 00:00:00 +0000
> +++ lib/lp/snappy/model/snapbuild.py 2015-07-30 12:59:37 +0000
> @@ -0,0 +1,357 @@
> +# Copyright 2015 Canonical Ltd. This software is licensed under the
> +# GNU Affero General Public License version 3 (see the file LICENSE).
> +
> +__metaclass__ = type
> +__all__ = [
> + 'SnapBuild',
> + 'SnapFile',
> + ]
> +
> +from datetime import timedelta
> +
> +import pytz
> +from storm.locals import (
> + Bool,
> + DateTime,
> + Desc,
> + Int,
> + Reference,
> + Store,
> + Storm,
> + Unicode,
> + )
> +from storm.store import EmptyResultSet
> +from zope.component import getUtility
> +from zope.interface import implementer
> +
> +from lp.app.errors import NotFoundError
> +from lp.buildmaster.enums import (
> + BuildFarmJobType,
> + BuildStatus,
> + )
> +from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource
> +from lp.buildmaster.model.buildfarmjob import SpecificBuildFarmJobSourceMixin
> +from lp.buildmaster.model.packagebuild import PackageBuildMixin
> +from lp.registry.interfaces.pocket import PackagePublishingPocket
> +from lp.registry.model.person import Person
> +from lp.services.database.bulk import load_related
> +from lp.services.database.constants import DEFAULT
> +from lp.services.database.decoratedresultset import DecoratedResultSet
> +from lp.services.database.enumcol import DBEnum
> +from lp.services.database.interfaces import (
> + IMasterStore,
> + IStore,
> + )
> +from lp.services.features import getFeatureFlag
> +from lp.services.librarian.browser import ProxiedLibraryFileAlias
> +from lp.services.librarian.model import (
> + LibraryFileAlias,
> + LibraryFileContent,
> + )
> +from lp.snappy.interfaces.snap import (
> + SNAP_FEATURE_FLAG,
> + SnapFeatureDisabled,
> + )
> +from lp.snappy.interfaces.snapbuild import (
> + ISnapBuild,
> + ISnapBuildSet,
> + ISnapFile,
> + )
> +from lp.soyuz.interfaces.component import IComponentSet
> +from lp.soyuz.model.archive import Archive
> +
> +
> +@implementer(ISnapFile)
> +class SnapFile(Storm):
> + """See `ISnap`."""
> +
> + __storm_table__ = 'SnapFile'
> +
> + id = Int(name='id', primary=True)
> +
> + snapbuild_id = Int(name='snapbuild', allow_none=False)
> + snapbuild = Reference(snapbuild_id, 'SnapBuild.id')
> +
> + libraryfile_id = Int(name='libraryfile', allow_none=False)
> + libraryfile = Reference(libraryfile_id, 'LibraryFileAlias.id')
> +
> + def __init__(self, snapbuild, libraryfile):
> + """Construct a `SnapFile`."""
> + super(SnapFile, self).__init__()
> + self.snapbuild = snapbuild
> + self.libraryfile = libraryfile
> +
> +
> +@implementer(ISnapBuild)
> +class SnapBuild(PackageBuildMixin, Storm):
> + """See `ISnapBuild`."""
> +
> + __storm_table__ = 'SnapBuild'
> +
> + job_type = BuildFarmJobType.SNAPBUILD
> +
> + id = Int(name='id', primary=True)
> +
> + build_farm_job_id = Int(name='build_farm_job', allow_none=False)
> + build_farm_job = Reference(build_farm_job_id, 'BuildFarmJob.id')
> +
> + requester_id = Int(name='requester', allow_none=False)
> + requester = Reference(requester_id, 'Person.id')
> +
> + snap_id = Int(name='snap', allow_none=False)
> + snap = Reference(snap_id, 'Snap.id')
> +
> + archive_id = Int(name='archive', allow_none=False)
> + archive = Reference(archive_id, 'Archive.id')
> +
> + distro_arch_series_id = Int(name='distro_arch_series', allow_none=False)
> + distro_arch_series = Reference(
> + distro_arch_series_id, 'DistroArchSeries.id')
> +
> + pocket = DBEnum(enum=PackagePublishingPocket, allow_none=False)
> +
> + processor_id = Int(name='processor', allow_none=False)
> + processor = Reference(processor_id, 'Processor.id')
> + virtualized = Bool(name='virtualized')
> +
> + date_created = DateTime(
> + name='date_created', tzinfo=pytz.UTC, allow_none=False)
> + date_started = DateTime(name='date_started', tzinfo=pytz.UTC)
> + date_finished = DateTime(name='date_finished', tzinfo=pytz.UTC)
> + date_first_dispatched = DateTime(
> + name='date_first_dispatched', tzinfo=pytz.UTC)
> +
> + builder_id = Int(name='builder')
> + builder = Reference(builder_id, 'Builder.id')
> +
> + status = DBEnum(name='status', enum=BuildStatus, allow_none=False)
> +
> + log_id = Int(name='log')
> + log = Reference(log_id, 'LibraryFileAlias.id')
> +
> + upload_log_id = Int(name='upload_log')
> + upload_log = Reference(upload_log_id, 'LibraryFileAlias.id')
> +
> + dependencies = Unicode(name='dependencies')
> +
> + failure_count = Int(name='failure_count', allow_none=False)
> +
> + def __init__(self, build_farm_job, requester, snap, archive,
> + distro_arch_series, pocket, processor, virtualized,
> + date_created):
> + """Construct a `SnapBuild`."""
> + if not getFeatureFlag(SNAP_FEATURE_FLAG):
> + raise SnapFeatureDisabled
> + super(SnapBuild, self).__init__()
> + self.build_farm_job = build_farm_job
> + self.requester = requester
> + self.snap = snap
> + self.archive = archive
> + self.distro_arch_series = distro_arch_series
> + self.pocket = pocket
> + self.processor = processor
> + self.virtualized = virtualized
> + self.date_created = date_created
> + self.status = BuildStatus.NEEDSBUILD
> +
> + @property
> + def is_private(self):
> + """See `IBuildFarmJob`."""
> + return self.snap.owner.private or self.archive.private
> +
> + @property
> + def title(self):
> + das = self.distro_arch_series
> + name = self.snap.name
> + return "%s build of %s snap package in %s %s" % (
> + das.architecturetag, name, das.distroseries.distribution.name,
> + das.distroseries.getSuite(self.pocket))
> +
> + @property
> + def distribution(self):
> + """See `IPackageBuild`."""
> + return self.distro_arch_series.distroseries.distribution
> +
> + @property
> + def distro_series(self):
> + """See `IPackageBuild`."""
> + return self.distro_arch_series.distroseries
> +
> + @property
> + def current_component(self):
> + component = self.archive.default_component
> + if component is not None:
> + return component
> + else:
> + # XXX cjwatson 2015-07-17: Hardcode to universe for the time being.
> + return getUtility(IComponentSet)["universe"]
Ah yes, quite probably. I'll change that, thanks.
> +
> + @property
> + def score(self):
> + """See `ISnapBuild`."""
> + if self.buildqueue_record is None:
> + return None
> + else:
> + return self.buildqueue_record.lastscore
> +
> + @property
> + def can_be_rescored(self):
> + """See `ISnapBuild`."""
> + return (
> + self.buildqueue_record is not None and
> + self.status is BuildStatus.NEEDSBUILD)
> +
> + @property
> + def can_be_cancelled(self):
> + """See `ISnapBuild`."""
> + if not self.buildqueue_record:
> + return False
> +
> + cancellable_statuses = [
> + BuildStatus.BUILDING,
> + BuildStatus.NEEDSBUILD,
> + ]
> + return self.status in cancellable_statuses
> +
> + def rescore(self, score):
> + """See `ISnapBuild`."""
> + assert self.can_be_rescored, "Build %s cannot be rescored" % self.id
> + self.buildqueue_record.manualScore(score)
> +
> + def cancel(self):
> + """See `ISnapBuild`."""
> + if not self.can_be_cancelled:
> + return
> + # BuildQueue.cancel() will decide whether to go straight to
> + # CANCELLED, or go through CANCELLING to let buildd-manager clean up
> + # the slave.
> + self.buildqueue_record.cancel()
> +
> + def calculateScore(self):
> + return 2505 + self.archive.relative_build_score
> +
> + def getMedianBuildDuration(self):
> + """Return the median duration of our successful builds."""
> + store = IStore(self)
> + result = store.find(
> + (SnapBuild.date_started, SnapBuild.date_finished),
> + SnapBuild.snap == self.snap_id,
> + SnapBuild.distro_arch_series == self.distro_arch_series_id,
> + SnapBuild.status == BuildStatus.FULLYBUILT)
> + result.order_by(Desc(SnapBuild.date_finished))
> + durations = [row[1] - row[0] for row in result[:9]]
> + if len(durations) == 0:
> + return None
> + durations.sort()
> + return durations[len(durations) // 2]
> +
> + def estimateDuration(self):
> + """See `IBuildFarmJob`."""
> + median = self.getMedianBuildDuration()
> + if median is not None:
> + return median
> + return timedelta(minutes=30)
> +
> + def getFiles(self):
> + """See `ISnapBuild`."""
> + result = Store.of(self).find(
> + (SnapFile, LibraryFileAlias, LibraryFileContent),
> + SnapFile.snapbuild == self.id,
> + LibraryFileAlias.id == SnapFile.libraryfile_id,
> + LibraryFileContent.id == LibraryFileAlias.contentID)
> + return result.order_by([LibraryFileAlias.filename, SnapFile.id])
> +
> + def getFileByName(self, filename):
> + """See `ISnapBuild`."""
> + if filename.endswith(".txt.gz"):
> + file_object = self.log
> + elif filename.endswith("_log.txt"):
> + file_object = self.upload_log
> + else:
> + file_object = Store.of(self).find(
> + LibraryFileAlias,
> + SnapFile.snapbuild == self.id,
> + LibraryFileAlias.id == SnapFile.libraryfile_id,
> + LibraryFileAlias.filename == filename).one()
> +
> + if file_object is not None and file_object.filename == filename:
> + return file_object
> +
> + raise NotFoundError(filename)
> +
> + def addFile(self, lfa):
> + """See `ISnapBuild`."""
> + snapfile = SnapFile(snapbuild=self, libraryfile=lfa)
> + IMasterStore(SnapFile).add(snapfile)
> + return snapfile
> +
> + def verifySuccessfulUpload(self):
> + """See `IPackageBuild`."""
> + return not self.getFiles().is_empty()
> +
> + def lfaUrl(self, lfa):
> + """Return the URL for a LibraryFileAlias in this context."""
> + if lfa is None:
> + return None
> + return ProxiedLibraryFileAlias(lfa, self).http_url
> +
> + @property
> + def log_url(self):
> + """See `IBuildFarmJob`."""
> + return self.lfaUrl(self.log)
> +
> + @property
> + def upload_log_url(self):
> + """See `IPackageBuild`."""
> + return self.lfaUrl(self.upload_log)
> +
> + def getFileUrls(self):
> + return [self.lfaUrl(lfa) for _, lfa, _ in self.getFiles()]
> +
> +
> +@implementer(ISnapBuildSet)
> +class SnapBuildSet(SpecificBuildFarmJobSourceMixin):
> +
> + def new(self, requester, snap, archive, distro_arch_series, pocket,
> + date_created=DEFAULT):
> + """See `ISnapBuildSet`."""
> + store = IMasterStore(SnapBuild)
> + build_farm_job = getUtility(IBuildFarmJobSource).new(
> + SnapBuild.job_type, BuildStatus.NEEDSBUILD, date_created, None,
> + archive)
> + snapbuild = SnapBuild(
> + build_farm_job, requester, snap, archive, distro_arch_series,
> + pocket, distro_arch_series.processor,
> + not distro_arch_series.processor.supports_nonvirtualized
> + or snap.require_virtualized or archive.require_virtualized,
> + date_created)
> + store.add(snapbuild)
> + return snapbuild
> +
> + def getByID(self, build_id):
> + """See `ISpecificBuildFarmJobSource`."""
> + store = IMasterStore(SnapBuild)
> + return store.get(SnapBuild, build_id)
> +
> + def getByBuildFarmJob(self, build_farm_job):
> + """See `ISpecificBuildFarmJobSource`."""
> + return Store.of(build_farm_job).find(
> + SnapBuild, build_farm_job_id=build_farm_job.id).one()
> +
> + def preloadBuildsData(self, builds):
> + # Circular import.
> + from lp.snappy.model.snap import Snap
> + load_related(Person, builds, ["requester_id"])
> + load_related(LibraryFileAlias, builds, ["log_id"])
> + archives = load_related(Archive, builds, ["archive_id"])
> + load_related(Person, archives, ["ownerID"])
> + load_related(Snap, builds, ["snap_id"])
> +
> + def getByBuildFarmJobs(self, build_farm_jobs):
> + """See `ISpecificBuildFarmJobSource`."""
> + if len(build_farm_jobs) == 0:
> + return EmptyResultSet()
> + rows = Store.of(build_farm_jobs[0]).find(
> + SnapBuild, SnapBuild.build_farm_job_id.is_in(
> + bfj.id for bfj in build_farm_jobs))
> + return DecoratedResultSet(rows, pre_iter_hook=self.preloadBuildsData)
--
https://code.launchpad.net/~cjwatson/launchpad/snapbuild-basic-model/+merge/265691
Your team Launchpad code reviewers is subscribed to branch lp:launchpad.
References