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
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
|
# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Initialise a distroseries from its parent distroseries."""
__metaclass__ = type
__all__ = [
'InitialisationError',
'InitialiseDistroSeries',
]
from operator import methodcaller
import transaction
from zope.component import getUtility
from canonical.database.sqlbase import sqlvalues
from canonical.launchpad.helpers import ensure_unicode
from canonical.launchpad.interfaces.lpstorm import (
IMasterStore,
IStore,
)
from lp.buildmaster.enums import BuildStatus
from lp.registry.interfaces.distroseriesparent import IDistroSeriesParentSet
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.soyuz.adapters.packagelocation import PackageLocation
from lp.soyuz.enums import (
ArchivePurpose,
PackageUploadStatus,
)
from lp.soyuz.interfaces.archive import IArchiveSet
from lp.soyuz.interfaces.component import IComponentSet
from lp.soyuz.interfaces.packagecloner import IPackageCloner
from lp.soyuz.interfaces.packageset import IPackagesetSet
from lp.soyuz.model.packageset import Packageset
class InitialisationError(Exception):
"""Raised when there is an exception during the initialisation process."""
class InitialiseDistroSeries:
"""Copy in all of the parent distroseries's configuration. This
includes all configuration for distroseries as well as distroarchseries,
publishing and all publishing records for sources and binaries.
Preconditions:
The distroseries must exist, and be completly unused, with no source
or binary packages existing, as well as no distroarchseries set up.
Section and component selections must be empty. It must not have a
parent series.
Outcome:
The distroarchseries set up in the parent series will be copied.
The publishing structure will be copied from the parent. All
PUBLISHED and PENDING packages in the parent will be created in
this distroseries and its distroarchseriess. All component and section
selections will be duplicated, as will any permission-related
structures.
Note:
This method will raise a InitialisationError when the pre-conditions
are not met. After this is run, you still need to construct chroots
for building, you need to add anything missing wrt. ports etc. This
method is only meant to give you a basic copy of a parent series in
order to assist you in preparing a new series of a distribution or
in the initialisation of a derivative.
"""
def __init__(
self, distroseries, parents, arches=(), packagesets=(),
rebuild=False, overlays=(), overlay_pockets=(),
overlay_components=()):
# Avoid circular imports
from lp.registry.model.distroseries import DistroSeries
# XXX: rvb 2011-05-27 bug=789091: This code should be fixed to support
# initialising from multiple parents.
self.parent_id = parents[0]
self.parent = IStore(
DistroSeries).get(DistroSeries, int(self.parent_id))
self.distroseries = distroseries
self.arches = arches
self.packagesets = [
ensure_unicode(packageset) for packageset in packagesets]
self.rebuild = rebuild
self.overlays = overlays
self.overlay_pockets = overlay_pockets
self.overlay_components = overlay_components
self._store = IMasterStore(DistroSeries)
def check(self):
if self.distroseries.is_derived_series:
raise InitialisationError(
("DistroSeries {child.name} has already been initialized"
".").format(
child=self.distroseries))
if self.distroseries.distribution.id == self.parent.distribution.id:
self._checkBuilds()
self._checkQueue()
self._checkSeries()
def _checkBuilds(self):
"""Assert there are no pending builds for parent series.
Only cares about the RELEASE pocket, which is the only one inherited
via initialiseFromParent method.
"""
# only the RELEASE pocket is inherited, so we only check
# pending build records for it.
pending_builds = self.parent.getBuildRecords(
BuildStatus.NEEDSBUILD, pocket=PackagePublishingPocket.RELEASE)
if pending_builds.any():
raise InitialisationError("Parent series has pending builds.")
def _checkQueue(self):
"""Assert upload queue is empty on parent series.
Only cares about the RELEASE pocket, which is the only one inherited
via initialiseFromParent method.
"""
# only the RELEASE pocket is inherited, so we only check
# queue items for it.
for queue in (
PackageUploadStatus.NEW, PackageUploadStatus.ACCEPTED,
PackageUploadStatus.UNAPPROVED):
items = self.parent.getQueueItems(
queue, pocket=PackagePublishingPocket.RELEASE)
if items:
raise InitialisationError(
"Parent series queues are not empty.")
def _checkSeries(self):
error = (
"Can not copy distroarchseries from parent, there are "
"already distroarchseries(s) initialised for this series.")
sources = self.distroseries.getAllPublishedSources()
binaries = self.distroseries.getAllPublishedBinaries()
if not all(
map(methodcaller('is_empty'), (
sources, binaries, self.distroseries.architectures,
self.distroseries.sections))):
raise InitialisationError(error)
if self.distroseries.components:
raise InitialisationError(error)
def initialise(self):
self._set_parent()
self._copy_configuration()
self._copy_architectures()
self._copy_packages()
self._copy_packagesets()
self._set_initialised()
transaction.commit()
def _set_parent(self):
# XXX: rvb 2011-05-27 bug=789091: This code should be fixed to support
# initialising from multiple parents.
dsp_set = getUtility(IDistroSeriesParentSet)
if self.overlays and self.overlays[0]:
pocket = PackagePublishingPocket.__metaclass__.getTermByToken(
PackagePublishingPocket, self.overlay_pockets[0]).value
component_set = getUtility(IComponentSet)
component = component_set[self.overlay_components[0]]
dsp_set.new(
self.distroseries, self.parent, initialized=False,
is_overlay=True, pocket=pocket, component=component)
else:
dsp_set.new(self.distroseries, self.parent, initialized=False)
def _set_initialised(self):
dsp_set = getUtility(IDistroSeriesParentSet)
distroseriesparent = dsp_set.getByDerivedAndParentSeries(
self.distroseries, self.parent)
distroseriesparent.initialized = True
def _copy_configuration(self):
self.distroseries.backports_not_automatic = \
self.parent.backports_not_automatic
def _copy_architectures(self):
include = ''
if self.arches:
include = "AND architecturetag IN %s" % sqlvalues(self.arches)
self._store.execute("""
INSERT INTO DistroArchSeries
(distroseries, processorfamily, architecturetag, owner, official)
SELECT %s, processorfamily, architecturetag, %s, official
FROM DistroArchSeries WHERE distroseries = %s
AND enabled = TRUE %s
""" % (sqlvalues(self.distroseries, self.distroseries.owner,
self.parent) + (include,)))
self._store.flush()
self.distroseries.nominatedarchindep = self.distroseries[
self.parent.nominatedarchindep.architecturetag]
def _copy_packages(self):
# Perform the copies
self._copy_component_section_and_format_selections()
# Prepare the list of distroarchseries for which binary packages
# shall be copied.
distroarchseries_list = []
for arch in self.distroseries.architectures:
if self.arches and (arch.architecturetag not in self.arches):
continue
parent_arch = self.parent[arch.architecturetag]
distroarchseries_list.append((parent_arch, arch))
# Now copy source and binary packages.
self._copy_publishing_records(distroarchseries_list)
self._copy_packaging_links()
def _copy_publishing_records(self, distroarchseries_list):
"""Copy the publishing records from the parent arch series
to the given arch series in ourselves.
We copy all PENDING and PUBLISHED records as PENDING into our own
publishing records.
We copy only the RELEASE pocket in the PRIMARY and DEBUG archives.
"""
archive_set = getUtility(IArchiveSet)
spns = []
# The overhead from looking up each packageset is mitigated by
# this usually running from a job.
if self.packagesets:
for pkgsetid in self.packagesets:
pkgset = self._store.get(Packageset, int(pkgsetid))
spns += list(pkgset.getSourcesIncluded())
for archive in self.parent.distribution.all_distro_archives:
if archive.purpose not in (
ArchivePurpose.PRIMARY, ArchivePurpose.DEBUG):
continue
target_archive = archive_set.getByDistroPurpose(
self.distroseries.distribution, archive.purpose)
if archive.purpose is ArchivePurpose.PRIMARY:
assert target_archive is not None, (
"Target archive doesn't exist?")
origin = PackageLocation(
archive, self.parent.distribution, self.parent,
PackagePublishingPocket.RELEASE)
destination = PackageLocation(
target_archive, self.distroseries.distribution,
self.distroseries, PackagePublishingPocket.RELEASE)
proc_families = None
if self.rebuild:
proc_families = [
das[1].processorfamily
for das in distroarchseries_list]
distroarchseries_list = ()
getUtility(IPackageCloner).clonePackages(
origin, destination, distroarchseries_list,
proc_families, spns, self.rebuild)
def _copy_component_section_and_format_selections(self):
"""Copy the section, component and format selections from the parent
distro series into this one.
"""
# Copy the component selections
self._store.execute('''
INSERT INTO ComponentSelection (distroseries, component)
SELECT %s AS distroseries, cs.component AS component
FROM ComponentSelection AS cs WHERE cs.distroseries = %s
''' % sqlvalues(self.distroseries.id,
self.parent.id))
# Copy the section selections
self._store.execute('''
INSERT INTO SectionSelection (distroseries, section)
SELECT %s as distroseries, ss.section AS section
FROM SectionSelection AS ss WHERE ss.distroseries = %s
''' % sqlvalues(self.distroseries.id,
self.parent.id))
# Copy the source format selections
self._store.execute('''
INSERT INTO SourcePackageFormatSelection (distroseries, format)
SELECT %s as distroseries, spfs.format AS format
FROM SourcePackageFormatSelection AS spfs
WHERE spfs.distroseries = %s
''' % sqlvalues(self.distroseries.id,
self.parent.id))
def _copy_packaging_links(self):
"""Copy the packaging links from the parent series to this one."""
self._store.execute("""
INSERT INTO
Packaging(
distroseries, sourcepackagename, productseries,
packaging, owner)
SELECT
ChildSeries.id,
Packaging.sourcepackagename,
Packaging.productseries,
Packaging.packaging,
Packaging.owner
FROM
Packaging
-- Joining the parent distroseries permits the query to build
-- the data set for the series being updated, yet results are
-- in fact the data from the original series.
JOIN Distroseries ChildSeries
ON Packaging.distroseries = %s
WHERE
-- Select only the packaging links that are in the parent
-- that are not in the child.
ChildSeries.id = %s
AND Packaging.sourcepackagename in (
SELECT sourcepackagename
FROM Packaging
WHERE distroseries in (
SELECT id
FROM Distroseries
WHERE id = %s
)
EXCEPT
SELECT sourcepackagename
FROM Packaging
WHERE distroseries in (
SELECT id
FROM Distroseries
WHERE id = ChildSeries.id
)
)
""" % (self.parent.id, self.distroseries.id, self.parent.id))
def _copy_packagesets(self):
"""Copy packagesets from the parent distroseries."""
packagesets = self._store.find(Packageset, distroseries=self.parent)
parent_to_child = {}
# Create the packagesets, and any archivepermissions
for parent_ps in packagesets:
# Cross-distro initialisations get packagesets owned by the
# distro owner, otherwise the old owner is preserved.
if self.packagesets and str(parent_ps.id) not in self.packagesets:
continue
if self.distroseries.distribution == self.parent.distribution:
new_owner = parent_ps.owner
else:
new_owner = self.distroseries.owner
child_ps = getUtility(IPackagesetSet).new(
parent_ps.name, parent_ps.description,
new_owner, distroseries=self.distroseries,
related_set=parent_ps)
self._store.execute("""
INSERT INTO Archivepermission
(person, permission, archive, packageset, explicit)
SELECT person, permission, %s, %s, explicit
FROM Archivepermission WHERE packageset = %s
""" % sqlvalues(
self.distroseries.main_archive, child_ps.id,
parent_ps.id))
parent_to_child[parent_ps] = child_ps
# Copy the relations between sets, and the contents
for old_series_ps, new_series_ps in parent_to_child.items():
old_series_sets = old_series_ps.setsIncluded(
direct_inclusion=True)
for old_series_child in old_series_sets:
new_series_ps.add(parent_to_child[old_series_child])
new_series_ps.add(old_series_ps.sourcesIncluded(
direct_inclusion=True))
|