Merge lp:~cjwatson/launchpad/gina-deb-vendor-debian into lp:launchpad

Proposed by Colin Watson on 2012-06-19
Status: Merged
Approved by: William Grant on 2012-06-20
Approved revision: no longer in the source branch.
Merged at revision: 15457
Proposed branch: lp:~cjwatson/launchpad/gina-deb-vendor-debian
Merge into: lp:launchpad
Diff against target: 387 lines (+118/-33)
5 files modified
lib/lp/archiveuploader/utils.py (+7/-3)
lib/lp/soyuz/scripts/gina/handlers.py (+4/-3)
lib/lp/soyuz/scripts/gina/packages.py (+12/-11)
lib/lp/soyuz/scripts/tests/test_gina.py (+72/-1)
scripts/gina.py (+23/-15)
To merge this branch: bzr merge lp:~cjwatson/launchpad/gina-deb-vendor-debian
Reviewer Review Type Date Requested Status
William Grant code 2012-06-19 Approve on 2012-06-20
Review via email: mp+111120@code.launchpad.net

Commit Message

Make gina extract Debian packages with DEB_VENDOR=debian.

Description of the Change

== Summary ==

Bug 901334 reports gina raising ExecutionErrors while attempting to extract a few source packages in Debian. The logs indicate that the affected packages are currently accountsservice, qjackctl, and xfce4-smartbookmark-plugin.

== Proposed fix ==

The affected packages can all be fixed by extracting with DEB_VENDOR=debian set in the environment, which tells dpkg-source to use the Debian patch series rather than the (broken) Ubuntu one. This is correct anyway when extracting Debian source packages, as opposed to source packages that have been synced from Debian into Ubuntu.

Not many packages make use of the facility to have different Debian and Ubuntu patch series, never mind leaving one of them broken, which explains why this failure only occurs on three packages. We'll still have to fix this up to make them build on Ubuntu, but this should be handled in Ubuntu (or Debian) rather than having gina fail.

== Implementation details ==

The only annoyance is that we have to pass the distribution name down through quite a number of layers of code.

== LOC Rationale ==

+84. I have 2074 lines of credit and just submitted a branch which gains me another 816, so I'd like to use some of that credit for this critical bug fix.

== Tests ==

bin/test -vvct gina

== Demo and Q/A ==

This I'm unsure about. https://code.launchpad.net/~jtv/launchpad/katie-and-gina-are-bad-bad-girls/+merge/75472 suggests that I might want to use a scratch table to compare gina results before and after the upgrade, and presumably I could inject one of the broken packages into the local mirror used for this. However, I'm unsure about how well gina would deal with the local mirror being several months older than dogfood's last database import, and thus several months older than its current /debian/+source/*. Any advice?

== Lint ==

The usual false positive for scripts:

./scripts/gina.py
      22: '_pythonpath' imported but unused

To post a comment you must log in.
William Grant (wgrant) wrote :

Looks good, thanks. Just one comment.

197 + # Some source packages unpack differently depending on dpkg's idea
198 + # of the "vendor", and in extreme cases may even fail with some
199 + # vendors. gina always sets the vendor to "debian" to ensure that
200 + # it unpacks packages as if unpacking on Debian.

It sets it to the target distro name, not always "debian".

review: Approve (code)
Colin Watson (cjwatson) wrote :

Consensus on IRC regarding QA appeared to be that we'll comment out the transaction.commit()s on dogfood so that we can just make sure it extracts things correctly.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/archiveuploader/utils.py'
2--- lib/lp/archiveuploader/utils.py 2011-06-14 20:20:13 +0000
3+++ lib/lp/archiveuploader/utils.py 2012-06-20 02:06:21 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Archive uploader utilities."""
10@@ -28,6 +28,7 @@
11
12
13 import email.Header
14+import os
15 import re
16 import signal
17 import subprocess
18@@ -267,7 +268,7 @@
19 return fix_maintainer(content, fieldname)
20
21
22-def extract_dpkg_source(dsc_filepath, target):
23+def extract_dpkg_source(dsc_filepath, target, vendor=None):
24 """Extract a source package by dsc file path.
25
26 :param dsc_filepath: Path of the DSC file
27@@ -281,9 +282,12 @@
28 # blosxom/2009-07-02-python-sigpipe.html
29 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
30 args = ["dpkg-source", "-sn", "-x", dsc_filepath]
31+ env = dict(os.environ)
32+ if vendor is not None:
33+ env["DEB_VENDOR"] = vendor
34 dpkg_source = subprocess.Popen(
35 args, stdout=subprocess.PIPE, cwd=target, stderr=subprocess.PIPE,
36- preexec_fn=subprocess_setup)
37+ preexec_fn=subprocess_setup, env=env)
38 output, unused = dpkg_source.communicate()
39 result = dpkg_source.wait()
40 if result != 0:
41
42=== modified file 'lib/lp/soyuz/scripts/gina/handlers.py'
43--- lib/lp/soyuz/scripts/gina/handlers.py 2012-01-05 14:23:33 +0000
44+++ lib/lp/soyuz/scripts/gina/handlers.py 2012-06-20 02:06:21 +0000
45@@ -179,7 +179,7 @@
46 self.imported_bins = {}
47
48 self.sphandler = SourcePackageHandler(
49- archive_root, pocket, component_override)
50+ distro_name, archive_root, pocket, component_override)
51 self.bphandler = BinaryPackageHandler(
52 self.sphandler, archive_root, pocket)
53
54@@ -454,8 +454,9 @@
55 on the launchpad db a little easier.
56 """
57
58- def __init__(self, archive_root, pocket, component_override):
59+ def __init__(self, distro_name, archive_root, pocket, component_override):
60 self.distro_handler = DistroHandler()
61+ self.distro_name = distro_name
62 self.archive_root = archive_root
63 self.pocket = pocket
64 self.component_override = component_override
65@@ -492,7 +493,7 @@
66 return None
67
68 # Process the package
69- sp_data.process_package(self.archive_root)
70+ sp_data.process_package(self.distro_name, self.archive_root)
71 sp_data.ensure_complete()
72
73 spr = self.createSourcePackageRelease(sp_data, distroseries)
74
75=== modified file 'lib/lp/soyuz/scripts/gina/packages.py'
76--- lib/lp/soyuz/scripts/gina/packages.py 2011-12-30 06:14:56 +0000
77+++ lib/lp/soyuz/scripts/gina/packages.py 2012-06-20 02:06:21 +0000
78@@ -1,4 +1,4 @@
79-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
80+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
81 # GNU Affero General Public License version 3 (see the file LICENSE).
82
83 # pylint: disable-msg=W0631
84@@ -100,11 +100,11 @@
85 raise PoolFileNotFound("File %s not in archive" % filename)
86
87
88-def unpack_dsc(package, version, component, archive_root):
89+def unpack_dsc(package, version, component, distro_name, archive_root):
90 dsc_name, dsc_path, component = get_dsc_path(package, version,
91 component, archive_root)
92 try:
93- extract_dpkg_source(dsc_path, ".")
94+ extract_dpkg_source(dsc_path, ".", vendor=distro_name)
95 except DpkgSourceError, e:
96 raise ExecutionError("Error %d unpacking source" % e.result)
97
98@@ -114,9 +114,9 @@
99 return source_dir, dsc_path
100
101
102-def read_dsc(package, version, component, archive_root):
103+def read_dsc(package, version, component, distro_name, archive_root):
104 source_dir, dsc_path = unpack_dsc(package, version, component,
105- archive_root)
106+ distro_name, archive_root)
107
108 dsc = open(dsc_path).read().strip()
109
110@@ -229,7 +229,7 @@
111 if missing:
112 raise MissingRequiredArguments(missing)
113
114- def process_package(self, archive_root):
115+ def process_package(self, distro_name, archive_root):
116 """Process the package using the files located in the archive.
117
118 Raises PoolFileNotFound if a file is not found in the pool.
119@@ -240,7 +240,7 @@
120 cwd = os.getcwd()
121 os.chdir(tempdir)
122 try:
123- self.do_package(archive_root)
124+ self.do_package(distro_name, archive_root)
125 finally:
126 os.chdir(cwd)
127 # We only rmtree if everything worked as expected; otherwise,
128@@ -250,7 +250,7 @@
129 self.date_uploaded = UTC_NOW
130 return True
131
132- def do_package(self, archive_root):
133+ def do_package(self, distro_name, archive_root):
134 """To be provided by derived class."""
135 raise NotImplementedError
136
137@@ -332,14 +332,15 @@
138
139 AbstractPackageData.__init__(self)
140
141- def do_package(self, archive_root):
142+ def do_package(self, distro_name, archive_root):
143 """Get the Changelog and urgency from the package on archive.
144
145 If successful processing of the package occurs, this method
146 sets the changelog and urgency attributes.
147 """
148 dsc, changelog, copyright = read_dsc(
149- self.package, self.version, self.component, archive_root)
150+ self.package, self.version, self.component, distro_name,
151+ archive_root)
152
153 self.dsc = encoding.guess(dsc)
154 self.copyright = encoding.guess(copyright)
155@@ -497,7 +498,7 @@
156
157 AbstractPackageData.__init__(self)
158
159- def do_package(self, archive_root):
160+ def do_package(self, distro_name, archive_root):
161 """Grab shared library info from .deb."""
162 fullpath = os.path.join(archive_root, self.filename)
163 if not os.path.exists(fullpath):
164
165=== modified file 'lib/lp/soyuz/scripts/tests/test_gina.py'
166--- lib/lp/soyuz/scripts/tests/test_gina.py 2012-01-01 02:58:52 +0000
167+++ lib/lp/soyuz/scripts/tests/test_gina.py 2012-06-20 02:06:21 +0000
168@@ -1,12 +1,18 @@
169-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
170+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
171 # GNU Affero General Public License version 3 (see the file LICENSE).
172
173 from doctest import DocTestSuite
174+import hashlib
175+import os
176+from textwrap import dedent
177 from unittest import TestLoader
178
179+from lp.archiveuploader.tagfiles import parse_tagfile
180 from lp.registry.interfaces.pocket import PackagePublishingPocket
181 from lp.services.log.logger import DevNullLogger
182+from lp.services.tarfile_helpers import LaunchpadWriteTarFile
183 from lp.soyuz.enums import PackagePublishingStatus
184+from lp.soyuz.scripts.gina import ExecutionError
185 from lp.soyuz.scripts.gina.dominate import dominate_imported_source_packages
186 import lp.soyuz.scripts.gina.handlers
187 from lp.soyuz.scripts.gina.handlers import (
188@@ -116,6 +122,71 @@
189 [pub.status for pub in pubs])
190
191
192+class TestSourcePackageData(TestCaseWithFactory):
193+
194+ layer = ZopelessDatabaseLayer
195+
196+ def test_unpack_dsc_with_vendor(self):
197+ # Some source packages unpack differently depending on dpkg's idea
198+ # of the "vendor", and in extreme cases may even fail with some
199+ # vendors. gina always sets the vendor to the target distribution
200+ # name to ensure that it unpacks packages as if unpacking on that
201+ # distribution.
202+ archive_root = self.useTempDir()
203+ pool_dir = os.path.join(archive_root, "pool/main/f/foo")
204+ os.makedirs(pool_dir)
205+
206+ # Synthesise a package that can be unpacked with DEB_VENDOR=debian
207+ # but not with DEB_VENDOR=ubuntu.
208+ with open(os.path.join(
209+ pool_dir, "foo_1.0.orig.tar.gz"), "wb+") as buffer:
210+ orig_tar = LaunchpadWriteTarFile(buffer)
211+ orig_tar.add_directory("foo-1.0")
212+ orig_tar.close()
213+ buffer.seek(0)
214+ orig_tar_contents = buffer.read()
215+ with open(os.path.join(
216+ pool_dir, "foo_1.0-1.debian.tar.gz"), "wb+") as buffer:
217+ debian_tar = LaunchpadWriteTarFile(buffer)
218+ debian_tar.add_file("debian/source/format", "3.0 (quilt)\n")
219+ debian_tar.add_file(
220+ "debian/patches/ubuntu.series", "--- corrupt patch\n")
221+ debian_tar.add_file("debian/rules", "")
222+ debian_tar.close()
223+ buffer.seek(0)
224+ debian_tar_contents = buffer.read()
225+ dsc_path = os.path.join(pool_dir, "foo_1.0-1.dsc")
226+ with open(dsc_path, "w") as dsc:
227+ dsc.write(dedent("""\
228+ Format: 3.0 (quilt)
229+ Source: foo
230+ Binary: foo
231+ Architecture: all
232+ Version: 1.0-1
233+ Maintainer: Foo Bar <foo.bar@canonical.com>
234+ Files:
235+ %s %s foo_1.0.orig.tar.gz
236+ %s %s foo_1.0-1.debian.tar.gz
237+ """ % (
238+ hashlib.md5(orig_tar_contents).hexdigest(),
239+ len(orig_tar_contents),
240+ hashlib.md5(debian_tar_contents).hexdigest(),
241+ len(debian_tar_contents))))
242+
243+ dsc_contents = parse_tagfile(dsc_path)
244+ dsc_contents["Directory"] = pool_dir
245+ dsc_contents["Package"] = "foo"
246+ dsc_contents["Component"] = "main"
247+ dsc_contents["Section"] = "misc"
248+
249+ sp_data = SourcePackageData(**dsc_contents)
250+ # Unpacking this in an Ubuntu context fails.
251+ self.assertRaises(
252+ ExecutionError, sp_data.do_package, "ubuntu", archive_root)
253+ # But all is well in a Debian context.
254+ sp_data.do_package("debian", archive_root)
255+
256+
257 class TestSourcePackagePublisher(TestCaseWithFactory):
258
259 layer = ZopelessDatabaseLayer
260
261=== modified file 'scripts/gina.py'
262--- scripts/gina.py 2012-01-01 03:13:08 +0000
263+++ scripts/gina.py 2012-06-20 02:06:21 +0000
264@@ -1,6 +1,6 @@
265 #!/usr/bin/python -S
266 #
267-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
268+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
269 # GNU Affero General Public License version 3 (see the file LICENSE).
270
271 # This module uses relative imports.
272@@ -114,7 +114,7 @@
273 importer_handler = ImporterHandler(
274 ztm, distro, distroseries, package_root, pocket, component_override)
275
276- import_sourcepackages(packages_map, package_root, importer_handler)
277+ import_sourcepackages(distro, packages_map, package_root, importer_handler)
278 importer_handler.commit()
279
280 # XXX JeroenVermeulen 2011-09-07 bug=843728: Dominate binaries as well.
281@@ -133,23 +133,26 @@
282 log.exception("Database setup required for run on %s", archtag)
283 sys.exit(1)
284
285- import_binarypackages(packages_map, package_root, importer_handler)
286+ import_binarypackages(distro, packages_map, package_root, importer_handler)
287 importer_handler.commit()
288
289
290-def attempt_source_package_import(source, package_root, importer_handler):
291+def attempt_source_package_import(distro, source, package_root,
292+ importer_handler):
293 """Attempt to import a source package, and handle typical errors."""
294 package_name = source.get("Package", "unknown")
295 try:
296 try:
297- do_one_sourcepackage(source, package_root, importer_handler)
298+ do_one_sourcepackage(
299+ distro, source, package_root, importer_handler)
300 except psycopg2.Error:
301 log.exception(
302 "Database error: unable to create SourcePackage for %s. "
303 "Retrying once..", package_name)
304 importer_handler.abort()
305 time.sleep(15)
306- do_one_sourcepackage(source, package_root, importer_handler)
307+ do_one_sourcepackage(
308+ distro, source, package_root, importer_handler)
309 except (
310 InvalidVersionError, MissingRequiredArguments,
311 DisplayNameDecodingError):
312@@ -168,7 +171,8 @@
313 "Database duplication processing %s", package_name)
314
315
316-def import_sourcepackages(packages_map, package_root, importer_handler):
317+def import_sourcepackages(distro, packages_map, package_root,
318+ importer_handler):
319 # Goes over src_map importing the sourcepackages packages.
320 count = 0
321 npacks = len(packages_map.src_map)
322@@ -178,25 +182,26 @@
323 for source in packages_map.src_map[package]:
324 count += 1
325 attempt_source_package_import(
326- source, package_root, importer_handler)
327+ distro, source, package_root, importer_handler)
328 if COUNTDOWN and (count % COUNTDOWN == 0):
329 log.warn('%i/%i sourcepackages processed', count, npacks)
330
331
332-def do_one_sourcepackage(source, package_root, importer_handler):
333+def do_one_sourcepackage(distro, source, package_root, importer_handler):
334 source_data = SourcePackageData(**source)
335 if importer_handler.preimport_sourcecheck(source_data):
336 # Don't bother reading package information if the source package
337 # already exists in the database
338 log.info('%s already exists in the archive', source_data.package)
339 return
340- source_data.process_package(package_root)
341+ source_data.process_package(distro, package_root)
342 source_data.ensure_complete()
343 importer_handler.import_sourcepackage(source_data)
344 importer_handler.commit()
345
346
347-def import_binarypackages(packages_map, package_root, importer_handler):
348+def import_binarypackages(distro, packages_map, package_root,
349+ importer_handler):
350 nosource = []
351
352 # Run over all the architectures we have
353@@ -212,7 +217,8 @@
354 try:
355 try:
356 do_one_binarypackage(
357- binary, archtag, package_root, importer_handler)
358+ distro, binary, archtag, package_root,
359+ importer_handler)
360 except psycopg2.Error:
361 log.exception(
362 "Database errors when importing a BinaryPackage "
363@@ -220,7 +226,8 @@
364 importer_handler.abort()
365 time.sleep(15)
366 do_one_binarypackage(
367- binary, archtag, package_root, importer_handler)
368+ distro, binary, archtag, package_root,
369+ importer_handler)
370 except (InvalidVersionError, MissingRequiredArguments):
371 log.exception(
372 "Unable to create BinaryPackageData for %s", package_name)
373@@ -257,12 +264,13 @@
374 log.warn(pkg)
375
376
377-def do_one_binarypackage(binary, archtag, package_root, importer_handler):
378+def do_one_binarypackage(distro, binary, archtag, package_root,
379+ importer_handler):
380 binary_data = BinaryPackageData(**binary)
381 if importer_handler.preimport_binarycheck(archtag, binary_data):
382 log.info('%s already exists in the archive', binary_data.package)
383 return
384- binary_data.process_package(package_root)
385+ binary_data.process_package(distro, package_root)
386 importer_handler.import_binarypackage(archtag, binary_data)
387 importer_handler.commit()
388