~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/soyuz/model/packagecopyjob.py

[r=benji][bug=795573,
 796233] On DistroSeries:+localpackagediffs ensure that the comment
 form is hidden after adding a new comment to a DistroSeriesDifference,
 prevent empty comments from being submitted,
 and add some animations and effects to make the UI less jarring and easier to
 follow.

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
    Unicode,
18
18
    )
19
19
import transaction
 
20
 
20
21
from zope.component import getUtility
21
22
from zope.interface import (
22
23
    classProvides,
24
25
    )
25
26
 
26
27
from canonical.database.enumcol import EnumCol
 
28
from canonical.database.sqlbase import sqlvalues
27
29
from canonical.launchpad.components.decoratedresultset import (
28
30
    DecoratedResultSet,
29
31
    )
43
45
    IDistroSeriesDifferenceCommentSource,
44
46
    )
45
47
from lp.services.database.stormbase import StormBase
46
 
from lp.services.job.interfaces.job import JobStatus
 
48
from lp.services.job.interfaces.job import (
 
49
    JobStatus,
 
50
    SuspendJobException,
 
51
    )
47
52
from lp.services.job.model.job import Job
48
53
from lp.services.job.runner import BaseRunnableJob
49
 
from lp.soyuz.adapters.overrides import SourceOverride
 
54
from lp.soyuz.adapters.overrides import (
 
55
    FromExistingOverridePolicy,
 
56
    SourceOverride,
 
57
    UnknownOverridePolicy,
 
58
    )
50
59
from lp.soyuz.enums import PackageCopyPolicy
51
60
from lp.soyuz.interfaces.archive import CannotCopy
52
61
from lp.soyuz.interfaces.component import IComponentSet
57
66
    IPlainPackageCopyJobSource,
58
67
    PackageCopyJobType,
59
68
    )
 
69
from lp.soyuz.interfaces.queue import IPackageUploadSet
60
70
from lp.soyuz.interfaces.section import ISectionSet
61
71
from lp.soyuz.model.archive import Archive
62
72
from lp.soyuz.scripts.packagecopier import do_copy
184
194
    classProvides(IPlainPackageCopyJobSource)
185
195
 
186
196
    @classmethod
 
197
    def _makeMetadata(cls, target_pocket, package_version, include_binaries):
 
198
        """."""
 
199
        return {
 
200
            'target_pocket': target_pocket.value,
 
201
            'package_version': package_version,
 
202
            'include_binaries': bool(include_binaries),
 
203
        }
 
204
 
 
205
    @classmethod
187
206
    def create(cls, package_name, source_archive,
188
207
               target_archive, target_distroseries, target_pocket,
189
208
               include_binaries=False, package_version=None,
190
209
               copy_policy=PackageCopyPolicy.INSECURE):
191
210
        """See `IPlainPackageCopyJobSource`."""
192
211
        assert package_version is not None, "No package version specified."
193
 
        metadata = {
194
 
            'target_pocket': target_pocket.value,
195
 
            'package_version': package_version,
196
 
            'include_binaries': bool(include_binaries),
197
 
            }
 
212
        metadata = cls._makeMetadata(
 
213
            target_pocket, package_version, include_binaries)
198
214
        job = PackageCopyJob(
199
215
            job_type=cls.class_job_type,
200
216
            source_archive=source_archive,
207
223
        return cls(job)
208
224
 
209
225
    @classmethod
 
226
    def _composeJobInsertionTuple(cls, target_distroseries, copy_policy,
 
227
                                  include_binaries, job_id, copy_task):
 
228
        """Create an SQL fragment for inserting a job into the database.
 
229
 
 
230
        :return: A string representing an SQL tuple containing initializers
 
231
            for a `PackageCopyJob` in the database (minus `id`, which is
 
232
            assigned automatically).  Contents are escaped for use in SQL.
 
233
        """
 
234
        (
 
235
            package_name,
 
236
            package_version,
 
237
            source_archive,
 
238
            target_archive,
 
239
            target_pocket,
 
240
        ) = copy_task
 
241
        metadata = cls._makeMetadata(
 
242
            target_pocket, package_version, include_binaries)
 
243
        data = (
 
244
            cls.class_job_type, target_distroseries, copy_policy,
 
245
            source_archive, target_archive, package_name, job_id,
 
246
            PackageCopyJob.serializeMetadata(metadata))
 
247
        format_string = "(%s)" % ", ".join(["%s"] * len(data))
 
248
        return format_string % sqlvalues(*data)
 
249
 
 
250
    @classmethod
 
251
    def createMultiple(cls, target_distroseries, copy_tasks,
 
252
                       copy_policy=PackageCopyPolicy.INSECURE,
 
253
                       include_binaries=False):
 
254
        """See `IPlainPackageCopyJobSource`."""
 
255
        store = IMasterStore(Job)
 
256
        job_ids = Job.createMultiple(store, len(copy_tasks))
 
257
        job_contents = [
 
258
            cls._composeJobInsertionTuple(
 
259
                target_distroseries, copy_policy, include_binaries, job_id,
 
260
                task)
 
261
            for job_id, task in zip(job_ids, copy_tasks)]
 
262
        result = store.execute("""
 
263
            INSERT INTO PackageCopyJob (
 
264
                job_type,
 
265
                target_distroseries,
 
266
                copy_policy,
 
267
                source_archive,
 
268
                target_archive,
 
269
                package_name,
 
270
                job,
 
271
                json_data)
 
272
            VALUES %s
 
273
            RETURNING id
 
274
            """ % ", ".join(job_contents))
 
275
        return [job_id for job_id, in result]
 
276
 
 
277
    @classmethod
210
278
    def getActiveJobs(cls, target_archive):
211
279
        """See `IPlainPackageCopyJobSource`."""
212
280
        jobs = IStore(PackageCopyJob).find(
254
322
    def include_binaries(self):
255
323
        return self.metadata['include_binaries']
256
324
 
 
325
    def _createPackageUpload(self, unapproved=False):
 
326
        pu = self.target_distroseries.createQueueEntry(
 
327
            pocket=self.target_pocket, archive=self.target_archive,
 
328
            package_copy_job=self.context)
 
329
        if unapproved:
 
330
            pu.setUnapproved()
 
331
 
257
332
    def addSourceOverride(self, override):
258
333
        """Add an `ISourceOverride` to the metadata."""
 
334
        component = ""
 
335
        section = ""
 
336
        if override.component is not None:
 
337
            component = override.component.name
 
338
        if override.section is not None:
 
339
            section = override.section.name
259
340
        metadata_dict = dict(
260
 
            component_override=override.component.name,
261
 
            section_override=override.section.name)
 
341
            component_override=component,
 
342
            section_override=section)
262
343
        self.context.extendMetadata(metadata_dict)
263
344
 
264
345
    def getSourceOverride(self):
265
346
        """Fetch an `ISourceOverride` from the metadata."""
266
 
        # There's only one package per job; although the schema allows
267
 
        # multiple we're not using that.
268
347
        name = self.package_name
269
348
        component_name = self.metadata.get("component_override")
270
349
        section_name = self.metadata.get("section_override")
271
350
        source_package_name = getUtility(ISourcePackageNameSet)[name]
272
 
        component = getUtility(IComponentSet)[component_name]
273
 
        section = getUtility(ISectionSet)[section_name]
 
351
        try:
 
352
            component = getUtility(IComponentSet)[component_name]
 
353
        except NotFoundError:
 
354
            component = None
 
355
        try:
 
356
            section = getUtility(ISectionSet)[section_name]
 
357
        except NotFoundError:
 
358
            section = None
 
359
 
274
360
        return SourceOverride(source_package_name, component, section)
275
361
 
 
362
    def _checkPolicies(self, source_name):
 
363
        # This helper will only return if it's safe to carry on with the
 
364
        # copy, otherwise it raises SuspendJobException to tell the job
 
365
        # runner to suspend the job.
 
366
        override_policy = FromExistingOverridePolicy()
 
367
        ancestry = override_policy.calculateSourceOverrides(
 
368
            self.target_archive, self.target_distroseries,
 
369
            self.target_pocket, [source_name])
 
370
 
 
371
        copy_policy = self.getPolicyImplementation()
 
372
 
 
373
        if len(ancestry) == 0:
 
374
            # We need to get the default overrides and put them in the
 
375
            # metadata.
 
376
            defaults = UnknownOverridePolicy().calculateSourceOverrides(
 
377
                self.target_archive, self.target_distroseries,
 
378
                self.target_pocket, [source_name])
 
379
            self.addSourceOverride(defaults[0])
 
380
 
 
381
            approve_new = copy_policy.autoApproveNew(
 
382
                self.target_archive, self.target_distroseries,
 
383
                self.target_pocket)
 
384
 
 
385
            if not approve_new:
 
386
                # There's no existing package with the same name and the
 
387
                # policy says unapproved, so we poke it in the NEW queue.
 
388
                self._createPackageUpload()
 
389
                raise SuspendJobException
 
390
        else:
 
391
            # Put the existing override in the metadata.
 
392
            self.addSourceOverride(ancestry[0])
 
393
 
 
394
        # The package is not new (it has ancestry) so check the copy
 
395
        # policy for existing packages.
 
396
        approve_existing = copy_policy.autoApprove(
 
397
            self.target_archive, self.target_distroseries, self.target_pocket)
 
398
        if not approve_existing:
 
399
            self._createPackageUpload(unapproved=True)
 
400
            raise SuspendJobException
 
401
 
276
402
    def run(self):
277
403
        """See `IRunnableJob`."""
278
404
        try:
298
424
            name=name, version=version, exact_match=True).first()
299
425
        if source_package is None:
300
426
            raise CannotCopy("Package %r %r not found." % (name, version))
301
 
 
 
427
        source_name = getUtility(ISourcePackageNameSet)[name]
 
428
 
 
429
        # If there's a PackageUpload associated with this job then this
 
430
        # job has just been released by an archive admin from the queue.
 
431
        # We don't need to check any policies, but the admin may have
 
432
        # set overrides which we will get from the job's metadata.
 
433
        pu = getUtility(IPackageUploadSet).getByPackageCopyJobIDs(
 
434
            [self.context.id])
 
435
        if not pu.any():
 
436
            self._checkPolicies(source_name)
 
437
 
 
438
        # The package is free to go right in, so just copy it now.
 
439
        override = self.getSourceOverride()
 
440
        copy_policy = self.getPolicyImplementation()
 
441
        send_email = copy_policy.send_email(self.target_archive)
302
442
        do_copy(
303
443
            sources=[source_package], archive=self.target_archive,
304
444
            series=self.target_distroseries, pocket=self.target_pocket,
305
 
            include_binaries=self.include_binaries, check_permissions=False)
 
445
            include_binaries=self.include_binaries, check_permissions=False,
 
446
            overrides=[override], send_email=send_email)
306
447
 
307
448
    def abort(self):
308
449
        """Abort work."""