~launchpad-pqm/launchpad/devel

7675.1143.2 by Jeroen Vermeulen
Spec and test IPlainPackageCopyJobSource.getPendingJobsPerPackage.
1
# Copyright 2010-2011 Canonical Ltd.  This software is licensed under the
11844.2.2 by Jelmer Vernooij
Add creation of sync package jobs.
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
4
__metaclass__ = type
5
6
__all__ = [
12881.3.1 by Gavin Panella
Rename SyncPackageJob to PackageCopyJob everywhere.
7
    "PackageCopyJob",
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
8
    "PlainPackageCopyJob",
13287.2.7 by Jeroen Vermeulen
Lint.
9
    ]
11844.2.2 by Jelmer Vernooij
Add creation of sync package jobs.
10
13699.1.1 by Jeroen Vermeulen
Make getForDistroSeries default "status=None" mean "all statuses."
11
import logging
12
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
13
from lazr.delegates import delegates
14
import simplejson
15
from storm.locals import (
16
    And,
17
    Int,
13891.2.12 by Gavin Panella
Switch back to Storm's JSON property.
18
    JSON,
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
19
    Reference,
20
    Unicode,
21
    )
7675.1166.2 by Jeroen Vermeulen
Outline and test changes needed; don't oops for CannotCopy.
22
import transaction
23
from zope.component import getUtility
11844.2.5 by Jelmer Vernooij
remove unused imports.
24
from zope.interface import (
25
    classProvides,
26
    implements,
27
    )
13287.2.2 by Jeroen Vermeulen
IPackageCopyJobSource.
28
from zope.security.proxy import removeSecurityProxy
11844.2.2 by Jelmer Vernooij
Add creation of sync package jobs.
29
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
30
from canonical.database.enumcol import EnumCol
7675.1190.3 by Jeroen Vermeulen
Implement PlainPackageCopyJob.createMultiple.
31
from canonical.database.sqlbase import sqlvalues
14560.2.29 by Curtis Hovey
Restored lpstorm module name because it lp engineers know that name.
32
from lp.services.database.lpstorm import (
11844.2.4 by Jelmer Vernooij
Add getActiveJobs call.
33
    IMasterStore,
34
    IStore,
35
    )
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
36
from lp.app.errors import NotFoundError
7675.1168.1 by Curtis Hovey
Merged stable to resolve conflicts.
37
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
13699.1.1 by Jeroen Vermeulen
Make getForDistroSeries default "status=None" mean "all statuses."
38
from lp.registry.enum import DistroSeriesDifferenceStatus
7675.1166.2 by Jeroen Vermeulen
Outline and test changes needed; don't oops for CannotCopy.
39
from lp.registry.interfaces.distroseriesdifference import (
40
    IDistroSeriesDifferenceSource,
41
    )
13244.1.2 by Jeroen Vermeulen
Add missing import, and reformat.
42
from lp.registry.interfaces.distroseriesdifferencecomment import (
43
    IDistroSeriesDifferenceCommentSource,
44
    )
14421.2.1 by Julian Edwards
Make PackageCopyJob store an optional "sponsored" person in the metadata.
45
from lp.registry.interfaces.person import IPersonSet
11844.2.2 by Jelmer Vernooij
Add creation of sync package jobs.
46
from lp.registry.interfaces.pocket import PackagePublishingPocket
13246.1.1 by Danilo Segan
Revert r13243.
47
from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
48
from lp.registry.model.distroseries import DistroSeries
14550.1.1 by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad
49
from lp.services.database.decoratedresultset import DecoratedResultSet
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
50
from lp.services.database.stormbase import StormBase
7675.1191.12 by Julian Edwards
Raise a SuspendJobException instead of trying to suspend the job inside the job. The runner will suspend it instead.
51
from lp.services.job.interfaces.job import (
52
    JobStatus,
53
    SuspendJobException,
54
    )
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
55
from lp.services.job.model.job import Job
56
from lp.services.job.runner import BaseRunnableJob
7675.1191.22 by Julian Edwards
ensure that default and ancestry component override is present in job metadata if it suspends after the first iteration
57
from lp.soyuz.adapters.overrides import (
58
    FromExistingOverridePolicy,
7675.1191.27 by Julian Edwards
merge the override-class branch that adds IOverride
59
    SourceOverride,
7675.1191.22 by Julian Edwards
ensure that default and ancestry component override is present in job metadata if it suspends after the first iteration
60
    UnknownOverridePolicy,
61
    )
7675.1176.4 by Jeroen Vermeulen
Create enum for copy policies.
62
from lp.soyuz.enums import PackageCopyPolicy
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
63
from lp.soyuz.interfaces.archive import CannotCopy
7675.1181.7 by Julian Edwards
add getSourceOverride to PCJ
64
from lp.soyuz.interfaces.component import IComponentSet
7675.1176.12 by Jeroen Vermeulen
Set PlainPackageCopyJob.copy_policy.
65
from lp.soyuz.interfaces.copypolicy import ICopyPolicy
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
66
from lp.soyuz.interfaces.packagecopyjob import (
12881.3.1 by Gavin Panella
Rename SyncPackageJob to PackageCopyJob everywhere.
67
    IPackageCopyJob,
13287.2.2 by Jeroen Vermeulen
IPackageCopyJobSource.
68
    IPackageCopyJobSource,
7675.1130.3 by Gavin Panella
A few tweaks and all but one test passes.
69
    IPlainPackageCopyJob,
70
    IPlainPackageCopyJobSource,
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
71
    PackageCopyJobType,
72
    )
14241.1.2 by Julian Edwards
Make PackageCopyJob generate a packagediff
73
from lp.soyuz.interfaces.packagediff import PackageDiffAlreadyRequested
7675.1191.30 by Julian Edwards
merge db-devel, there are some test failures as a result
74
from lp.soyuz.interfaces.queue import IPackageUploadSet
7675.1181.7 by Julian Edwards
add getSourceOverride to PCJ
75
from lp.soyuz.interfaces.section import ISectionSet
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
76
from lp.soyuz.model.archive import Archive
12881.3.8 by Gavin Panella
Switch to using do_copy().
77
from lp.soyuz.scripts.packagecopier import do_copy
11844.2.2 by Jelmer Vernooij
Add creation of sync package jobs.
78
79
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
80
class PackageCopyJob(StormBase):
81
    """Base class for package copying jobs."""
82
83
    implements(IPackageCopyJob)
13287.2.2 by Jeroen Vermeulen
IPackageCopyJobSource.
84
    classProvides(IPackageCopyJobSource)
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
85
86
    __storm_table__ = 'PackageCopyJob'
87
88
    id = Int(primary=True)
89
90
    job_id = Int(name='job')
91
    job = Reference(job_id, Job.id)
92
93
    source_archive_id = Int(name='source_archive')
94
    source_archive = Reference(source_archive_id, Archive.id)
95
96
    target_archive_id = Int(name='target_archive')
97
    target_archive = Reference(target_archive_id, Archive.id)
98
7675.1130.3 by Gavin Panella
A few tweaks and all but one test passes.
99
    target_distroseries_id = Int(name='target_distroseries')
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
100
    target_distroseries = Reference(target_distroseries_id, DistroSeries.id)
101
7675.1176.1 by Jeroen Vermeulen
Move PackageCopyJob package name into db column, add copy_policy.
102
    package_name = Unicode('package_name')
7675.1176.4 by Jeroen Vermeulen
Create enum for copy policies.
103
    copy_policy = EnumCol(enum=PackageCopyPolicy)
7675.1176.1 by Jeroen Vermeulen
Move PackageCopyJob package name into db column, add copy_policy.
104
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
105
    job_type = EnumCol(enum=PackageCopyJobType, notNull=True)
106
13891.2.6 by Gavin Panella
Borrow the JSON property from lp:~allenap/storm/json-variable-unicode-bug-846867.
107
    metadata = JSON('json_data')
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
108
13287.2.2 by Jeroen Vermeulen
IPackageCopyJobSource.
109
    # Derived concrete classes.  The entire class gets one dict for
110
    # this; it's not meant to be on an instance.
111
    concrete_classes = {}
112
113
    @classmethod
114
    def registerConcreteClass(cls, new_class):
115
        """Register a concrete `IPackageCopyJob`-implementing class."""
116
        assert new_class.class_job_type not in cls.concrete_classes, (
117
            "Class %s is already registered." % new_class)
118
        cls.concrete_classes[new_class.class_job_type] = new_class
119
120
    @classmethod
121
    def wrap(cls, package_copy_job):
122
        """See `IPackageCopyJobSource`."""
123
        if package_copy_job is None:
124
            return None
13287.2.4 by Jeroen Vermeulen
Commented an rSP, as per review.
125
        # Read job_type so You Don't Have To.  If any other code reads
126
        # job_type, that's probably a sign that the interfaces need more
127
        # work.
13287.2.2 by Jeroen Vermeulen
IPackageCopyJobSource.
128
        job_type = removeSecurityProxy(package_copy_job).job_type
129
        concrete_class = cls.concrete_classes[job_type]
130
        return concrete_class(package_copy_job)
131
132
    @classmethod
133
    def getByID(cls, pcj_id):
134
        """See `IPackageCopyJobSource`."""
135
        return cls.wrap(IStore(PackageCopyJob).get(PackageCopyJob, pcj_id))
136
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
137
    def __init__(self, source_archive, target_archive, target_distroseries,
13444.3.4 by Julian Edwards
Create single packagecopyjobs with a requester.
138
                 job_type, metadata, requester, package_name=None,
139
                 copy_policy=None):
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
140
        super(PackageCopyJob, self).__init__()
141
        self.job = Job()
13444.3.4 by Julian Edwards
Create single packagecopyjobs with a requester.
142
        self.job.requester = requester
7675.1176.1 by Jeroen Vermeulen
Move PackageCopyJob package name into db column, add copy_policy.
143
        self.job_type = job_type
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
144
        self.source_archive = source_archive
145
        self.target_archive = target_archive
146
        self.target_distroseries = target_distroseries
7675.1176.1 by Jeroen Vermeulen
Move PackageCopyJob package name into db column, add copy_policy.
147
        self.package_name = unicode(package_name)
148
        self.copy_policy = copy_policy
13891.2.6 by Gavin Panella
Borrow the JSON property from lp:~allenap/storm/json-variable-unicode-bug-846867.
149
        self.metadata = metadata
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
150
13222.3.4 by Jeroen Vermeulen
Make IPackageCopyJob available even on package copy jobs of unknown subtype.
151
    @property
152
    def package_version(self):
153
        return self.metadata["package_version"]
154
7675.1171.2 by Julian Edwards
add code to set override data on packageupload with a packagecopyjob
155
    def extendMetadata(self, metadata_dict):
156
        """Add metadata_dict to the existing metadata."""
157
        existing = self.metadata
158
        existing.update(metadata_dict)
13891.2.6 by Gavin Panella
Borrow the JSON property from lp:~allenap/storm/json-variable-unicode-bug-846867.
159
        self.metadata = existing
7675.1171.2 by Julian Edwards
add code to set override data on packageupload with a packagecopyjob
160
13287.2.2 by Jeroen Vermeulen
IPackageCopyJobSource.
161
    @property
162
    def component_name(self):
163
        """See `IPackageCopyJob`."""
164
        return self.metadata.get("component_override")
165
166
    @property
167
    def section_name(self):
168
        """See `IPackageCopyJob`."""
169
        return self.metadata.get("section_override")
170
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
171
172
class PackageCopyJobDerived(BaseRunnableJob):
173
    """Abstract class for deriving from PackageCopyJob."""
174
175
    delegates(IPackageCopyJob)
176
177
    def __init__(self, job):
178
        self.context = job
179
180
    @classmethod
181
    def get(cls, job_id):
182
        """Get a job by id.
183
184
        :return: the PackageCopyJob with the specified id, as the current
185
            PackageCopyJobDerived subclass.
186
        :raises: NotFoundError if there is no job with the specified id, or
187
            its job_type does not match the desired subclass.
188
        """
7675.1175.2 by Julian Edwards
Future proof for different job types
189
        job = IStore(PackageCopyJob).get(PackageCopyJob, job_id)
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
190
        if job.job_type != cls.class_job_type:
191
            raise NotFoundError(
192
                'No object found with id %d and type %s' % (job_id,
193
                cls.class_job_type.title))
194
        return cls(job)
195
196
    @classmethod
197
    def iterReady(cls):
198
        """Iterate through all ready PackageCopyJobs."""
199
        jobs = IStore(PackageCopyJob).find(
200
            PackageCopyJob,
201
            And(PackageCopyJob.job_type == cls.class_job_type,
202
                PackageCopyJob.job == Job.id,
203
                Job.id.is_in(Job.ready_jobs)))
204
        return (cls(job) for job in jobs)
205
206
    def getOopsVars(self):
207
        """See `IRunnableJob`."""
208
        vars = super(PackageCopyJobDerived, self).getOopsVars()
209
        vars.extend([
210
            ('source_archive_id', self.context.source_archive_id),
211
            ('target_archive_id', self.context.target_archive_id),
212
            ('target_distroseries_id', self.context.target_distroseries_id),
213
            ('package_copy_job_id', self.context.id),
214
            ('package_copy_job_type', self.context.job_type.title),
215
            ])
216
        return vars
217
7675.1176.12 by Jeroen Vermeulen
Set PlainPackageCopyJob.copy_policy.
218
    @property
219
    def copy_policy(self):
220
        """See `PlainPackageCopyJob`."""
221
        return self.context.copy_policy
222
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
223
224
class PlainPackageCopyJob(PackageCopyJobDerived):
7675.1176.1 by Jeroen Vermeulen
Move PackageCopyJob package name into db column, add copy_policy.
225
    """Job that copies a package from one archive to another."""
7675.1166.10 by Jeroen Vermeulen
Added comment as per Gavin.
226
    # This job type serves in different places: it supports copying
227
    # packages between archives, but also the syncing of packages from
228
    # parents into a derived distroseries.  We may split these into
229
    # separate types at some point, but for now we (allenap, bigjools,
230
    # jtv) chose to keep it as one.
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
231
7675.1130.3 by Gavin Panella
A few tweaks and all but one test passes.
232
    implements(IPlainPackageCopyJob)
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
233
234
    class_job_type = PackageCopyJobType.PLAIN
7675.1130.3 by Gavin Panella
A few tweaks and all but one test passes.
235
    classProvides(IPlainPackageCopyJobSource)
11844.2.2 by Jelmer Vernooij
Add creation of sync package jobs.
236
237
    @classmethod
14421.2.1 by Julian Edwards
Make PackageCopyJob store an optional "sponsored" person in the metadata.
238
    def _makeMetadata(cls, target_pocket, package_version,
239
                      include_binaries, sponsored=None):
13222.3.4 by Jeroen Vermeulen
Make IPackageCopyJob available even on package copy jobs of unknown subtype.
240
        """Produce a metadata dict for this job."""
14421.2.1 by Julian Edwards
Make PackageCopyJob store an optional "sponsored" person in the metadata.
241
        if sponsored:
242
            sponsored_name = sponsored.name
243
        else:
244
            sponsored_name = None
7675.1190.3 by Jeroen Vermeulen
Implement PlainPackageCopyJob.createMultiple.
245
        return {
246
            'target_pocket': target_pocket.value,
247
            'package_version': package_version,
248
            'include_binaries': bool(include_binaries),
14421.2.1 by Julian Edwards
Make PackageCopyJob store an optional "sponsored" person in the metadata.
249
            'sponsored': sponsored_name,
7675.1190.3 by Jeroen Vermeulen
Implement PlainPackageCopyJob.createMultiple.
250
        }
251
252
    @classmethod
7675.1176.1 by Jeroen Vermeulen
Move PackageCopyJob package name into db column, add copy_policy.
253
    def create(cls, package_name, source_archive,
12881.3.5 by Gavin Panella
Accept multiple source packages into PackageCopyJob.create().
254
               target_archive, target_distroseries, target_pocket,
7675.1176.1 by Jeroen Vermeulen
Move PackageCopyJob package name into db column, add copy_policy.
255
               include_binaries=False, package_version=None,
14421.2.1 by Julian Edwards
Make PackageCopyJob store an optional "sponsored" person in the metadata.
256
               copy_policy=PackageCopyPolicy.INSECURE, requester=None,
257
               sponsored=None):
7675.1130.3 by Gavin Panella
A few tweaks and all but one test passes.
258
        """See `IPlainPackageCopyJobSource`."""
7675.1176.1 by Jeroen Vermeulen
Move PackageCopyJob package name into db column, add copy_policy.
259
        assert package_version is not None, "No package version specified."
13444.3.4 by Julian Edwards
Create single packagecopyjobs with a requester.
260
        assert requester is not None, "No requester specified."
7675.1190.3 by Jeroen Vermeulen
Implement PlainPackageCopyJob.createMultiple.
261
        metadata = cls._makeMetadata(
14421.2.1 by Julian Edwards
Make PackageCopyJob store an optional "sponsored" person in the metadata.
262
            target_pocket, package_version, include_binaries, sponsored)
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
263
        job = PackageCopyJob(
7675.1176.1 by Jeroen Vermeulen
Move PackageCopyJob package name into db column, add copy_policy.
264
            job_type=cls.class_job_type,
7675.1130.3 by Gavin Panella
A few tweaks and all but one test passes.
265
            source_archive=source_archive,
266
            target_archive=target_archive,
267
            target_distroseries=target_distroseries,
7675.1176.1 by Jeroen Vermeulen
Move PackageCopyJob package name into db column, add copy_policy.
268
            package_name=package_name,
269
            copy_policy=copy_policy,
13444.3.4 by Julian Edwards
Create single packagecopyjobs with a requester.
270
            metadata=metadata,
271
            requester=requester)
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
272
        IMasterStore(PackageCopyJob).add(job)
11844.2.2 by Jelmer Vernooij
Add creation of sync package jobs.
273
        return cls(job)
274
11844.2.4 by Jelmer Vernooij
Add getActiveJobs call.
275
    @classmethod
7675.1190.3 by Jeroen Vermeulen
Implement PlainPackageCopyJob.createMultiple.
276
    def _composeJobInsertionTuple(cls, target_distroseries, copy_policy,
14421.2.4 by Julian Edwards
Fix IArchive.copyPackage[s] to supply a sponsor to the packagecopier.
277
                                  include_binaries, job_id, copy_task,
278
                                  sponsored):
7675.1190.3 by Jeroen Vermeulen
Implement PlainPackageCopyJob.createMultiple.
279
        """Create an SQL fragment for inserting a job into the database.
280
281
        :return: A string representing an SQL tuple containing initializers
282
            for a `PackageCopyJob` in the database (minus `id`, which is
283
            assigned automatically).  Contents are escaped for use in SQL.
284
        """
285
        (
286
            package_name,
287
            package_version,
288
            source_archive,
289
            target_archive,
290
            target_pocket,
291
        ) = copy_task
292
        metadata = cls._makeMetadata(
14421.2.4 by Julian Edwards
Fix IArchive.copyPackage[s] to supply a sponsor to the packagecopier.
293
            target_pocket, package_version, include_binaries, sponsored)
7675.1190.3 by Jeroen Vermeulen
Implement PlainPackageCopyJob.createMultiple.
294
        data = (
295
            cls.class_job_type, target_distroseries, copy_policy,
296
            source_archive, target_archive, package_name, job_id,
13891.2.6 by Gavin Panella
Borrow the JSON property from lp:~allenap/storm/json-variable-unicode-bug-846867.
297
            simplejson.dumps(metadata, ensure_ascii=False))
7675.1190.3 by Jeroen Vermeulen
Implement PlainPackageCopyJob.createMultiple.
298
        format_string = "(%s)" % ", ".join(["%s"] * len(data))
299
        return format_string % sqlvalues(*data)
300
301
    @classmethod
13444.3.5 by Julian Edwards
createMultiple now stores the requester.
302
    def createMultiple(cls, target_distroseries, copy_tasks, requester,
7675.1190.2 by Jeroen Vermeulen
Sketch out IPlainPackageCopyJobSource.createMultiple.
303
                       copy_policy=PackageCopyPolicy.INSECURE,
14421.2.4 by Julian Edwards
Fix IArchive.copyPackage[s] to supply a sponsor to the packagecopier.
304
                       include_binaries=False, sponsored=None):
7675.1190.2 by Jeroen Vermeulen
Sketch out IPlainPackageCopyJobSource.createMultiple.
305
        """See `IPlainPackageCopyJobSource`."""
306
        store = IMasterStore(Job)
13444.3.5 by Julian Edwards
createMultiple now stores the requester.
307
        job_ids = Job.createMultiple(store, len(copy_tasks), requester)
7675.1190.3 by Jeroen Vermeulen
Implement PlainPackageCopyJob.createMultiple.
308
        job_contents = [
7675.1190.6 by Jeroen Vermeulen
Fixed up test, satisfied it.
309
            cls._composeJobInsertionTuple(
7675.1190.3 by Jeroen Vermeulen
Implement PlainPackageCopyJob.createMultiple.
310
                target_distroseries, copy_policy, include_binaries, job_id,
14421.2.4 by Julian Edwards
Fix IArchive.copyPackage[s] to supply a sponsor to the packagecopier.
311
                task, sponsored)
7675.1190.3 by Jeroen Vermeulen
Implement PlainPackageCopyJob.createMultiple.
312
            for job_id, task in zip(job_ids, copy_tasks)]
313
        result = store.execute("""
314
            INSERT INTO PackageCopyJob (
315
                job_type,
316
                target_distroseries,
317
                copy_policy,
318
                source_archive,
319
                target_archive,
320
                package_name,
321
                job,
322
                json_data)
323
            VALUES %s
324
            RETURNING id
325
            """ % ", ".join(job_contents))
326
        return [job_id for job_id, in result]
7675.1190.2 by Jeroen Vermeulen
Sketch out IPlainPackageCopyJobSource.createMultiple.
327
328
    @classmethod
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
329
    def getActiveJobs(cls, target_archive):
7675.1130.3 by Gavin Panella
A few tweaks and all but one test passes.
330
        """See `IPlainPackageCopyJobSource`."""
331
        jobs = IStore(PackageCopyJob).find(
7675.1130.2 by Gavin Panella
Create the PackageCopyJob model class, and update dependencies of it.
332
            PackageCopyJob,
333
            PackageCopyJob.job_type == cls.class_job_type,
7675.1144.1 by Jeroen Vermeulen
Sketch out test and fix.
334
            PackageCopyJob.target_archive == target_archive,
335
            Job.id == PackageCopyJob.job_id,
7675.1144.2 by Jeroen Vermeulen
Ah yes, Job._status not Job.status.
336
            Job._status == JobStatus.WAITING)
7675.1144.4 by Jeroen Vermeulen
Missing order_by... where did I lose it?
337
        jobs = jobs.order_by(PackageCopyJob.id)
7675.1130.3 by Gavin Panella
A few tweaks and all but one test passes.
338
        return DecoratedResultSet(jobs, cls)
11844.2.4 by Jelmer Vernooij
Add getActiveJobs call.
339
7675.1143.2 by Jeroen Vermeulen
Spec and test IPlainPackageCopyJobSource.getPendingJobsPerPackage.
340
    @classmethod
7675.1143.3 by Jeroen Vermeulen
Implemented getPendingJobsPerPackage, but still 1 failing test.
341
    def getPendingJobsForTargetSeries(cls, target_series):
342
        """Get upcoming jobs for `target_series`, ordered by age."""
343
        raw_jobs = IStore(PackageCopyJob).find(
344
            PackageCopyJob,
345
            Job.id == PackageCopyJob.job_id,
346
            PackageCopyJob.job_type == cls.class_job_type,
347
            PackageCopyJob.target_distroseries == target_series,
7675.1143.8 by Jeroen Vermeulen
Review changes.
348
            Job._status.is_in(Job.PENDING_STATUSES))
349
        raw_jobs = raw_jobs.order_by(PackageCopyJob.id)
7675.1143.3 by Jeroen Vermeulen
Implemented getPendingJobsPerPackage, but still 1 failing test.
350
        return DecoratedResultSet(raw_jobs, cls)
351
352
    @classmethod
7675.1143.2 by Jeroen Vermeulen
Spec and test IPlainPackageCopyJobSource.getPendingJobsPerPackage.
353
    def getPendingJobsPerPackage(cls, target_series):
354
        """See `IPlainPackageCopyJobSource`."""
7675.1143.3 by Jeroen Vermeulen
Implemented getPendingJobsPerPackage, but still 1 failing test.
355
        result = {}
7675.1143.5 by Jeroen Vermeulen
Also prepare for version check, and show Wait icon for dsds with pending syncs.
356
        # Go through jobs in-order, picking the first matching job for
357
        # any (package, version) tuple.  Because of how
358
        # getPendingJobsForTargetSeries orders its results, the first
359
        # will be the oldest and thus presumably the first to finish.
7675.1143.3 by Jeroen Vermeulen
Implemented getPendingJobsPerPackage, but still 1 failing test.
360
        for job in cls.getPendingJobsForTargetSeries(target_series):
7675.1176.1 by Jeroen Vermeulen
Move PackageCopyJob package name into db column, add copy_policy.
361
            result.setdefault(job.package_name, job)
7675.1143.3 by Jeroen Vermeulen
Implemented getPendingJobsPerPackage, but still 1 failing test.
362
        return result
7675.1143.2 by Jeroen Vermeulen
Spec and test IPlainPackageCopyJobSource.getPendingJobsPerPackage.
363
14579.1.1 by Julian Edwards
Add PackageCopyJob.getIncompleteJobsForArchive() method in preparation for showing them on the PPA page
364
    @classmethod
365
    def getIncompleteJobsForArchive(cls, archive):
366
        """See `IPlainPackageCopyJobSource`."""
367
        jobs = IStore(PackageCopyJob).find(
368
            PackageCopyJob,
369
            PackageCopyJob.target_archive == archive,
370
            PackageCopyJob.job_type == cls.class_job_type,
371
            Job.id == PackageCopyJob.job_id,
372
            Job._status.is_in(
373
                [JobStatus.WAITING, JobStatus.RUNNING, JobStatus.FAILED])
374
            )
375
        return DecoratedResultSet(jobs, cls)
376
12881.3.6 by Gavin Panella
Ditch source_package_name and source_package_version fields, and just go with source_packages.
377
    @property
12881.3.7 by Gavin Panella
Rename pocket to target_pocket.
378
    def target_pocket(self):
12881.3.5 by Gavin Panella
Accept multiple source packages into PackageCopyJob.create().
379
        return PackagePublishingPocket.items[self.metadata['target_pocket']]
11844.2.2 by Jelmer Vernooij
Add creation of sync package jobs.
380
381
    @property
382
    def include_binaries(self):
383
        return self.metadata['include_binaries']
384
14421.2.1 by Julian Edwards
Make PackageCopyJob store an optional "sponsored" person in the metadata.
385
    @property
386
    def sponsored(self):
14421.2.7 by Julian Edwards
fix lint
387
        name = self.metadata['sponsored']
14421.2.1 by Julian Edwards
Make PackageCopyJob store an optional "sponsored" person in the metadata.
388
        if name is None:
389
            return None
390
        return getUtility(IPersonSet).getByName(name)
391
7675.1191.15 by Julian Edwards
refactor packageupload creation
392
    def _createPackageUpload(self, unapproved=False):
393
        pu = self.target_distroseries.createQueueEntry(
7675.1191.38 by Julian Edwards
Remove the fake changesfile code, devel is fixed to cope without now
394
            pocket=self.target_pocket, archive=self.target_archive,
7675.1191.15 by Julian Edwards
refactor packageupload creation
395
            package_copy_job=self.context)
396
        if unapproved:
397
            pu.setUnapproved()
398
7675.1181.6 by Julian Edwards
Add a addSourceOverride to packagecopyjob
399
    def addSourceOverride(self, override):
400
        """Add an `ISourceOverride` to the metadata."""
13366.1.4 by Jeroen Vermeulen
Ignore None component/section on sync-upload source override.
401
        metadata_changes = {}
7675.1191.31 by Julian Edwards
Fix test failures caused by db-devel merge
402
        if override.component is not None:
13366.1.4 by Jeroen Vermeulen
Ignore None component/section on sync-upload source override.
403
            metadata_changes['component_override'] = override.component.name
7675.1191.31 by Julian Edwards
Fix test failures caused by db-devel merge
404
        if override.section is not None:
13366.1.4 by Jeroen Vermeulen
Ignore None component/section on sync-upload source override.
405
            metadata_changes['section_override'] = override.section.name
406
        self.context.extendMetadata(metadata_changes)
7675.1191.22 by Julian Edwards
ensure that default and ancestry component override is present in job metadata if it suspends after the first iteration
407
7675.1181.7 by Julian Edwards
add getSourceOverride to PCJ
408
    def getSourceOverride(self):
409
        """Fetch an `ISourceOverride` from the metadata."""
7675.1181.12 by Julian Edwards
fix bustage caused by merging db-devel that had jtv's changes to the job schema.
410
        name = self.package_name
13287.2.2 by Jeroen Vermeulen
IPackageCopyJobSource.
411
        component_name = self.component_name
412
        section_name = self.section_name
7675.1181.7 by Julian Edwards
add getSourceOverride to PCJ
413
        source_package_name = getUtility(ISourcePackageNameSet)[name]
7675.1191.34 by Julian Edwards
fix a bunch of post-merge test failures
414
        try:
415
            component = getUtility(IComponentSet)[component_name]
416
        except NotFoundError:
417
            component = None
418
        try:
419
            section = getUtility(ISectionSet)[section_name]
420
        except NotFoundError:
421
            section = None
422
7675.1181.7 by Julian Edwards
add getSourceOverride to PCJ
423
        return SourceOverride(source_package_name, component, section)
424
13751.3.1 by Raphael Badin
Change UnknownOverridePolicy to deal with sources from debian.
425
    def _checkPolicies(self, source_name, source_component=None):
7675.1191.16 by Julian Edwards
merciless refactoring of the job's run method.
426
        # This helper will only return if it's safe to carry on with the
427
        # copy, otherwise it raises SuspendJobException to tell the job
428
        # runner to suspend the job.
7675.1191.6 by Julian Edwards
attempt to start fixing the job run code to check the ancestry and suspend the job, but it isn't working yet
429
        override_policy = FromExistingOverridePolicy()
430
        ancestry = override_policy.calculateSourceOverrides(
431
            self.target_archive, self.target_distroseries,
7675.1191.30 by Julian Edwards
merge db-devel, there are some test failures as a result
432
            self.target_pocket, [source_name])
7675.1191.6 by Julian Edwards
attempt to start fixing the job run code to check the ancestry and suspend the job, but it isn't working yet
433
7675.1191.34 by Julian Edwards
fix a bunch of post-merge test failures
434
        copy_policy = self.getPolicyImplementation()
435
436
        if len(ancestry) == 0:
7675.1191.22 by Julian Edwards
ensure that default and ancestry component override is present in job metadata if it suspends after the first iteration
437
            # We need to get the default overrides and put them in the
438
            # metadata.
439
            defaults = UnknownOverridePolicy().calculateSourceOverrides(
440
                self.target_archive, self.target_distroseries,
13751.3.1 by Raphael Badin
Change UnknownOverridePolicy to deal with sources from debian.
441
                self.target_pocket, [source_name], source_component)
7675.1191.27 by Julian Edwards
merge the override-class branch that adds IOverride
442
            self.addSourceOverride(defaults[0])
7675.1191.22 by Julian Edwards
ensure that default and ancestry component override is present in job metadata if it suspends after the first iteration
443
7675.1191.34 by Julian Edwards
fix a bunch of post-merge test failures
444
            approve_new = copy_policy.autoApproveNew(
445
                self.target_archive, self.target_distroseries,
446
                self.target_pocket)
447
448
            if not approve_new:
449
                # There's no existing package with the same name and the
450
                # policy says unapproved, so we poke it in the NEW queue.
451
                self._createPackageUpload()
452
                raise SuspendJobException
453
        else:
454
            # Put the existing override in the metadata.
455
            self.addSourceOverride(ancestry[0])
7675.1191.6 by Julian Edwards
attempt to start fixing the job run code to check the ancestry and suspend the job, but it isn't working yet
456
7675.1191.10 by Julian Edwards
package copy job will now hold packages in UNAPPROVED per the copy policy
457
        # The package is not new (it has ancestry) so check the copy
458
        # policy for existing packages.
459
        approve_existing = copy_policy.autoApprove(
460
            self.target_archive, self.target_distroseries, self.target_pocket)
461
        if not approve_existing:
7675.1191.15 by Julian Edwards
refactor packageupload creation
462
            self._createPackageUpload(unapproved=True)
7675.1191.12 by Julian Edwards
Raise a SuspendJobException instead of trying to suspend the job inside the job. The runner will suspend it instead.
463
            raise SuspendJobException
7675.1191.10 by Julian Edwards
package copy job will now hold packages in UNAPPROVED per the copy policy
464
13474.2.3 by Julian Edwards
Tweak a comment
465
    def _rejectPackageUpload(self):
466
        # Helper to find and reject any associated PackageUpload.
467
        pu = getUtility(IPackageUploadSet).getByPackageCopyJobIDs(
468
            [self.context.id]).any()
469
        if pu is not None:
470
            pu.setRejected()
471
7675.1191.16 by Julian Edwards
merciless refactoring of the job's run method.
472
    def run(self):
473
        """See `IRunnableJob`."""
7675.1166.2 by Jeroen Vermeulen
Outline and test changes needed; don't oops for CannotCopy.
474
        try:
475
            self.attemptCopy()
476
        except CannotCopy, e:
13474.2.4 by Julian Edwards
Fix breakage by re-raising SuspendJobException.
477
            logger = logging.getLogger()
13474.2.6 by Julian Edwards
Fix lint.
478
            logger.info("Job:\n%s\nraised CannotCopy:\n%s" % (self, e))
13474.2.2 by Julian Edwards
Make the PackageUpload get rejected if CannotCopy is raised.
479
            self.abort()  # Abort the txn.
7675.1166.2 by Jeroen Vermeulen
Outline and test changes needed; don't oops for CannotCopy.
480
            self.reportFailure(e)
481
13474.2.2 by Julian Edwards
Make the PackageUpload get rejected if CannotCopy is raised.
482
            # If there is an associated PackageUpload we need to reject it,
483
            # else it will sit in ACCEPTED forever.
13474.2.3 by Julian Edwards
Tweak a comment
484
            self._rejectPackageUpload()
13474.2.2 by Julian Edwards
Make the PackageUpload get rejected if CannotCopy is raised.
485
486
            # Rely on the job runner to do the final commit.  Note that
487
            # we're not raising any exceptions here, failure of a copy is
488
            # not a failure of the job.
13474.2.4 by Julian Edwards
Fix breakage by re-raising SuspendJobException.
489
        except SuspendJobException:
490
            raise
13474.2.3 by Julian Edwards
Tweak a comment
491
        except:
13516.1.4 by Julian Edwards
Make packagecopyjob commit the rejection to the PackageUpload if the job fails.
492
            # Abort work done so far, but make sure that we commit the
493
            # rejection to the PackageUpload.
494
            transaction.abort()
13474.2.3 by Julian Edwards
Tweak a comment
495
            self._rejectPackageUpload()
13516.1.4 by Julian Edwards
Make packagecopyjob commit the rejection to the PackageUpload if the job fails.
496
            transaction.commit()
13474.2.3 by Julian Edwards
Tweak a comment
497
            raise
13474.2.2 by Julian Edwards
Make the PackageUpload get rejected if CannotCopy is raised.
498
7675.1166.2 by Jeroen Vermeulen
Outline and test changes needed; don't oops for CannotCopy.
499
    def attemptCopy(self):
500
        """Attempt to perform the copy.
501
502
        :raise CannotCopy: If the copy fails for a reason that the user
503
            can deal with.
504
        """
7675.1191.16 by Julian Edwards
merciless refactoring of the job's run method.
505
        if self.target_archive.is_ppa:
506
            if self.target_pocket != PackagePublishingPocket.RELEASE:
507
                raise CannotCopy(
508
                    "Destination pocket must be 'release' for a PPA.")
509
7675.1176.1 by Jeroen Vermeulen
Move PackageCopyJob package name into db column, add copy_policy.
510
        name = self.package_name
511
        version = self.package_version
512
        source_package = self.source_archive.getPublishedSources(
513
            name=name, version=version, exact_match=True).first()
514
        if source_package is None:
515
            raise CannotCopy("Package %r %r not found." % (name, version))
7675.1191.30 by Julian Edwards
merge db-devel, there are some test failures as a result
516
        source_name = getUtility(ISourcePackageNameSet)[name]
7675.1191.16 by Julian Edwards
merciless refactoring of the job's run method.
517
518
        # If there's a PackageUpload associated with this job then this
519
        # job has just been released by an archive admin from the queue.
520
        # We don't need to check any policies, but the admin may have
521
        # set overrides which we will get from the job's metadata.
522
        pu = getUtility(IPackageUploadSet).getByPackageCopyJobIDs(
13382.1.1 by Julian Edwards
Make the PackageCopyJob runner responsible for setting PackageUpload's
523
            [self.context.id]).any()
524
        if pu is None:
13751.3.1 by Raphael Badin
Change UnknownOverridePolicy to deal with sources from debian.
525
            self._checkPolicies(
526
                source_name, source_package.sourcepackagerelease.component)
7675.1191.16 by Julian Edwards
merciless refactoring of the job's run method.
527
7675.1191.10 by Julian Edwards
package copy job will now hold packages in UNAPPROVED per the copy policy
528
        # The package is free to go right in, so just copy it now.
14241.1.2 by Julian Edwards
Make PackageCopyJob generate a packagediff
529
        ancestry = self.target_archive.getPublishedSources(
14241.1.3 by Julian Edwards
fix test breakage
530
            name=name, distroseries=self.target_distroseries,
14241.1.2 by Julian Edwards
Make PackageCopyJob generate a packagediff
531
            pocket=self.target_pocket, exact_match=True).first()
7675.1191.32 by Julian Edwards
pick up overrides from the job and pass them to do_copy
532
        override = self.getSourceOverride()
7675.1191.36 by Julian Edwards
Add email notifications to package copy jobs
533
        copy_policy = self.getPolicyImplementation()
7675.1191.39 by Julian Edwards
fix send_email usage
534
        send_email = copy_policy.send_email(self.target_archive)
14241.1.2 by Julian Edwards
Make PackageCopyJob generate a packagediff
535
        copied_sources = do_copy(
7675.1176.1 by Jeroen Vermeulen
Move PackageCopyJob package name into db column, add copy_policy.
536
            sources=[source_package], archive=self.target_archive,
12881.3.11 by Gavin Panella
Add PackageCopyJob.target_distroseries.
537
            series=self.target_distroseries, pocket=self.target_pocket,
13410.1.23 by Julian Edwards
Force packagecopyjob to pass the requester and check permissions when
538
            include_binaries=self.include_binaries, check_permissions=True,
539
            person=self.requester, overrides=[override],
14421.2.3 by Julian Edwards
PCJ should pass sponsored to the packagecopier.
540
            send_email=send_email, announce_from_person=self.requester,
541
            sponsored=self.sponsored)
7675.1130.3 by Gavin Panella
A few tweaks and all but one test passes.
542
14241.1.2 by Julian Edwards
Make PackageCopyJob generate a packagediff
543
        # Add a PackageDiff for this new upload if it has ancestry.
544
        if ancestry is not None:
545
            to_sourcepackagerelease = ancestry.sourcepackagerelease
546
            copied_source = copied_sources[0]
547
            try:
14291.1.2 by Jeroen Vermeulen
Lint.
548
                to_sourcepackagerelease.requestDiffTo(
14241.1.2 by Julian Edwards
Make PackageCopyJob generate a packagediff
549
                    self.requester, copied_source.sourcepackagerelease)
550
            except PackageDiffAlreadyRequested:
551
                pass
552
13382.1.1 by Julian Edwards
Make the PackageCopyJob runner responsible for setting PackageUpload's
553
        if pu is not None:
554
            # A PackageUpload will only exist if the copy job had to be
555
            # held in the queue because of policy/ancestry checks.  If one
13410.1.13 by Julian Edwards
drive-by comment fix
556
            # does exist we need to make sure it gets moved to DONE.
13382.1.1 by Julian Edwards
Make the PackageCopyJob runner responsible for setting PackageUpload's
557
            pu.setDone()
558
7675.1166.2 by Jeroen Vermeulen
Outline and test changes needed; don't oops for CannotCopy.
559
    def abort(self):
560
        """Abort work."""
561
        transaction.abort()
562
7675.1166.8 by Jeroen Vermeulen
Merge latest, resolve conflicts, drop unused parameter.
563
    def findMatchingDSDs(self):
7675.1166.4 by Jeroen Vermeulen
Test and adjust database privileges.
564
        """Find any `DistroSeriesDifference`s that this job might resolve."""
7675.1166.2 by Jeroen Vermeulen
Outline and test changes needed; don't oops for CannotCopy.
565
        dsd_source = getUtility(IDistroSeriesDifferenceSource)
7675.1166.4 by Jeroen Vermeulen
Test and adjust database privileges.
566
        target_series = self.target_distroseries
7675.1166.2 by Jeroen Vermeulen
Outline and test changes needed; don't oops for CannotCopy.
567
        candidates = dsd_source.getForDistroSeries(
13699.1.1 by Jeroen Vermeulen
Make getForDistroSeries default "status=None" mean "all statuses."
568
            distro_series=target_series, name_filter=self.package_name,
569
            status=DistroSeriesDifferenceStatus.NEEDS_ATTENTION)
7675.1176.1 by Jeroen Vermeulen
Move PackageCopyJob package name into db column, add copy_policy.
570
7675.1166.2 by Jeroen Vermeulen
Outline and test changes needed; don't oops for CannotCopy.
571
        # The job doesn't know what distroseries a given package is
572
        # coming from, and the version number in the DSD may have
7675.1166.4 by Jeroen Vermeulen
Test and adjust database privileges.
573
        # changed.  We can however filter out DSDs that are from
7675.1166.2 by Jeroen Vermeulen
Outline and test changes needed; don't oops for CannotCopy.
574
        # different distributions, based on the job's target archive.
7675.1166.9 by Jeroen Vermeulen
Review changes.
575
        source_distro_id = self.source_archive.distributionID
7675.1166.2 by Jeroen Vermeulen
Outline and test changes needed; don't oops for CannotCopy.
576
        return [
577
            dsd
578
            for dsd in candidates
7675.1176.1 by Jeroen Vermeulen
Move PackageCopyJob package name into db column, add copy_policy.
579
                if dsd.parent_series.distributionID == source_distro_id]
7675.1166.2 by Jeroen Vermeulen
Outline and test changes needed; don't oops for CannotCopy.
580
581
    def reportFailure(self, cannotcopy_exception):
582
        """Attempt to report failure to the user."""
583
        message = unicode(cannotcopy_exception)
7675.1166.8 by Jeroen Vermeulen
Merge latest, resolve conflicts, drop unused parameter.
584
        dsds = self.findMatchingDSDs()
7675.1166.2 by Jeroen Vermeulen
Outline and test changes needed; don't oops for CannotCopy.
585
        comment_source = getUtility(IDistroSeriesDifferenceCommentSource)
7675.1166.4 by Jeroen Vermeulen
Test and adjust database privileges.
586
587
        # Register the error comment in the name of the Janitor.  Not a
588
        # great choice, but we have no user identity to represent
589
        # Launchpad; it's far too costly to create one; and
590
        # impersonating the requester can be misleading and would also
591
        # involve extra bookkeeping.
592
        reporting_persona = getUtility(ILaunchpadCelebrities).janitor
593
7675.1166.2 by Jeroen Vermeulen
Outline and test changes needed; don't oops for CannotCopy.
594
        for dsd in dsds:
7675.1166.4 by Jeroen Vermeulen
Test and adjust database privileges.
595
            comment_source.new(dsd, reporting_persona, message)
7675.1166.2 by Jeroen Vermeulen
Outline and test changes needed; don't oops for CannotCopy.
596
7675.1130.3 by Gavin Panella
A few tweaks and all but one test passes.
597
    def __repr__(self):
7675.1130.6 by Gavin Panella
Test for PlainPackageCopyJob.__repr__().
598
        """Returns an informative representation of the job."""
7675.1130.3 by Gavin Panella
A few tweaks and all but one test passes.
599
        parts = ["%s to copy" % self.__class__.__name__]
7675.1176.1 by Jeroen Vermeulen
Move PackageCopyJob package name into db column, add copy_policy.
600
        if self.package_name is None:
601
            parts.append(" no package (!)")
7675.1130.3 by Gavin Panella
A few tweaks and all but one test passes.
602
        else:
7675.1176.1 by Jeroen Vermeulen
Move PackageCopyJob package name into db column, add copy_policy.
603
            parts.append(" package %s" % self.package_name)
7675.1130.3 by Gavin Panella
A few tweaks and all but one test passes.
604
        parts.append(
605
            " from %s/%s" % (
606
                self.source_archive.distribution.name,
607
                self.source_archive.name))
608
        parts.append(
609
            " to %s/%s" % (
610
                self.target_archive.distribution.name,
611
                self.target_archive.name))
612
        parts.append(
613
            ", %s pocket," % self.target_pocket.name)
614
        if self.target_distroseries is not None:
615
            parts.append(" in %s" % self.target_distroseries)
616
        if self.include_binaries:
617
            parts.append(", including binaries")
618
        return "<%s>" % "".join(parts)
7675.1176.12 by Jeroen Vermeulen
Set PlainPackageCopyJob.copy_policy.
619
620
    def getPolicyImplementation(self):
621
        """Return the `ICopyPolicy` applicable to this job."""
622
        return ICopyPolicy(self.copy_policy)
13287.2.2 by Jeroen Vermeulen
IPackageCopyJobSource.
623
624
625
PackageCopyJob.registerConcreteClass(PlainPackageCopyJob)