344.1.1
by Steve Langasek
Move all shebangs to python3. |
1 |
#! /usr/bin/python3
|
93
by Colin Watson
archive-reports, extra-germinate: do an extra germinate run for -proposed |
2 |
|
3 |
# Based on lib/lp/archivepublisher/scripts/generate_extra_overrides.py from
|
|
4 |
# Launchpad, which is:
|
|
5 |
#
|
|
6 |
# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
|
|
7 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
|
8 |
||
135
by Colin Watson
extra-germinate: Parallelise by architecture. |
9 |
import copy |
93
by Colin Watson
archive-reports, extra-germinate: do an extra germinate run for -proposed |
10 |
from functools import partial |
11 |
import glob |
|
12 |
import logging |
|
13 |
from optparse import OptionParser |
|
14 |
import os |
|
135
by Colin Watson
extra-germinate: Parallelise by architecture. |
15 |
import pickle |
93
by Colin Watson
archive-reports, extra-germinate: do an extra germinate run for -proposed |
16 |
import shutil |
17 |
import sys |
|
135
by Colin Watson
extra-germinate: Parallelise by architecture. |
18 |
import traceback |
93
by Colin Watson
archive-reports, extra-germinate: do an extra germinate run for -proposed |
19 |
|
20 |
from germinate.archive import TagFile |
|
21 |
from germinate.germinator import Germinator |
|
22 |
from germinate.log import GerminateFormatter |
|
23 |
from germinate.seeds import SeedError, SeedStructure |
|
24 |
from launchpadlib.launchpad import Launchpad |
|
25 |
import lputils |
|
26 |
||
27 |
||
28 |
class AtomicFile: |
|
29 |
"""Facilitate atomic writing of files."""
|
|
30 |
||
31 |
def __init__(self, filename): |
|
32 |
self.filename = filename |
|
33 |
self.fd = open("%s.new" % self.filename, "w") |
|
34 |
||
35 |
def __enter__(self): |
|
36 |
return self.fd |
|
37 |
||
38 |
def __exit__(self, exc_type, exc_value, exc_tb): |
|
39 |
self.fd.close() |
|
40 |
if exc_type is None: |
|
41 |
os.rename("%s.new" % self.filename, self.filename) |
|
42 |
||
43 |
||
135
by Colin Watson
extra-germinate: Parallelise by architecture. |
44 |
class BufferHandler(logging.Handler): |
45 |
"""A log handler which stores records for emission by another logger."""
|
|
46 |
||
47 |
def __init__(self): |
|
48 |
super(BufferHandler, self).__init__() |
|
49 |
self.records = [] |
|
50 |
||
51 |
def emit(self, record): |
|
52 |
# Record arguments may not be available at the other end.
|
|
53 |
record_copy = copy.copy(record) |
|
54 |
record_copy.msg = record.getMessage() |
|
55 |
record_copy.args = None |
|
56 |
self.records.append(pickle.dumps(record_copy, -1)) |
|
57 |
||
58 |
||
93
by Colin Watson
archive-reports, extra-germinate: do an extra germinate run for -proposed |
59 |
class ExtraGerminate: |
60 |
def __init__(self, args): |
|
61 |
self.germinate_logger = None |
|
62 |
self.raw_args = args |
|
63 |
||
64 |
def processOptions(self): |
|
65 |
"""Handle command-line options."""
|
|
66 |
parser = OptionParser() |
|
67 |
parser.add_option( |
|
68 |
"-l", "--launchpad", dest="launchpad_instance", |
|
69 |
default="production") |
|
70 |
parser.add_option( |
|
71 |
"-d", "--distribution", dest="distribution", metavar="DISTRO", |
|
72 |
default="ubuntu", |
|
73 |
help="look in distribution DISTRO") |
|
74 |
parser.add_option("-s", "--suite", help="process this suite") |
|
75 |
parser.add_option( |
|
76 |
"-o", "--output", default="germinate-for-proposed", |
|
77 |
help="output to this directory") |
|
78 |
parser.add_option( |
|
79 |
"--archiveroot", |
|
80 |
default="file://%s" % os.path.expanduser("~/mirror/ubuntu/"), |
|
81 |
help="distribution archive URL") |
|
82 |
self.options, self.args = parser.parse_args(self.raw_args) |
|
83 |
||
84 |
self.options.launchpad = Launchpad.login_anonymously( |
|
85 |
'germinate-for-proposed', self.options.launchpad_instance) |
|
86 |
lputils.setup_location(self.options, default_pocket="Proposed") |
|
87 |
self.germinateroot = "%s.new" % self.options.output |
|
88 |
||
89 |
def setUpDirs(self): |
|
90 |
"""Create output directories if they did not already exist."""
|
|
91 |
if os.path.exists(self.germinateroot): |
|
92 |
shutil.rmtree(self.germinateroot) |
|
93 |
logging.debug("Creating germinate root %s." % self.germinateroot) |
|
94 |
os.makedirs(self.germinateroot) |
|
95 |
||
96 |
def addLogHandler(self): |
|
97 |
"""Send germinate's log output to a separate file."""
|
|
98 |
if self.germinate_logger is not None: |
|
99 |
return
|
|
100 |
||
101 |
self.germinate_logger = logging.getLogger("germinate") |
|
102 |
self.germinate_logger.setLevel(logging.INFO) |
|
103 |
self.log_file = os.path.join(self.germinateroot, "germinate.output") |
|
135
by Colin Watson
extra-germinate: Parallelise by architecture. |
104 |
self.log_handler = logging.FileHandler(self.log_file, mode="w") |
105 |
self.log_handler.setFormatter(GerminateFormatter()) |
|
106 |
self.germinate_logger.addHandler(self.log_handler) |
|
93
by Colin Watson
archive-reports, extra-germinate: do an extra germinate run for -proposed |
107 |
self.germinate_logger.propagate = False |
108 |
||
109 |
def setUp(self): |
|
110 |
"""Process options, and set up internal state."""
|
|
111 |
self.processOptions() |
|
112 |
self.setUpDirs() |
|
113 |
self.addLogHandler() |
|
114 |
||
115 |
def getComponents(self, series): |
|
116 |
"""Get the list of components to process for a given distroseries."""
|
|
117 |
return ["main", "restricted", "universe", "multiverse"] |
|
118 |
||
119 |
def makeSeedStructures(self, suite, flavours, seed_bases=None): |
|
120 |
series_name = suite.split("-")[0] |
|
121 |
structures = {} |
|
122 |
for flavour in flavours: |
|
123 |
try: |
|
124 |
structure = SeedStructure( |
|
125 |
"%s.%s" % (flavour, series_name), seed_bases=seed_bases) |
|
126 |
if len(structure): |
|
127 |
structures[flavour] = structure |
|
128 |
else: |
|
129 |
logging.warning( |
|
130 |
"Skipping empty seed structure for %s.%s", |
|
131 |
flavour, series_name) |
|
132 |
except SeedError as e: |
|
133 |
logging.warning( |
|
134 |
"Failed to fetch seeds for %s.%s: %s", |
|
135 |
flavour, series_name, e) |
|
136 |
return structures |
|
137 |
||
138 |
def logGerminateProgress(self, *args): |
|
139 |
"""Log a "progress" entry to the germinate log file.
|
|
140 |
||
141 |
Germinate logs quite a bit of detailed information. To make it
|
|
142 |
easier to see the structure of its operation, GerminateFormatter
|
|
143 |
allows tagging some log entries as "progress" entries, which are
|
|
144 |
printed without a prefix.
|
|
145 |
"""
|
|
146 |
self.germinate_logger.info(*args, extra={"progress": True}) |
|
147 |
||
97
by Colin Watson
archive-reports, extra-germinate: write out extra germinate files by series rather than suite, to match expectations of component-mismatches |
148 |
def composeOutputPath(self, flavour, series_name, arch, base): |
93
by Colin Watson
archive-reports, extra-germinate: do an extra germinate run for -proposed |
149 |
return os.path.join( |
97
by Colin Watson
archive-reports, extra-germinate: write out extra germinate files by series rather than suite, to match expectations of component-mismatches |
150 |
self.germinateroot, |
151 |
"%s_%s_%s_%s" % (base, flavour, series_name, arch)) |
|
93
by Colin Watson
archive-reports, extra-germinate: do an extra germinate run for -proposed |
152 |
|
153 |
def recordOutput(self, path, seed_outputs): |
|
154 |
if seed_outputs is not None: |
|
155 |
seed_outputs.add(os.path.basename(path)) |
|
156 |
||
157 |
def writeGerminateOutput(self, germinator, structure, flavour, |
|
158 |
suite, arch, seed_outputs=None): |
|
159 |
"""Write dependency-expanded output files.
|
|
160 |
||
161 |
These files are a reduced subset of those written by the germinate
|
|
162 |
command-line program.
|
|
163 |
"""
|
|
97
by Colin Watson
archive-reports, extra-germinate: write out extra germinate files by series rather than suite, to match expectations of component-mismatches |
164 |
series_name = suite.split("-")[0] |
165 |
path = partial(self.composeOutputPath, flavour, series_name, arch) |
|
93
by Colin Watson
archive-reports, extra-germinate: do an extra germinate run for -proposed |
166 |
|
167 |
# The structure file makes it possible to figure out how the other
|
|
168 |
# output files relate to each other.
|
|
169 |
structure.write(path("structure")) |
|
170 |
self.recordOutput(path("structure"), seed_outputs) |
|
171 |
||
172 |
# "all" and "all.sources" list the full set of binary and source
|
|
173 |
# packages respectively for a given flavour/suite/architecture
|
|
174 |
# combination.
|
|
175 |
germinator.write_all_list(structure, path("all")) |
|
176 |
self.recordOutput(path("all"), seed_outputs) |
|
177 |
germinator.write_all_source_list(structure, path("all.sources")) |
|
178 |
self.recordOutput(path("all.sources"), seed_outputs) |
|
179 |
||
180 |
# Write the dependency-expanded output for each seed. Several of
|
|
181 |
# these are used by archive administration tools, and others are
|
|
182 |
# useful for debugging, so it's best to just write them all.
|
|
183 |
for seedname in structure.names: |
|
184 |
germinator.write_full_list(structure, path(seedname), seedname) |
|
185 |
self.recordOutput(path(seedname), seed_outputs) |
|
186 |
||
187 |
def germinateArchFlavour(self, germinator, suite, arch, flavour, |
|
188 |
structure, primary_flavour, seed_outputs=None): |
|
189 |
"""Germinate seeds on a single flavour for a single architecture."""
|
|
190 |
# Expand dependencies.
|
|
191 |
germinator.plant_seeds(structure) |
|
192 |
germinator.grow(structure) |
|
193 |
germinator.add_extras(structure) |
|
194 |
||
195 |
self.writeGerminateOutput( |
|
196 |
germinator, structure, flavour, suite, arch, |
|
197 |
seed_outputs=seed_outputs) |
|
198 |
||
135
by Colin Watson
extra-germinate: Parallelise by architecture. |
199 |
def germinateArch(self, suite, components, arch, flavours, structures): |
93
by Colin Watson
archive-reports, extra-germinate: do an extra germinate run for -proposed |
200 |
"""Germinate seeds on all flavours for a single architecture."""
|
135
by Colin Watson
extra-germinate: Parallelise by architecture. |
201 |
# Buffer log output for each architecture so that it appears
|
202 |
# sequential.
|
|
203 |
self.germinate_logger.removeHandler(self.log_handler) |
|
204 |
log_handler = BufferHandler() |
|
205 |
self.germinate_logger.addHandler(log_handler) |
|
206 |
||
93
by Colin Watson
archive-reports, extra-germinate: do an extra germinate run for -proposed |
207 |
germinator = Germinator(arch) |
208 |
||
209 |
# Compute ogre model.
|
|
210 |
if "-" in suite: |
|
211 |
series, pocket = suite.split("-") |
|
212 |
suites = [series] |
|
213 |
if pocket in ("updates", "security", "proposed"): |
|
214 |
suites.append("%s-security" % series) |
|
215 |
if pocket in ("updates", "proposed"): |
|
216 |
suites.append("%s-updates" % series) |
|
217 |
if pocket == "proposed": |
|
218 |
suites.append("%s-proposed" % series) |
|
219 |
else: |
|
220 |
suites = [suite] |
|
221 |
||
222 |
# Read archive metadata.
|
|
223 |
archive = TagFile( |
|
224 |
suites, components, arch, self.options.archiveroot, cleanup=True) |
|
225 |
germinator.parse_archive(archive) |
|
226 |
||
135
by Colin Watson
extra-germinate: Parallelise by architecture. |
227 |
seed_outputs = set() |
93
by Colin Watson
archive-reports, extra-germinate: do an extra germinate run for -proposed |
228 |
for flavour in flavours: |
229 |
logging.info( |
|
230 |
"Germinating for %s/%s/%s", flavour, suite, arch) |
|
231 |
# Add this to the germinate log as well so that that can be
|
|
232 |
# debugged more easily. Log a separator line first.
|
|
233 |
self.logGerminateProgress("") |
|
234 |
self.logGerminateProgress( |
|
235 |
"Germinating for %s/%s/%s", flavour, suite, arch) |
|
236 |
||
237 |
self.germinateArchFlavour( |
|
238 |
germinator, suite, arch, flavour, |
|
239 |
structures[flavour], flavour == flavours[0], |
|
240 |
seed_outputs=seed_outputs) |
|
241 |
||
135
by Colin Watson
extra-germinate: Parallelise by architecture. |
242 |
return log_handler.records, seed_outputs |
243 |
||
244 |
def germinateArchChild(self, close_in_child, wfd, *args): |
|
245 |
"""Helper method to call germinateArch in a forked child process."""
|
|
246 |
try: |
|
247 |
for fd in close_in_child: |
|
248 |
os.close(fd) |
|
249 |
with os.fdopen(wfd, "wb") as writer: |
|
250 |
pickle.dump(self.germinateArch(*args), writer, -1) |
|
251 |
return 0 |
|
252 |
except: |
|
253 |
traceback.print_exc() |
|
254 |
pickle.dump(([], set()), writer, -1) |
|
255 |
return 1 |
|
256 |
||
93
by Colin Watson
archive-reports, extra-germinate: do an extra germinate run for -proposed |
257 |
def removeStaleOutputs(self, suite, seed_outputs): |
258 |
"""Remove stale outputs for a suite.
|
|
259 |
||
260 |
Any per-seed outputs not in seed_outputs are considered stale.
|
|
261 |
"""
|
|
97
by Colin Watson
archive-reports, extra-germinate: write out extra germinate files by series rather than suite, to match expectations of component-mismatches |
262 |
series_name = suite.split("-")[0] |
93
by Colin Watson
archive-reports, extra-germinate: do an extra germinate run for -proposed |
263 |
all_outputs = glob.glob( |
97
by Colin Watson
archive-reports, extra-germinate: write out extra germinate files by series rather than suite, to match expectations of component-mismatches |
264 |
os.path.join(self.germinateroot, "*_*_%s_*" % series_name)) |
93
by Colin Watson
archive-reports, extra-germinate: do an extra germinate run for -proposed |
265 |
for output in all_outputs: |
266 |
if os.path.basename(output) not in seed_outputs: |
|
267 |
os.remove(output) |
|
268 |
||
269 |
def generateExtraOverrides(self, suite, components, architectures, |
|
270 |
flavours, seed_bases=None): |
|
271 |
structures = self.makeSeedStructures( |
|
272 |
suite, flavours, seed_bases=seed_bases) |
|
273 |
||
274 |
if structures: |
|
135
by Colin Watson
extra-germinate: Parallelise by architecture. |
275 |
procs = [] |
276 |
close_in_child = [] |
|
277 |
for arch in architectures: |
|
278 |
rfd, wfd = os.pipe() |
|
279 |
close_in_child.append(rfd) |
|
280 |
pid = os.fork() |
|
281 |
if pid == 0: # child |
|
282 |
os._exit(self.germinateArchChild( |
|
283 |
close_in_child, wfd, |
|
284 |
suite, components, arch, flavours, structures)) |
|
285 |
else: # parent |
|
286 |
os.close(wfd) |
|
287 |
reader = os.fdopen(rfd, "rb") |
|
288 |
procs.append((pid, reader)) |
|
289 |
||
93
by Colin Watson
archive-reports, extra-germinate: do an extra germinate run for -proposed |
290 |
seed_outputs = set() |
135
by Colin Watson
extra-germinate: Parallelise by architecture. |
291 |
for pid, reader in procs: |
292 |
log_records, arch_seed_outputs = pickle.load(reader) |
|
293 |
for log_record in log_records: |
|
294 |
self.germinate_logger.handle(pickle.loads(log_record)) |
|
295 |
seed_outputs |= arch_seed_outputs |
|
296 |
reader.close() |
|
297 |
os.waitpid(pid, 0) |
|
93
by Colin Watson
archive-reports, extra-germinate: do an extra germinate run for -proposed |
298 |
self.removeStaleOutputs(suite, seed_outputs) |
299 |
||
300 |
def process(self, seed_bases=None): |
|
301 |
"""Do the bulk of the work."""
|
|
302 |
self.setUp() |
|
303 |
||
304 |
components = self.getComponents(self.options.series) |
|
305 |
architectures = [ |
|
306 |
a.architecture_tag for a in self.options.architectures] |
|
307 |
||
308 |
self.generateExtraOverrides( |
|
309 |
self.options.suite, components, architectures, self.args, |
|
310 |
seed_bases=seed_bases) |
|
311 |
||
312 |
if os.path.exists("%s.old" % self.options.output): |
|
313 |
shutil.rmtree("%s.old" % self.options.output) |
|
314 |
if os.path.exists(self.options.output): |
|
315 |
os.rename(self.options.output, "%s.old" % self.options.output) |
|
316 |
os.rename(self.germinateroot, self.options.output) |
|
317 |
if os.path.exists("%s.old" % self.options.output): |
|
318 |
shutil.rmtree("%s.old" % self.options.output) |
|
319 |
||
320 |
||
321 |
if __name__ == "__main__": |
|
322 |
ExtraGerminate(sys.argv[1:]).process() |