~ubuntu-archive/ubuntu-archive-scripts/trunk

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()