~launchpad-pqm/launchpad/devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

# pylint: disable-msg=E0211,E0213

"""Interface for Soyuz build farm jobs."""

__metaclass__ = type

__all__ = [
    'IBuildFarmJob',
    'IBuildFarmJobOld',
    'IBuildFarmJobSet',
    'IBuildFarmJobSource',
    'InconsistentBuildFarmJobError',
    'ISpecificBuildFarmJobSource',
    ]

from lazr.enum import DBEnumeratedType
from lazr.restful.declarations import exported
from lazr.restful.fields import Reference
from zope.interface import (
    Attribute,
    Interface,
    )
from zope.schema import (
    Bool,
    Choice,
    Datetime,
    Int,
    TextLine,
    Timedelta,
    )

from canonical.launchpad import _
from lp.buildmaster.enums import BuildFarmJobType
from lp.buildmaster.interfaces.builder import IBuilder
from lp.services.librarian.interfaces import ILibraryFileAlias
from lp.soyuz.interfaces.processor import IProcessor


class InconsistentBuildFarmJobError(Exception):
    """Raised when a BuildFarmJob is in an inconsistent state.

    For example, if a BuildFarmJob has a job type for which no adapter
    is yet implemented. Or when adapting the BuildFarmJob to a specific
    type of build job (such as a BinaryPackageBuild) fails.
    """


class IBuildFarmJobOld(Interface):
    """Defines the previous non-database BuildFarmJob interface.

    This interface is still used by the temporary build queue related
    classes (TranslationTemplatesBuildJob, SourcePackageRecipeBuildJob
    and BuildPackageJob).

    XXX 2010-04-28 michael.nelson bug=567922
    This class can be removed (merging all the attributes directly into
    IBuildFarmJob) once all the corresponding *Build classes and the
    BuildQueue have been transitioned to the new database schema.
    """
    processor = Reference(
        IProcessor, title=_("Processor"), required=False, readonly=True,
        description=_(
            "The Processor required by this build farm job. "
            "This should be None for processor-independent job types."))

    virtualized = Bool(
        title=_('Virtualized'), required=False, readonly=True,
        description=_(
            "The virtualization setting required by this build farm job. "
            "This should be None for job types that do not care whether "
            "they run virtualized."))

    def score():
        """Calculate a job score appropriate for the job type in question."""

    def getLogFileName():
        """The preferred file name for this job's log."""

    def getName():
        """An appropriate name for this job."""

    def getTitle():
        """A string to identify and describe the job to users."""

    def jobStarted():
        """'Job started' life cycle event, handle as appropriate."""

    def jobReset():
        """'Job reset' life cycle event, handle as appropriate."""

    def jobAborted():
        """'Job aborted' life cycle event, handle as appropriate."""

    def jobCancel():
        """'Job cancel' life cycle event."""

    def addCandidateSelectionCriteria(processor, virtualized):
        """Provide a sub-query to refine the candidate job selection.

        Return a sub-query to narrow down the list of candidate jobs.
        The sub-query will become part of an "outer query" and is free to
        refer to the `BuildQueue` and `Job` tables already utilized in the
        latter.

        Example (please see the `BuildPackageJob` implementation for a
        complete example):

            SELECT TRUE
            FROM Archive, Build, BuildPackageJob, DistroArchSeries
            WHERE
            BuildPackageJob.job = Job.id AND
            ..

        :param processor: the type of processor that the candidate jobs are
            expected to run on.
        :param virtualized: whether the candidate jobs are expected to run on
            the `processor` natively or inside a virtual machine.
        :return: a string containing a sub-query that narrows down the list of
            candidate jobs.
        """

    def postprocessCandidate(job, logger):
        """True if the candidate job is fine and should be dispatched
        to a builder, False otherwise.

        :param job: The `BuildQueue` instance to be scrutinized.
        :param logger: The logger to use.

        :return: True if the candidate job should be dispatched
            to a builder, False otherwise.
        """

    def getByJob(job):
        """Get the specific `IBuildFarmJob` for the given `Job`.

        Invoked on the specific `IBuildFarmJob`-implementing class that
        has an entry associated with `job`.
        """

    def generateSlaveBuildCookie():
        """Produce a cookie for the slave as a token of the job it's doing.

        The cookie need not be unique, but should be hard for a
        compromised slave to guess.

        :return: a hard-to-guess ASCII string that can be reproduced
            accurately based on this job's properties.
        """

    def makeJob():
        """Create the specific job relating this with an lp.services.job.

        XXX 2010-04-26 michael.nelson bug=567922
        Once all *Build classes are using BuildFarmJob we can lose the
        'specific_job' attributes and simply have a reference to the
        services job directly on the BuildFarmJob.
        """

    def cleanUp():
        """Job's finished.  Delete its supporting data."""


class IBuildFarmJob(IBuildFarmJobOld):
    """Operations that jobs for the build farm must implement."""

    id = Attribute('The build farm job ID.')

    date_created = exported(
        Datetime(
            title=_("Date created"), required=True, readonly=True,
            description=_(
                "The timestamp when the build farm job was created.")),
        ("1.0", dict(exported_as="datecreated")),
        as_of="beta",
        )

    date_started = Datetime(
        title=_("Date started"), required=False, readonly=True,
        description=_("The timestamp when the build farm job was started."))

    date_finished = exported(
        Datetime(
            title=_("Date finished"), required=False, readonly=True,
            description=_(
                "The timestamp when the build farm job was finished.")),
        ("1.0", dict(exported_as="datebuilt")),
        as_of="beta",
        )

    duration = Timedelta(
        title=_("Duration"), required=False,
        description=_("Duration interval, calculated when the "
                      "result gets collected."))

    date_first_dispatched = exported(
        Datetime(
            title=_("Date finished"), required=False, readonly=True,
            description=_("The actual build start time. Set when the build "
                          "is dispatched the first time and not changed in "
                          "subsequent build attempts.")))

    builder = Reference(
        title=_("Builder"), schema=IBuilder, required=False, readonly=True,
        description=_("The builder assigned to this job."))

    buildqueue_record = Reference(
        # Really IBuildQueue, set in _schema_circular_imports to avoid
        # circular import.
        schema=Interface, required=True,
        title=_("Corresponding BuildQueue record"))

    status = exported(
        Choice(
            title=_('Status'), required=True,
            # Really BuildStatus, patched in
            # _schema_circular_imports.py
            vocabulary=DBEnumeratedType,
            description=_("The current status of the job.")),
        ("1.0", dict(exported_as="buildstate")),
        as_of="beta",
        )

    log = Reference(
        schema=ILibraryFileAlias, required=False,
        title=_(
            "The LibraryFileAlias containing the entire log for this job."))

    log_url = exported(
        TextLine(
            title=_("Build Log URL"), required=False,
            description=_("A URL for the build log. None if there is no "
                          "log available.")),
        ("1.0", dict(exported_as="build_log_url")),
        as_of="beta",
        )

    is_private = Bool(
        title=_("is private"), required=False, readonly=True,
        description=_("Whether the build should be treated as private."))

    job_type = Choice(
        title=_("Job type"), required=True, readonly=True,
        vocabulary=BuildFarmJobType,
        description=_("The specific type of job."))

    failure_count = Int(
        title=_("Failure Count"), required=False, readonly=True,
        default=0,
        description=_("Number of consecutive failures for this job."))

    def getSpecificJob():
        """Return the specific build job associated with this record.

        :raises InconsistentBuildFarmJobError: if a specific job could not be
            returned.
        """

    def gotFailure():
        """Increment the failure_count for this job."""

    title = exported(TextLine(title=_("Title"), required=False),
                     as_of="beta")

    was_built = Attribute("Whether or not modified by the builddfarm.")

    # This doesn't belong here.  It really belongs in IPackageBuild, but
    # the TAL assumes it can read this directly.
    dependencies = exported(
        TextLine(
            title=_('Dependencies'), required=False,
            description=_(
                'Debian-like dependency line that must be satisfied before '
                'attempting to build this request.')),
        as_of="beta")


class ISpecificBuildFarmJobSource(Interface):
    """A utility for retrieving objects of a specific IBuildFarmJob type.

    Implementations are registered with their BuildFarmJobType's name.
    """

    def getByID(id):
        """Look up a concrete `IBuildFarmJob` by ID.

        :param id: An ID of the concrete job class to look up.
        """

    def getByBuildFarmJobs(build_farm_jobs):
        """"Look up the concrete `IBuildFarmJob`s for a list of BuildFarmJobs.

        :param build_farm_jobs: A list of BuildFarmJobs for which to get the
            concrete jobs.
        """

    def getByBuildFarmJob(build_farm_job):
        """"Look up the concrete `IBuildFarmJob` for a BuildFarmJob.

        :param build_farm_job: A BuildFarmJob for which to get the concrete
            job.
        """


class IBuildFarmJobSource(Interface):
    """A utility of BuildFarmJob used to create _things_."""

    def new(job_type, status=None, processor=None,
            virtualized=None):
        """Create a new `IBuildFarmJob`.

        :param job_type: A `BuildFarmJobType` item.
        :param status: A `BuildStatus` item, defaulting to PENDING.
        :param processor: An optional processor for this job.
        :param virtualized: An optional boolean indicating whether
            this job should be run virtualized.
        """


class IBuildFarmJobSet(Interface):
    """A utility representing a set of build farm jobs."""

    def getBuildsForBuilder(builder_id, status=None, user=None):
        """Return `IBuildFarmJob` records touched by a builder.

        :param builder_id: The id of the builder for which to find builds.
        :param status: If given, limit the search to builds with this status.
        :param user: If given, this will be used to determine private builds
            that should be included.
        :return: a `ResultSet` representing the requested builds.
        """

    def getByID(job_id):
        """Look up a `IBuildFarmJob` record by id.
        """