Merge lp:~brian-murray/apport/retrace-package-versions into lp:~apport-hackers/apport/trunk

Proposed by Brian Murray
Status: Merged
Merged at revision: 2791
Proposed branch: lp:~brian-murray/apport/retrace-package-versions
Merge into: lp:~apport-hackers/apport/trunk
Diff against target: 200 lines (+50/-21)
3 files modified
apport/sandboxutils.py (+22/-4)
backends/packaging-apt-dpkg.py (+21/-13)
test/test_backend_apt_dpkg.py (+7/-4)
To merge this branch: bzr merge lp:~brian-murray/apport/retrace-package-versions
Reviewer Review Type Date Requested Status
Martin Pitt (community) Needs Fixing
Review via email: mp+210308@code.launchpad.net

Description of the change

These changes to apport and its retracing process will, in some cases, attempt to install the specific package version that the client was using when they experienced the crash.

It does this by examining the report's Package and Dependencies keys to determine the version of the package used. However, this is incomplete in that the lookup for returning a package to which a file belongs (get_file_package used for items in ProcMaps) is unaware of package versions. Given that _search_contents is searching each pocket individually we would know the pocket from which the package came, so perhaps we could do another search using those two things. Do you have any ideas?

I've tested these changes using some crashes and corresponding coredumps I received from the error tracker. I added some information to apport-retrace to determine how many of the packages remained without a version:

/mnt/sec-machines/20140204-cores/03273f1e-1f10-11e3-b2fe-2c768aafd08c.crash
57 packages have a version, 38 do not.
/mnt/sec-machines/20140204-cores/10165494-3e32-11e3-9577-2c768aafd08c.crash
9 packages have a version, 2 do not.
/mnt/sec-machines/20140204-cores/2e2ee904-3d70-11e3-af86-2c768aafd08c.crash
60 packages have a version, 4 do not.
/mnt/sec-machines/20140204-cores/4dd5303e-2096-11e3-921f-2c768aafd08c.crash
85 packages have a version, 20 do not.
/mnt/sec-machines/20140204-cores/586432ac-8bb7-11e3-baa3-e4115b0f8a4a.crash
115 packages have a version, 17 do not.
/mnt/sec-machines/20140204-cores/6f6484e6-145e-11e3-a19d-e4115b0f8a4a.crash
47 packages have a version, 11 do not.
/mnt/sec-machines/20140204-cores/89c60494-3ecb-11e3-afb2-e4115b0f8a4a.crash
30 packages have a version, 0 do not.
/mnt/sec-machines/20140204-cores/8e546922-8d60-11e3-a75b-e4115b0f8a4a.crash
68 packages have a version, 41 do not.
/mnt/sec-machines/20140204-cores/a4f86840-0f43-11e3-9f48-e4115b0f8a4a.crash
59 packages have a version, 48 do not.
/mnt/sec-machines/20140204-cores/b925ddf4-3d9f-11e3-a1bf-2c768aafd08c.crash
102 packages have a version, 4 do not.
/mnt/sec-machines/20140204-cores/ec88c4be-3d21-11e3-a287-2c768aafd08c.crash
1 packages have a version, 55 do not.

So it seems like there are still some improvements to be made. I've also renamed a variable from c to cache and as c is short for continue in ipdb and this made it hard to debug cache.

To post a comment you must log in.
Revision history for this message
Martin Pitt (pitti) wrote :

8 -

Bikeshedding, I know, but I like the empty line here better as the following code is in a different flow branch/context.

22 + # first, grab the versions that we captured at crash time

As there is no "second", perhaps just "# create a package -> version map from report"?

34 + return [(p, pkg_vers.get(p, 'None')) for p in pkgs]

This needs to be None, not 'None'. get() defaults to None as fallback, so just pkg_vers.get(p) will do.

43 + if pkg in report['Package']:

This could fail in weird corner cases if it matches a substring or even the version number. Let's use this instead:

  if report['Package'].startswith(pkg + ' ')

45 + if version:
46 + pkgs.append((pkg, version))
47 + else:
48 + pkgs.append((pkg, None))

This needs to be indented by a block, as version is otherwise not defined.

The other changes look good to me. This still needs a test case. This can be tested by adjusting test_install_packages_versioned() in test/test_backend_apt_dpkg.py, which currently uses Ubuntu precise. There are plenty of different versions in -updates to choose from :-), i. e. selecting a version from precise final should download that version instead of current precise-updates.

> However, this is incomplete in that the lookup for returning a package to which a file belongs (get_file_package used for items in ProcMaps) is unaware of package versions.

If you are speaking of plugins or other things with undeclared Depends:, and thus we don't have the version of the package that we reverse-mapped from ProcMaps, then we lost; we should just continue to install the most current version of the package. If you mean something else, I'm afraid I don't understand. As long as we have a package version in Package: or Dependencies:, that's the one we should install, and we have the report object available in all places which are concerned about package installation.

Thanks for working on this!

review: Needs Fixing
2761. By Brian Murray

make changes based on pitti's feedback

Revision history for this message
Brian Murray (brian-murray) wrote :

On Fri, Mar 14, 2014 at 09:23:01AM -0000, Martin Pitt wrote:
> Review: Needs Fixing
>
> 8 -
>
> Bikeshedding, I know, but I like the empty line here better as the following code is in a different flow branch/context.
>
> 22 + # first, grab the versions that we captured at crash time
>
> As there is no "second", perhaps just "# create a package -> version map from report"?
>
> 34 + return [(p, pkg_vers.get(p, 'None')) for p in pkgs]
>
> This needs to be None, not 'None'. get() defaults to None as fallback, so just pkg_vers.get(p) will do.
>
> 43 + if pkg in report['Package']:
>
> This could fail in weird corner cases if it matches a substring or even the version number. Let's use this instead:
>
> if report['Package'].startswith(pkg + ' ')
>
> 45 + if version:
> 46 + pkgs.append((pkg, version))
> 47 + else:
> 48 + pkgs.append((pkg, None))
>
> This needs to be indented by a block, as version is otherwise not defined.

I've made the above changes.

> The other changes look good to me. This still needs a test case. This can be tested by adjusting test_install_packages_versioned() in test/test_backend_apt_dpkg.py, which currently uses Ubuntu precise. There are plenty of different versions in -updates to choose from :-), i. e. selecting a version from precise final should download that version instead of current precise-updates.

I'll work on the test case now.

> > However, this is incomplete in that the lookup for returning a package to which a file belongs (get_file_package used for items in ProcMaps) is unaware of package versions.
>
> If you are speaking of plugins or other things with undeclared Depends:, and thus we don't have the version of the package that we reverse-mapped from ProcMaps, then we lost; we should just continue to install the most current version of the package. If you mean something else, I'm afraid I don't understand. As long as we have a package version in Package: or Dependencies:, that's the one we should install, and we have the report object available in all places which are concerned about package installation.

This is what I am referring to but instead of just installing the most
current version of the package I'm suggesting that we record which
pocket '_search_contents' found the file in and then query Launchpad (or
something) to determine what is the current version of that package in
that pocket. Then we would be installing a more correct package version
and could look at enabling the -proposed repository in the retracer
configurations.

--
Brian Murray

2762. By Brian Murray

update test_backend_apt_dpkg.py to install the version of coreutils from -updates and the other packages from the release pocket

Revision history for this message
Martin Pitt (pitti) wrote :

I committed the c → cache renaming in http://bazaar.launchpad.net/~apport-hackers/apport/trunk/revision/2790 as it is quite a lot of noise and unrelated to the actual change here.

I'm not happy about the test case, as it now assumes fixed version numbers in precise-updates, which may change in the future. I think it's better (as suggested above) to pin the version down to the precise-final one and confirm that those get installed instead of the latest precise-updates versions. I'll work on that.

Thanks!

Revision history for this message
Martin Pitt (pitti) wrote :

Also, in make_sandbox()'es second hunk it only checks report['Package'] but not 'Dependencies'? Should mostly be harmless though. But the inner if is missing an else path, otherwise we won't append the package at all.

Looks good to me otherwise, thanks!

Revision history for this message
Martin Pitt (pitti) wrote :

For the record, this also needs adjustment of install_packages()'s grabbing of the -dbg/-dbgsym package versions. I adjusted the test case to check the installed package versions, and with that it's now installing non-matching -dbg versions.

Revision history for this message
Martin Pitt (pitti) wrote :

And more generally, I'm afraid this patch is currently a no-op. While it now picks the right "candidate", this isn't being used for fetching packages, as

       for p in real_pkgs:
            cache[p].mark_install(False, False)

is always using the latest version. So this needs to be reworked completely.

review: Needs Fixing
Revision history for this message
Martin Pitt (pitti) wrote :

I merged the backends/packaging-apt-dpkg.py part of this and reworked the whole install_packages() to actually select the specified version in apt rather than just complaining about mismatches. I also changed the test case to actually verify the downloaded/installed package versions and be robust against further precise-updates versions: http://bazaar.launchpad.net/~apport-hackers/apport/trunk/revision/2791

This does not yet include the sandboxutils changes, as they go on top of this and aren't covered by tests.

Revision history for this message
Martin Pitt (pitti) wrote :

And finally, the cleaned up sandboxutils.py improvements: http://bazaar.launchpad.net/~apport-hackers/apport/trunk/revision/2792

Thanks!

Revision history for this message
Martin Pitt (pitti) wrote :

For the record, I rolled this out to the Launchpad retracers now.

Revision history for this message
Brian Murray (brian-murray) wrote :

Thanks for finishing this! I've had daisy updated now too. While we've now addressed versioning for the Package and Dependencies keys there may be still some improvements to make regarding ProcMaps. Previously we talked about this:

> If you are speaking of plugins or other things with undeclared Depends:, and thus we don't have the version > of the package that we reverse-mapped from ProcMaps, then we lost; we should just continue to install the
> most current version of the package. If you mean something else, I'm afraid I don't understand. As long as > we have a package version in Package: or Dependencies:, that's the one we should install, and we have the
> report object available in all places which are concerned about package installation.

This is what I am referring to but instead of just installing the most
current version of the package I'm suggesting that we record which
pocket '_search_contents' found the file in and then query Launchpad (or
something) to determine what is the current version of that package in
that pocket. Then we would be installing a more correct package version
than just using the latest version.

Revision history for this message
Martin Pitt (pitti) wrote :

Hey Brian,

Brian Murray [2014-04-23 17:22 -0000]:
> This is what I am referring to but instead of just installing the most
> current version of the package I'm suggesting that we record which
> pocket '_search_contents' found the file in and then query Launchpad (or
> something) to determine what is the current version of that package in
> that pocket.

How would that help? The file would be present in all pockets (unless
we are dealing with things like a library transition in an SRU, which
should be fairly uncommon -- but then the _search_contents query will
give us even better results), so we already know the current version
during apport-retrace time.

The only thing which would actually help with this is to do the file
-> package -> version mapping on the client side at report time. I. e.
at report time we need to walk all files in ProcMaps and add their
packages to Dependencies: if they aren't there already.
That'd be fairly expensive though.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'apport/sandboxutils.py'
2--- apport/sandboxutils.py 2013-09-30 06:07:14 +0000
3+++ apport/sandboxutils.py 2014-03-26 19:33:35 +0000
4@@ -83,9 +83,22 @@
5 apport.log('dynamically loaded %s needs package %s, queueing' % (l, pkg))
6 pkgs.add(pkg)
7 else:
8- apport.warning('%s is needed, but cannot be mapped to a package', l)
9-
10- return [(p, None) for p in pkgs]
11+ apport.warning('%s is needed, but cannot be mapped to a package', l)
12+
13+ pkg_vers = {}
14+ # create a package -> version map from the report
15+ for l in (report.get('Package', '') + '\n' + report.get('Dependencies', '')).splitlines():
16+ if not l.strip():
17+ continue
18+ try:
19+ (pkg, version) = l.split()[:2]
20+ except ValueError:
21+ apport.warning('invalid Package/Dependencies line: %s', l)
22+ # invalid line, ignore
23+ continue
24+ pkg_vers[pkg] = version
25+
26+ return [(p, pkg_vers.get(p)) for p in pkgs]
27
28
29 def make_sandbox(report, config_dir, cache_dir=None, sandbox_dir=None,
30@@ -184,7 +197,12 @@
31 arch=report.get('Architecture'))
32 if pkg:
33 apport.log('Installing extra package %s to get %s' % (pkg, path), log_timestamps)
34- pkgs.append((pkg, None))
35+ if report['Package'].startswith(pkg + ' '):
36+ version = report['Package'].split()[1]
37+ if version:
38+ pkgs.append((pkg, version))
39+ else:
40+ pkgs.append((pkg, None))
41 else:
42 apport.warning('Cannot find package which ships %s', path)
43
44
45=== modified file 'backends/packaging-apt-dpkg.py'
46--- backends/packaging-apt-dpkg.py 2014-01-22 08:24:55 +0000
47+++ backends/packaging-apt-dpkg.py 2014-03-26 19:33:35 +0000
48@@ -362,9 +362,11 @@
49 match = self.__fgrep_files(file, all_lists)
50
51 if match:
52+ # no version here
53 return os.path.splitext(os.path.basename(match))[0].split(':')[0]
54
55 if uninstalled:
56+ # no version here
57 return self._search_contents(file, map_cachedir, release, arch)
58 else:
59 return None
60@@ -605,15 +607,15 @@
61 else:
62 fetchProgress = apt.progress.base.AcquireProgress()
63 if not tmp_aptroot:
64- c = self._sandbox_cache(aptroot, apt_sources, fetchProgress)
65+ cache = self._sandbox_cache(aptroot, apt_sources, fetchProgress)
66 else:
67 self._build_apt_sandbox(aptroot, apt_sources)
68- c = apt.Cache(rootdir=os.path.abspath(aptroot))
69+ cache = apt.Cache(rootdir=os.path.abspath(aptroot))
70 try:
71- c.update(fetchProgress)
72+ cache.update(fetchProgress)
73 except apt.cache.FetchFailedException as e:
74 raise SystemError(str(e))
75- c.open()
76+ cache.open()
77
78 obsolete = ''
79
80@@ -623,7 +625,12 @@
81 real_pkgs = set()
82 for (pkg, ver) in packages:
83 try:
84- candidate = c[pkg].candidate
85+ versions = cache[pkg].versions
86+ if ver not in versions:
87+ # try the most recent if we don't have the right version
88+ candidate = cache[pkg].candidate
89+ else:
90+ candidate = cache[pkg].versions[ver]
91 except KeyError:
92 candidate = None
93 if not candidate:
94@@ -657,7 +664,7 @@
95 # Replaces/Depends, we can safely choose the first value
96 # here.
97 conflict = conflict[0]
98- if c.is_virtual_package(conflict[0]):
99+ if cache.is_virtual_package(conflict[0]):
100 try:
101 providers = virtual_mapping[conflict[0]]
102 except KeyError:
103@@ -680,32 +687,32 @@
104 os.unlink(path)
105
106 if candidate.architecture != 'all':
107- if pkg + '-dbg' in c:
108+ if pkg + '-dbg' in cache:
109 real_pkgs.add(pkg + '-dbg')
110 else:
111 # install all -dbg from the source package
112 if src_records.lookup(candidate.source_name):
113- dbgs = [p for p in src_records.binaries if p.endswith('-dbg') and p in c]
114+ dbgs = [p for p in src_records.binaries if p.endswith('-dbg') and p in cache]
115 else:
116 dbgs = []
117 if dbgs:
118 for p in dbgs:
119 real_pkgs.add(p)
120 else:
121- if pkg + '-dbgsym' in c:
122+ if pkg + '-dbgsym' in cache:
123 real_pkgs.add(pkg + '-dbgsym')
124- if c[pkg + '-dbgsym'].candidate.version != candidate.version:
125+ if cache[pkg + '-dbgsym'].candidate.version != candidate.version:
126 obsolete += 'outdated debug symbol package for %s: package version %s dbgsym version %s\n' % (
127- pkg, candidate.version, c[pkg + '-dbgsym'].candidate.version)
128+ pkg, candidate.version, cache[pkg + '-dbgsym'].candidate.version)
129
130 for p in real_pkgs:
131- c[p].mark_install(False, False)
132+ cache[p].mark_install(False, False)
133
134 last_written = time.time()
135 # fetch packages
136 fetcher = apt.apt_pkg.Acquire(fetchProgress)
137 try:
138- c.fetch_archives(fetcher=fetcher)
139+ cache.fetch_archives(fetcher=fetcher)
140 except apt.cache.FetchFailedException as e:
141 apport.error('Package download error, try again later: %s', str(e))
142 sys.exit(99) # transient error
143@@ -879,6 +886,7 @@
144 out = zgrep.communicate()[0].decode('UTF-8')
145 # we do not check the return code, since zgrep -m1 often errors out
146 # with 'stdout: broken pipe'
147+ # no version here
148 if out:
149 package = out.split()[1].split(',')[0].split('/')[-1]
150 if package:
151
152=== modified file 'test/test_backend_apt_dpkg.py'
153--- test/test_backend_apt_dpkg.py 2014-01-27 14:20:26 +0000
154+++ test/test_backend_apt_dpkg.py 2014-03-26 19:33:35 +0000
155@@ -480,7 +480,7 @@
156 self._setup_foonux_config()
157 obsolete = impl.install_packages(self.rootdir, self.configdir,
158 'Foonux 1.2',
159- [('coreutils', '8.13-3ubuntu3'),
160+ [('coreutils', '8.13-3ubuntu3.2'),
161 ('libc6', '2.15-0ubuntu10'),
162 ('tzdata', '2012b-1'),
163 ], False, self.cachedir)
164@@ -518,7 +518,7 @@
165 os.unlink(os.path.join(self.rootdir, 'usr/bin/stat'))
166 obsolete = impl.install_packages(self.rootdir, self.configdir,
167 'Foonux 1.2',
168- [('coreutils', '8.13-3ubuntu3'),
169+ [('coreutils', '8.13-3ubuntu3.2'),
170 ], False, self.cachedir)
171 self.assertEqual(obsolete, '')
172 self.assertTrue(os.path.exists(
173@@ -553,7 +553,7 @@
174 # still installs packages after above operations
175 os.unlink(os.path.join(self.rootdir, 'usr/bin/stat'))
176 impl.install_packages(self.rootdir, self.configdir, 'Foonux 1.2',
177- [('coreutils', '8.13-3ubuntu3'),
178+ [('coreutils', '8.13-3ubuntu3.2'),
179 ('dpkg', None),
180 ], False, self.cachedir)
181 self.assertTrue(os.path.exists(os.path.join(self.rootdir,
182@@ -764,7 +764,7 @@
183 self.assertTrue(os.path.isdir(os.path.join(res, 'debian')))
184 # this needs to be updated when the release in _setup_foonux_config
185 # changes
186- self.assertTrue(res.endswith('/base-files-6.5ubuntu6'),
187+ self.assertTrue(res.endswith('/base-files-6.5ubuntu6.7'),
188 'unexpected version: ' + res.split('/')[-1])
189
190 def _setup_foonux_config(self):
191@@ -781,6 +781,9 @@
192 f.write('deb http://archive.ubuntu.com/ubuntu/ precise main\n')
193 f.write('deb-src http://archive.ubuntu.com/ubuntu/ precise main\n')
194 f.write('deb http://ddebs.ubuntu.com/ precise main\n')
195+ f.write('deb http://archive.ubuntu.com/ubuntu/ precise-updates main\n')
196+ f.write('deb-src http://archive.ubuntu.com/ubuntu/ precise-updates main\n')
197+ f.write('deb http://ddebs.ubuntu.com/ precise-updates main\n')
198 os.mkdir(os.path.join(self.configdir, 'Foonux 1.2', 'armhf'))
199 with open(os.path.join(self.configdir, 'Foonux 1.2', 'armhf', 'sources.list'), 'w') as f:
200 f.write('deb http://ports.ubuntu.com/ precise main\n')

Subscribers

People subscribed via source and target branches