Merge lp:~tealeg/landscape-client/tidy-apt-facade-perform-changes into lp:~landscape/landscape-client/trunk

Proposed by Geoff Teale
Status: Merged
Approved by: Chris Glass
Approved revision: 560
Merged at revision: 556
Proposed branch: lp:~tealeg/landscape-client/tidy-apt-facade-perform-changes
Merge into: lp:~landscape/landscape-client/trunk
Diff against target: 322 lines (+181/-102)
2 files modified
landscape/package/facade.py (+167/-102)
landscape/package/tests/test_facade.py (+14/-0)
To merge this branch: bzr merge lp:~tealeg/landscape-client/tidy-apt-facade-perform-changes
Reviewer Review Type Date Requested Status
Chris Glass (community) Approve
Alberto Donato (community) Approve
Review via email: mp+102654@code.launchpad.net

Description of the change

Fixes bug #985493

Nothing fancy, this just splits out a large method into several smaller calls and maintains the same interface.

To post a comment you must log in.
Revision history for this message
Alberto Donato (ack) wrote :

Looks good to me, +1!

review: Approve
Revision history for this message
Chris Glass (tribaal) wrote :

Great! +1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'landscape/package/facade.py'
2--- landscape/package/facade.py 2012-04-11 08:21:17 +0000
3+++ landscape/package/facade.py 2012-04-19 09:24:18 +0000
4@@ -559,126 +559,191 @@
5 all_info.append(info + or_divider.join(relation_infos))
6 return "\n".join(all_info)
7
8- def perform_changes(self):
9- """Perform the pending package operations."""
10- # Try to enforce non-interactivity
11+ def _set_frontend_noninteractive(self):
12+ """
13+ Set the environment to avoid attempts by apt to interact with a user.
14+ """
15 os.environ["DEBIAN_FRONTEND"] = "noninteractive"
16 os.environ["APT_LISTCHANGES_FRONTEND"] = "none"
17 os.environ["APT_LISTBUGS_FRONTEND"] = "none"
18+
19+ def _default_path_when_missing(self):
20+ """
21+ If no PATH is set in the environment, use the Ubuntu default PATH.
22+
23+ When the client is launched from the landscape-client-settings-ui the
24+ PATH variable is incorrectly set, this method rectifies that.
25+ """
26 # dpkg will fail if no path is set.
27 if "PATH" not in os.environ:
28 os.environ["PATH"] = UBUNTU_PATH
29+
30+ def _setup_dpkg_for_changes(self):
31+ """
32+ Setup environment and apt options for successful package operations.
33+ """
34+ self._set_frontend_noninteractive()
35+ self._default_path_when_missing()
36 apt_pkg.config.clear("DPkg::options")
37 apt_pkg.config.set("DPkg::options::", "--force-confold")
38
39+ def _perform_hold_changes(self):
40+ """
41+ Perform pending hold operations on packages.
42+ """
43+ hold_changes = (len(self._version_hold_creations) > 0 or
44+ len(self._version_hold_removals) > 0)
45+ if not hold_changes:
46+ return None
47+ not_installed = [version for version in
48+ self._version_hold_creations
49+ if not self.is_package_installed(version)]
50+ if not_installed:
51+ raise TransactionError(
52+ "Cannot perform the changes, since the following " +
53+ "packages are not installed: %s" % ", ".join(
54+ [version.package.name
55+ for version in sorted(not_installed)]))
56+
57+ for version in self._version_hold_creations:
58+ self.set_package_hold(version)
59+
60+ for version in self._version_hold_removals:
61+ self.remove_package_hold(version)
62+
63+ return "Package holds successfully changed."
64+
65+ def _commit_package_changes(self):
66+ """
67+ Commit cached APT operations and give feedback on the results as a
68+ string.
69+ """
70+ fetch_output = StringIO()
71+ # Redirect stdout and stderr to a file. We need to work with the
72+ # file descriptors, rather than sys.stdout/stderr, since dpkg is
73+ # run in a subprocess.
74+ fd, install_output_path = tempfile.mkstemp()
75+ old_stdout = os.dup(1)
76+ old_stderr = os.dup(2)
77+ os.dup2(fd, 1)
78+ os.dup2(fd, 2)
79+ install_progress = LandscapeInstallProgress()
80+ try:
81+ self._cache.commit(
82+ fetch_progress=LandscapeAcquireProgress(fetch_output),
83+ install_progress=install_progress)
84+ if not install_progress.dpkg_exited:
85+ raise SystemError("dpkg didn't exit cleanly.")
86+ except SystemError, error:
87+ result_text = (
88+ fetch_output.getvalue() + read_file(install_output_path))
89+ raise TransactionError(error.args[0] +
90+ "\n\nPackage operation log:\n" +
91+ result_text)
92+ else:
93+ result_text = (
94+ fetch_output.getvalue() + read_file(install_output_path))
95+ finally:
96+ # Restore stdout and stderr.
97+ os.dup2(old_stdout, 1)
98+ os.dup2(old_stderr, 2)
99+ os.remove(install_output_path)
100+ return result_text
101+
102+ def _preprocess_installs(self, fixer):
103+ for version in self._version_installs:
104+ # Set the candidate version, so that the version we want to
105+ # install actually is the one getting installed.
106+ version.package.candidate = version
107+ # Set auto_fix=False to avoid removing the package we asked to
108+ # install when we need to resolve dependencies.
109+ version.package.mark_install(auto_fix=False)
110+ fixer.clear(version.package._pkg)
111+ fixer.protect(version.package._pkg)
112+
113+ def _preprocess_removes(self, fixer):
114 held_package_names = set()
115+
116 package_installs = set(
117 version.package for version in self._version_installs)
118+
119 package_upgrades = set(
120 version.package for version in self._version_removals
121 if version.package in package_installs)
122+
123+ for version in self._version_removals:
124+ if self._is_package_held(version.package):
125+ held_package_names.add(version.package.name)
126+ if version.package in package_upgrades:
127+ # The server requests the old version to be removed for
128+ # upgrades, since Smart worked that way. For Apt we have
129+ # to take care not to mark upgraded packages for removal.
130+ continue
131+ version.package.mark_delete(auto_fix=False)
132+ # Configure the resolver in the same way
133+ # mark_delete(auto_fix=True) would have done.
134+ fixer.clear(version.package._pkg)
135+ fixer.protect(version.package._pkg)
136+ fixer.remove(version.package._pkg)
137+ fixer.install_protect()
138+
139+ if held_package_names:
140+ raise TransactionError(
141+ "Can't perform the changes, since the following packages" +
142+ " are held: %s" % ", ".join(sorted(held_package_names)))
143+
144+ def _preprocess_global_upgrade(self):
145+ if self._global_upgrade:
146+ self._cache.upgrade(dist_upgrade=True)
147+
148+ def _resolve_broken_packages(self, fixer, already_broken_packages):
149+ """
150+ Attempt to automatically resolve problems with broken packages.
151+ """
152+ now_broken_packages = self._get_broken_packages()
153+ if now_broken_packages != already_broken_packages:
154+ try:
155+ fixer.resolve(True)
156+ except SystemError, error:
157+ raise TransactionError(error.args[0] + "\n" +
158+ self._get_unmet_dependency_info())
159+
160+ def _preprocess_package_changes(self):
161 version_changes = self._version_installs[:]
162 version_changes.extend(self._version_removals)
163- hold_changes = (len(self._version_hold_creations) > 0 or
164- len(self._version_hold_removals) > 0)
165- if (not hold_changes and not version_changes and
166- not self._global_upgrade):
167+ if (not version_changes and not self._global_upgrade):
168+ return []
169+ already_broken_packages = self._get_broken_packages()
170+ fixer = apt_pkg.ProblemResolver(self._cache._depcache)
171+ self._preprocess_installs(fixer)
172+ self._preprocess_global_upgrade()
173+ self._preprocess_removes(fixer)
174+ self._resolve_broken_packages(fixer, already_broken_packages)
175+ return version_changes
176+
177+ def _perform_package_changes(self):
178+ """
179+ Perform pending install/remove/upgrade operations.
180+ """
181+ version_changes = self._preprocess_package_changes()
182+ if not self._check_changes(version_changes):
183 return None
184- if hold_changes:
185- not_installed = [version for version in
186- self._version_hold_creations
187- if not self.is_package_installed(version)]
188- if not_installed:
189- raise TransactionError(
190- "Cannot perform the changes, since the following " +
191- "packages are not installed: %s" % ", ".join(
192- [version.package.name
193- for version in sorted(not_installed)]))
194-
195- for version in self._version_hold_creations:
196- self.set_package_hold(version)
197-
198- for version in self._version_hold_removals:
199- self.remove_package_hold(version)
200-
201- result_text = "Package holds successfully changed."
202- if version_changes or self._global_upgrade:
203- fixer = apt_pkg.ProblemResolver(self._cache._depcache)
204- already_broken_packages = self._get_broken_packages()
205- for version in self._version_installs:
206- # Set the candidate version, so that the version we want to
207- # install actually is the one getting installed.
208- version.package.candidate = version
209- version.package.mark_install(auto_fix=False)
210- # If we need to resolve dependencies, try avoiding having
211- # the package we asked to be installed from being removed.
212- # (This is what would have been done if auto_fix would have
213- # been True.
214- fixer.clear(version.package._pkg)
215- fixer.protect(version.package._pkg)
216- if self._global_upgrade:
217- self._cache.upgrade(dist_upgrade=True)
218- for version in self._version_removals:
219- if self._is_package_held(version.package):
220- held_package_names.add(version.package.name)
221- if version.package in package_upgrades:
222- # The server requests the old version to be removed for
223- # upgrades, since Smart worked that way. For Apt we have
224- # to take care not to mark upgraded packages for # removal.
225- continue
226- version.package.mark_delete(auto_fix=False)
227- # Configure the resolver in the same way
228- # mark_delete(auto_fix=True) would have done.
229- fixer.clear(version.package._pkg)
230- fixer.protect(version.package._pkg)
231- fixer.remove(version.package._pkg)
232- fixer.install_protect()
233-
234- if held_package_names:
235- raise TransactionError(
236- "Can't perform the changes, since the following packages" +
237- " are held: %s" % ", ".join(sorted(held_package_names)))
238-
239- now_broken_packages = self._get_broken_packages()
240- if now_broken_packages != already_broken_packages:
241- try:
242- fixer.resolve(True)
243- except SystemError, error:
244- raise TransactionError(error.args[0] + "\n" +
245- self._get_unmet_dependency_info())
246- if not self._check_changes(version_changes):
247- return None
248- fetch_output = StringIO()
249- # Redirect stdout and stderr to a file. We need to work with the
250- # file descriptors, rather than sys.stdout/stderr, since dpkg is
251- # run in a subprocess.
252- fd, install_output_path = tempfile.mkstemp()
253- old_stdout = os.dup(1)
254- old_stderr = os.dup(2)
255- os.dup2(fd, 1)
256- os.dup2(fd, 2)
257- install_progress = LandscapeInstallProgress()
258- try:
259- self._cache.commit(
260- fetch_progress=LandscapeAcquireProgress(fetch_output),
261- install_progress=install_progress)
262- if not install_progress.dpkg_exited:
263- raise SystemError("dpkg didn't exit cleanly.")
264- except SystemError, error:
265- result_text = (
266- fetch_output.getvalue() + read_file(install_output_path))
267- raise TransactionError(error.args[0] +
268- "\n\nPackage operation log:\n" +
269- result_text)
270- else:
271- result_text = (
272- fetch_output.getvalue() + read_file(install_output_path))
273- finally:
274- # Restore stdout and stderr.
275- os.dup2(old_stdout, 1)
276- os.dup2(old_stderr, 2)
277- os.remove(install_output_path)
278- return result_text
279+ return self._commit_package_changes()
280+
281+ def perform_changes(self):
282+ """
283+ Perform the pending package operations.
284+ """
285+ self._setup_dpkg_for_changes()
286+ hold_result_text = self._perform_hold_changes()
287+ package_result_text = self._perform_package_changes()
288+ results = []
289+ if package_result_text is not None:
290+ results.append(package_result_text)
291+ if hold_result_text is not None:
292+ results.append(hold_result_text)
293+ if len(results) > 0:
294+ return " ".join(results)
295
296 def reset_marks(self):
297 """Clear the pending package operations."""
298
299=== modified file 'landscape/package/tests/test_facade.py'
300--- landscape/package/tests/test_facade.py 2012-04-11 08:41:49 +0000
301+++ landscape/package/tests/test_facade.py 2012-04-19 09:24:18 +0000
302@@ -2357,6 +2357,20 @@
303 self.assertEqual(
304 ["baz", "foo"], sorted(self.facade.get_package_holds()))
305
306+ def test_mark_hold_and_perform_hold_changes(self):
307+ """
308+ Test that L{perform_hold_changes} holds packages that have previously
309+ been marked for hold.
310+ """
311+ self._add_system_package("foo")
312+ self.facade.reload_channels()
313+ [foo] = self.facade.get_packages_by_name("foo")
314+ self.facade.mark_hold(foo)
315+ self.assertEqual("Package holds successfully changed.",
316+ self.facade._perform_hold_changes())
317+ self.facade.reload_channels()
318+ self.assertEqual(["foo"], self.facade.get_package_holds())
319+
320 def test_mark_hold(self):
321 """
322 C{mark_hold} marks a package to be held.

Subscribers

People subscribed via source and target branches

to all changes: