~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/soyuz/scripts/initialize_distroseries.py

  • Committer: Colin Watson
  • Date: 2011-08-19 00:25:11 UTC
  • mfrom: (7675.1045.728 db-devel)
  • mto: This revision was merged to the branch mainline in revision 13909.
  • Revision ID: cjwatson@canonical.com-20110819002511-0x8hrqs1ckiqk53g
merge db-devel

Show diffs side-by-side

added added

removed removed

Lines of Context:
26
26
from lp.soyuz.adapters.packagelocation import PackageLocation
27
27
from lp.soyuz.enums import (
28
28
    ArchivePurpose,
 
29
    PackagePublishingStatus,
29
30
    PackageUploadStatus,
30
31
    )
31
32
from lp.soyuz.interfaces.archive import (
33
34
    IArchiveSet,
34
35
    )
35
36
from lp.soyuz.interfaces.component import IComponentSet
 
37
from lp.soyuz.interfaces.distributionjob import (
 
38
    IDistroSeriesDifferenceJobSource,
 
39
    )
36
40
from lp.soyuz.interfaces.packagecloner import IPackageCloner
37
41
from lp.soyuz.interfaces.packageset import (
38
42
    IPackagesetSet,
39
43
    NoSuchPackageSet,
40
44
    )
 
45
from lp.soyuz.interfaces.queue import IPackageUploadSet
41
46
from lp.soyuz.model.packageset import Packageset
42
47
from lp.soyuz.scripts.packagecopier import do_copy
43
48
 
45
50
class InitializationError(Exception):
46
51
    """Raised when there is an exception during the initialization process."""
47
52
 
 
53
# Pockets to consider when initializing the derived series from its parent(s).
 
54
INIT_POCKETS = [
 
55
    PackagePublishingPocket.RELEASE,
 
56
    PackagePublishingPocket.SECURITY,
 
57
    PackagePublishingPocket.UPDATES,
 
58
    ]
 
59
 
48
60
 
49
61
class InitializeDistroSeries:
50
62
    """Copy in all of the parents distroseries's configuration. This
102
114
            parents_bulk,
103
115
            key=lambda parent: self.parent_ids.index(parent.id))
104
116
        self.arches = arches
105
 
        self.packagesets = [
 
117
        self.packagesets_ids = [
106
118
            ensure_unicode(packageset) for packageset in packagesets]
 
119
        self.packagesets = bulk.load(
 
120
            Packageset, [int(packageset) for packageset in packagesets])
107
121
        self.rebuild = rebuild
108
122
        self.overlays = overlays
109
123
        self.overlay_pockets = overlay_pockets
112
126
 
113
127
        self.first_derivation = (
114
128
            not self.distroseries.distribution.has_published_sources)
 
129
 
115
130
        if self.first_derivation:
116
131
            # Use-case #1.
117
132
            self.derivation_parents = self.parents
124
139
            if self.parent_ids == []:
125
140
                self.parents = (
126
141
                    self.distroseries.previous_series.getParentSeries())
 
142
        self._create_source_names_by_parent()
127
143
 
128
144
    def check(self):
129
145
        if self.distroseries.isDerivedSeries():
144
160
                self._checkBuilds(parent)
145
161
            self._checkQueue(parent)
146
162
        self._checkSeries()
 
163
        return True
147
164
 
148
165
    def _checkParents(self):
149
166
        """If self.first_derivation, the parents list cannot be empty."""
160
177
    def _checkBuilds(self, parent):
161
178
        """Assert there are no pending builds for the given parent series.
162
179
 
163
 
        Only cares about the RELEASE pocket, which is the only one inherited
164
 
        via initializeFromParent method.
 
180
        Only cares about the RELEASE, SECURITY and UPDATES pockets, which are
 
181
        the only ones inherited via initializeFromParent method.
 
182
        Restrict the check to the select architectures (if applicable).
 
183
        Restrict the check to the selected packages if a limited set of
 
184
        packagesets is used by the initialization.
165
185
        """
166
 
        # only the RELEASE pocket is inherited, so we only check
167
 
        # pending build records for it.
 
186
        spns = self.source_names_by_parent.get(parent, None)
 
187
        if spns is not None and len(spns) == 0:
 
188
            # If no sources are selected in this parent, skip the check.
 
189
            return
 
190
        # spns=None means no packagesets selected so we need to consider
 
191
        # all sources.
 
192
 
 
193
        arch_tags = self.arches if self.arches is not () else None
168
194
        pending_builds = parent.getBuildRecords(
169
 
            BuildStatus.NEEDSBUILD, pocket=PackagePublishingPocket.RELEASE)
 
195
            BuildStatus.NEEDSBUILD, pocket=INIT_POCKETS,
 
196
            arch_tag=arch_tags, name=spns)
170
197
 
171
 
        if pending_builds.any():
172
 
            raise InitializationError("Parent series has pending builds.")
 
198
        if not pending_builds.is_empty():
 
199
            raise InitializationError(
 
200
                "Parent series has pending builds for selected sources, "
 
201
                "see help text for more information.")
173
202
 
174
203
    def _checkQueue(self, parent):
175
204
        """Assert upload queue is empty on the given parent series.
176
205
 
177
 
        Only cares about the RELEASE pocket, which is the only one inherited
178
 
        via initializeFromParent method.
179
 
        """
180
 
        # only the RELEASE pocket is inherited, so we only check
181
 
        # queue items for it.
 
206
        Only cares about the RELEASE, SECURITY and UPDATES pockets, which are
 
207
        the only ones inherited via initializeFromParent method.
 
208
        Restrict the check to the selected packages if a limited set of
 
209
        packagesets is used by the initialization.
 
210
         """
182
211
        statuses = [
183
212
            PackageUploadStatus.NEW,
184
213
            PackageUploadStatus.ACCEPTED,
185
214
            PackageUploadStatus.UNAPPROVED,
186
215
            ]
187
 
        items = parent.getPackageUploads(
188
 
            status=statuses, pocket=PackagePublishingPocket.RELEASE)
 
216
        spns = self.source_names_by_parent.get(parent, None)
 
217
        if spns is not None and len(spns) == 0:
 
218
            # If no sources are selected in this parent, skip the check.
 
219
            return
 
220
        # spns=None means no packagesets selected so we need to consider
 
221
        # all sources.
 
222
 
 
223
        items = getUtility(IPackageUploadSet).getBuildsForSources(
 
224
            parent, statuses, INIT_POCKETS, spns)
189
225
        if not items.is_empty():
190
226
            raise InitializationError(
191
 
                "Parent series queues are not empty.")
 
227
                "Parent series has sources waiting in its upload queues "
 
228
                "that match your selection, see help text for more "
 
229
                "information.")
192
230
 
193
231
    def _checkSeries(self):
194
232
        error = (
210
248
        self._copy_architectures()
211
249
        self._copy_packages()
212
250
        self._copy_packagesets()
 
251
        self._create_dsds()
213
252
        self._set_initialized()
214
253
        transaction.commit()
215
254
 
240
279
        for distroseriesparent in distroseriesparents:
241
280
            distroseriesparent.initialized = True
242
281
 
 
282
    def _has_same_parents_as_previous_series(self):
 
283
        # Does this distroseries have the same parents as its previous
 
284
        # series? (note that the parent's order does not matter here)
 
285
        dsp_set = getUtility(IDistroSeriesParentSet)
 
286
        previous_series_parents = [
 
287
            dsp.parent_series for dsp in dsp_set.getByDerivedSeries(
 
288
                self.distroseries.previous_series)]
 
289
        return set(previous_series_parents) == set(self.parents)
 
290
 
 
291
    def _create_dsds(self):
 
292
        if not self.first_derivation:
 
293
            if (self._has_same_parents_as_previous_series() and
 
294
                not self.packagesets_ids):
 
295
                # If the parents are the same as previous_series's
 
296
                # parents and all the packagesets are being copied,
 
297
                # then we simply copy the DSDs from previous_series
 
298
                # for performance reasons.
 
299
                self._copy_dsds_from_previous_series()
 
300
            else:
 
301
                # Either the parents have changed (compared to
 
302
                # previous_series's parents) or a selection only of the
 
303
                # packagesets is being copied so we have to recompute
 
304
                # the DSDs by creating DSD Jobs.
 
305
                self._create_dsd_jobs()
 
306
        else:
 
307
            # If this is the first derivation, create the DSD Jobs.
 
308
            self._create_dsd_jobs()
 
309
 
 
310
    def _copy_dsds_from_previous_series(self):
 
311
        self._store.execute("""
 
312
            INSERT INTO DistroSeriesDifference
 
313
                (derived_series, source_package_name, package_diff,
 
314
                status, difference_type, parent_package_diff,
 
315
                source_version, parent_source_version,
 
316
                base_version, parent_series)
 
317
            SELECT
 
318
                %s AS derived_series, source_package_name,
 
319
                package_diff, status,
 
320
                difference_type, parent_package_diff, source_version,
 
321
                parent_source_version, base_version, parent_series
 
322
            FROM DistroSeriesDifference AS dsd
 
323
                WHERE dsd.derived_series = %s
 
324
            """ % sqlvalues(
 
325
                self.distroseries.id,
 
326
                self.distroseries.previous_series.id))
 
327
 
 
328
    def _create_dsd_jobs(self):
 
329
        job_source = getUtility(IDistroSeriesDifferenceJobSource)
 
330
        job_source.massCreateForSeries(self.distroseries)
 
331
 
243
332
    def _copy_configuration(self):
244
333
        self.distroseries.backports_not_automatic = any(
245
334
            parent.backports_not_automatic
256
345
                sqlvalues(self.arches))
257
346
        self._store.execute("""
258
347
            INSERT INTO DistroArchSeries
259
 
            (distroseries, processorfamily, architecturetag, owner, official)
 
348
            (distroseries, processorfamily, architecturetag, owner, official,
 
349
             supports_virtualized)
260
350
            SELECT %s, processorfamily, architecturetag, %s,
261
 
                bool_and(official)
 
351
                bool_and(official), bool_or(supports_virtualized)
262
352
            FROM DistroArchSeries WHERE enabled = TRUE %s
263
353
            GROUP BY processorfamily, architecturetag
264
354
            """ % (sqlvalues(self.distroseries, self.distroseries.owner)
291
381
        self._copy_publishing_records(distroarchseries_lists)
292
382
        self._copy_packaging_links()
293
383
 
294
 
    @classmethod
295
 
    def _use_cloner(cls, target_archive, archive, distroseries):
 
384
    def _use_cloner(self, target_archive, archive):
296
385
        """Returns True if it's safe to use the packagecloner (as opposed
297
386
        to using the packagecopier).
298
387
        We use two different ways to copy packages:
299
388
         - the packagecloner: fast but not conflict safe.
300
389
         - the packagecopier: slow but performs lots of checks to
301
390
         avoid creating conflicts.
302
 
        1a. If the archives are different and the target archive is
303
 
            empty use the cloner.
304
 
        1b. If the archives are the same and the target series is
305
 
            empty use the cloner.
 
391
        1. We'll use the cloner:
 
392
        If this is not a first initialization.
 
393
        And If:
 
394
            1.a If the archives are different and the target archive is
 
395
                empty use the cloner.
 
396
            Or
 
397
            1.b. If the archives are the same and the target series is
 
398
                empty use the cloner.
306
399
        2.  Otherwise use the copier.
307
400
        """
 
401
        if self.first_derivation:
 
402
            return False
 
403
 
308
404
        target_archive_empty = target_archive.getPublishedSources().is_empty()
309
405
        case_1a = (target_archive != archive and
310
406
                   target_archive_empty)
311
407
        case_1b = (target_archive == archive and
312
408
                   (target_archive_empty or
313
409
                    target_archive.getPublishedSources(
314
 
                        distroseries=distroseries).is_empty()))
 
410
                        distroseries=self.distroseries).is_empty()))
315
411
        return case_1a or case_1b
316
412
 
 
413
    def _create_source_names_by_parent(self):
 
414
        """If only a subset of the packagesets was selected to be copied,
 
415
        create a dict with the list of source names to be copied for each
 
416
        parent.
 
417
 
 
418
        source_names_by_parent.get(parent) can be 3 different things:
 
419
        - None: this means that no specific packagesets where selected
 
420
        for the initialization. In this case we need to consider *all*
 
421
        the packages in this parent.
 
422
        - []: this means that some specific packagesets where selected
 
423
        for the initialization but none in this parent. We can skip
 
424
        this parent for all the copy/check operations.
 
425
        - [name1, ...]: this means that some specific packagesets
 
426
        were selected for the initialization and some are in this
 
427
        parent so the list of packages to consider in not empty.
 
428
        """
 
429
        source_names_by_parent = {}
 
430
        if self.packagesets_ids:
 
431
            for parent in self.derivation_parents:
 
432
                spns = []
 
433
                for pkgset in self.packagesets:
 
434
                    if pkgset.distroseries == parent:
 
435
                        spns += list(pkgset.getSourcesIncluded())
 
436
                source_names_by_parent[parent] = spns
 
437
        self.source_names_by_parent = source_names_by_parent
 
438
 
317
439
    def _copy_publishing_records(self, distroarchseries_lists):
318
440
        """Copy the publishing records from the parent arch series
319
441
        to the given arch series in ourselves.
326
448
        archive_set = getUtility(IArchiveSet)
327
449
 
328
450
        for parent in self.derivation_parents:
329
 
            spns = []
330
 
            # The overhead from looking up each packageset is mitigated by
331
 
            # this usually running from a job.
332
 
            if self.packagesets:
333
 
                for pkgsetid in self.packagesets:
334
 
                    pkgset = self._store.get(Packageset, int(pkgsetid))
335
 
                    if pkgset.distroseries == parent:
336
 
                        spns += list(pkgset.getSourcesIncluded())
337
 
 
 
451
            spns = self.source_names_by_parent.get(parent, None)
 
452
            if spns is not None and len(spns) == 0:
338
453
                # Some packagesets where selected but not a single
339
454
                # source from this parent: we skip the copy since
340
455
                # calling copy with spns=[] would copy all the packagesets
341
456
                # from this parent.
342
 
                if len(spns) == 0:
343
 
                    continue
 
457
                continue
 
458
            # spns=None means no packagesets selected so we need to consider
 
459
            # all sources.
344
460
 
345
461
            distroarchseries_list = distroarchseries_lists[parent]
346
462
            for archive in parent.distribution.all_distro_archives:
353
469
                if archive.purpose is ArchivePurpose.PRIMARY:
354
470
                    assert target_archive is not None, (
355
471
                        "Target archive doesn't exist?")
356
 
 
357
 
                if self._use_cloner(
358
 
                    target_archive, archive, self.distroseries):
 
472
                if self._use_cloner(target_archive, archive):
359
473
                    origin = PackageLocation(
360
474
                        archive, parent.distribution, parent,
361
475
                        PackagePublishingPocket.RELEASE)
374
488
                else:
375
489
                    # There is only one available pocket in an unreleased
376
490
                    # series.
377
 
                    pocket = PackagePublishingPocket.RELEASE
 
491
                    target_pocket = PackagePublishingPocket.RELEASE
378
492
                    sources = archive.getPublishedSources(
379
 
                        distroseries=parent, pocket=pocket, name=spns)
 
493
                        distroseries=parent, pocket=INIT_POCKETS,
 
494
                        status=(PackagePublishingStatus.PENDING,
 
495
                                PackagePublishingStatus.PUBLISHED),
 
496
                        name=spns)
380
497
                    # XXX: rvb 2011-06-23 bug=801112: do_copy is atomic (all
381
498
                    # or none of the sources will be copied). This might
382
499
                    # lead to a partially initialised series if there is a
384
501
                    try:
385
502
                        sources_published = do_copy(
386
503
                            sources, target_archive, self.distroseries,
387
 
                            pocket, include_binaries=not self.rebuild,
388
 
                            check_permissions=False, strict_binaries=False)
 
504
                            target_pocket, include_binaries=not self.rebuild,
 
505
                            check_permissions=False, strict_binaries=False,
 
506
                            close_bugs=False, create_dsd_job=False)
389
507
                        if self.rebuild:
390
508
                            for pubrec in sources_published:
391
 
                                pubrec.createMissingBuilds()
392
 
 
 
509
                                pubrec.createMissingBuilds(
 
510
                                   list(self.distroseries.architectures))
393
511
                    except CannotCopy, error:
394
512
                        raise InitializationError(error)
395
513
 
475
593
        packagesets = self._store.find(
476
594
            Packageset, DistroSeries.id.is_in(self.derivation_parent_ids))
477
595
        parent_to_child = {}
478
 
        # Create the packagesets.
 
596
        # Create the packagesets and any archivepermissions if we're not
 
597
        # copying cross-distribution.
479
598
        parent_distro_ids = [
480
599
            parent.distribution.id for parent in self.derivation_parents]
481
600
        for parent_ps in packagesets:
482
601
            # Cross-distro initializations get packagesets owned by the
483
602
            # distro owner, otherwise the old owner is preserved.
484
 
            if self.packagesets and str(parent_ps.id) not in self.packagesets:
 
603
            if (self.packagesets_ids and
 
604
                str(parent_ps.id) not in self.packagesets_ids):
485
605
                continue
486
606
            packageset_set = getUtility(IPackagesetSet)
487
607
            # First, try to fetch an existing packageset with this name.
497
617
                    parent_ps.name, parent_ps.description,
498
618
                    new_owner, distroseries=self.distroseries,
499
619
                    related_set=parent_ps)
500
 
 
501
620
            parent_to_child[parent_ps] = child_ps
 
621
            # Copy archivepermissions if we're not copying
 
622
            # cross-distribution.
 
623
            if (self.distroseries.distribution ==
 
624
                    parent_ps.distroseries.distribution):
 
625
                self._store.execute("""
 
626
                    INSERT INTO Archivepermission
 
627
                    (person, permission, archive, packageset, explicit)
 
628
                    SELECT person, permission, %s, %s, explicit
 
629
                    FROM Archivepermission WHERE packageset = %s
 
630
                    """ % sqlvalues(
 
631
                        self.distroseries.main_archive, child_ps.id,
 
632
                        parent_ps.id))
502
633
        # Copy the relations between sets, and the contents.
503
634
        for old_series_ps, new_series_ps in parent_to_child.items():
504
635
            old_series_sets = old_series_ps.setsIncluded(