Merge lp:~ahasenack/landscape-client/landscape-client-12.05-0ubuntu1-quantal into lp:ubuntu/quantal/landscape-client

Proposed by Andreas Hasenack
Status: Merged
Merged at revision: 46
Proposed branch: lp:~ahasenack/landscape-client/landscape-client-12.05-0ubuntu1-quantal
Merge into: lp:ubuntu/quantal/landscape-client
Diff against target: 5384 lines (+762/-3332)
39 files modified
debian/changelog (+41/-0)
debian/control (+6/-4)
debian/landscape-client.templates (+1/-1)
debian/landscape-common.templates (+1/-1)
debian/po/templates.pot (+28/-6)
debian/rules (+9/-6)
debian/source.lintian-overrides (+16/-0)
debian/source/format (+1/-0)
debian/watch (+2/-0)
landscape/__init__.py (+6/-2)
landscape/constants.py (+3/-2)
landscape/deployment.py (+1/-1)
landscape/manager/aptsources.py (+2/-2)
landscape/manager/tests/test_aptsources.py (+1/-1)
landscape/monitor/packagemonitor.py (+1/-1)
landscape/package/changer.py (+37/-119)
landscape/package/facade.py (+159/-494)
landscape/package/interface.py (+0/-84)
landscape/package/releaseupgrader.py (+2/-2)
landscape/package/reporter.py (+13/-110)
landscape/package/skeleton.py (+0/-48)
landscape/package/store.py (+0/-37)
landscape/package/taskhandler.py (+4/-8)
landscape/package/tests/helpers.py (+0/-39)
landscape/package/tests/test_changer.py (+202/-521)
landscape/package/tests/test_facade.py (+88/-735)
landscape/package/tests/test_interface.py (+0/-34)
landscape/package/tests/test_releaseupgrader.py (+2/-2)
landscape/package/tests/test_reporter.py (+94/-695)
landscape/package/tests/test_skeleton.py (+28/-87)
landscape/package/tests/test_store.py (+3/-103)
landscape/package/tests/test_taskhandler.py (+4/-48)
landscape/ui/view/tests/test_configuration.py (+3/-0)
landscape/ui/view/ui/landscape-client-settings.glade (+1/-1)
landscape/watchdog.py (+1/-1)
po/fr.po (+1/-1)
po/landscape-client.pot (+1/-1)
smart-update/Makefile (+0/-6)
smart-update/smart-update.c (+0/-129)
To merge this branch: bzr merge lp:~ahasenack/landscape-client/landscape-client-12.05-0ubuntu1-quantal
Reviewer Review Type Date Requested Status
Ubuntu branches Pending
Review via email: mp+108569@code.launchpad.net

Description of the change

This updates landscape-client in quantal to upstream version 12.05.

Upstream now removes debian/* from the tarball, so this branch also addresses that. For details, please see the debian/changelog new entries.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'debian/changelog'
--- debian/changelog 2012-03-28 10:59:58 +0000
+++ debian/changelog 2012-06-04 14:08:28 +0000
@@ -1,3 +1,44 @@
1landscape-client (12.05-0ubuntu1) quantal; urgency=low
2
3 * New upstream release 12.05 (r561 in trunk) (LP: #1004678).
4 * Make all subpackages that depend on each other require the exact same
5 version, instead of >= $version.
6 * Added python-gi to client depends starting natty.
7 * Make change-packages also handle package holds (LP: #972489).
8 * Fix typo in glade file (LP: #983096).
9 * Tidy up apt facade (LP: #985493).
10 * Remove SmartFacade and its tests, no longer used (LP: #996269).
11 * Remove check for apt version, since we won't release this for
12 Hardy where that check matters (LP: #996837).
13 * Remove methods that were smart specific. We no longer use smart
14 (LP: #996841).
15 * Remove smart-update helper binary. We no longer use smart
16 (LP: #997408).
17 * Remove test-mixins that were useful only during the apt-to-smart
18 code migration. Now with smart gone, they are no longer necessary
19 (LP: #997760).
20 * Build the packages from precise onward, not just precise.
21 * Assorted packaging fixes:
22 - Switched to format 3.0 (quilt).
23 - Added source lintian overrides, with comments.
24 - Updated debian/rules:
25 - Added build-arch and build-indep dummy target.
26 - Build the GUI packages from precise onwards, and not just on precise.
27 - Re-enable dh_lintian.
28 - Strip the binaries (dh_strip), and also call dh_shlibdeps for the
29 automatic shlibs dependency.
30 - Added python-gi from natty onwards.
31 - Used __Choices instead of _Choices in landscape-common.templates, it's
32 better for translators.
33 - Updated standard version to 3.8.2, the version from Lucid (oldest one
34 we support)
35 - Added shlibs depends.
36 - Dropped deprecated ${Source-Version} and replaced it with
37 ${binary:Version}
38 - Require exact version of the sibling package instead of >=.
39
40 -- Andreas Hasenack <andreas@canonical.com> Fri, 01 Jun 2012 17:48:43 -0300
41
1landscape-client (12.04.3-0ubuntu1) precise; urgency=low42landscape-client (12.04.3-0ubuntu1) precise; urgency=low
243
3 * Warn on unicode entry into settings UI (LP: #956612).44 * Warn on unicode entry into settings UI (LP: #956612).
445
=== modified file 'debian/control'
--- debian/control 2012-03-22 10:10:39 +0000
+++ debian/control 2012-06-04 14:08:28 +0000
@@ -4,12 +4,13 @@
4Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>4Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
5XSBC-Original-Maintainer: Landscape Team <landscape-team@canonical.com>5XSBC-Original-Maintainer: Landscape Team <landscape-team@canonical.com>
6Build-Depends: debhelper (>= 5), po-debconf, python-dev, lsb-release, gawk, python-twisted-core, python-distutils-extra6Build-Depends: debhelper (>= 5), po-debconf, python-dev, lsb-release, gawk, python-twisted-core, python-distutils-extra
7Standards-Version: 3.8.07Standards-Version: 3.8.2
8XS-Python-Version: >= 2.4, << 2.88XS-Python-Version: >= 2.4, << 2.8
99
10Package: landscape-common10Package: landscape-common
11Architecture: any11Architecture: any
12Depends: ${python:Depends}, ${misc:Depends}, ${extra:Depends},12Depends: ${python:Depends}, ${misc:Depends}, ${extra:Depends},
13 ${shlibs:Depends},
13 python-gnupginterface,14 python-gnupginterface,
14 python-twisted-core,15 python-twisted-core,
15 python-apt,16 python-apt,
@@ -33,9 +34,10 @@
33Package: landscape-client34Package: landscape-client
34Architecture: any35Architecture: any
35Depends: ${python:Depends}, ${misc:Depends}, ${extra:Depends},36Depends: ${python:Depends}, ${misc:Depends}, ${extra:Depends},
37 ${shlibs:Depends},
36 python-twisted-web,38 python-twisted-web,
37 python-twisted-names,39 python-twisted-names,
38 landscape-common (>= ${Source-Version})40 landscape-common (= ${binary:Version})
39Suggests: ${extra:Suggests}41Suggests: ${extra:Suggests}
40Description: The Landscape administration system client42Description: The Landscape administration system client
41 Landscape is a web-based tool for managing Ubuntu systems. This43 Landscape is a web-based tool for managing Ubuntu systems. This
@@ -48,8 +50,8 @@
48Package: landscape-client-ui50Package: landscape-client-ui
49Architecture: any51Architecture: any
50Depends: ${python:Depends}, ${misc:Depends},52Depends: ${python:Depends}, ${misc:Depends},
51 landscape-client (>= ${Source-Version}),53 landscape-client (= ${binary:Version}),
52 landscape-client-ui-install (>= ${Source-Version}),54 landscape-client-ui-install (= ${binary:Version}),
53 python-gi,55 python-gi,
54 python-dbus,56 python-dbus,
55 policykit-1,57 policykit-1,
5658
=== modified file 'debian/landscape-client.templates'
--- debian/landscape-client.templates 2012-03-19 09:33:34 +0000
+++ debian/landscape-client.templates 2012-06-04 14:08:28 +0000
@@ -64,7 +64,7 @@
64Template: landscape-client/tags64Template: landscape-client/tags
65Type: string65Type: string
66Default:66Default:
67_Description: Initial tags for first registration67_Description: Initial tags for first registration:
68 Comma separated list of tags which will be assigned to this computer on its68 Comma separated list of tags which will be assigned to this computer on its
69 first registration. Once the machine is registered, these tags can only be69 first registration. Once the machine is registered, these tags can only be
70 changed using the Landscape server.70 changed using the Landscape server.
7171
=== modified file 'debian/landscape-common.templates'
--- debian/landscape-common.templates 2012-03-19 09:33:34 +0000
+++ debian/landscape-common.templates 2012-06-04 14:08:28 +0000
@@ -6,7 +6,7 @@
6# try to keep below ~71 characters.6# try to keep below ~71 characters.
7# DO NOT USE commas (,) in Choices translations otherwise7# DO NOT USE commas (,) in Choices translations otherwise
8# this will break the choices shown to users8# this will break the choices shown to users
9_Choices: Do not display sysinfo on login, Cache sysinfo in /etc/motd, Run sysinfo on every login9__Choices: Do not display sysinfo on login, Cache sysinfo in /etc/motd, Run sysinfo on every login
10Default: Cache sysinfo in /etc/motd10Default: Cache sysinfo in /etc/motd
11_Description: landscape-sysinfo configuration:11_Description: landscape-sysinfo configuration:
12 Landscape includes a tool and a set of modules that can display12 Landscape includes a tool and a set of modules that can display
1313
=== modified file 'debian/po/templates.pot'
--- debian/po/templates.pot 2012-03-19 09:33:34 +0000
+++ debian/po/templates.pot 2012-06-04 14:08:28 +0000
@@ -6,9 +6,9 @@
6#, fuzzy6#, fuzzy
7msgid ""7msgid ""
8msgstr ""8msgstr ""
9"Project-Id-Version: PACKAGE VERSION\n"9"Project-Id-Version: landscape-client\n"
10"Report-Msgid-Bugs-To: landscape-client@packages.debian.org\n"10"Report-Msgid-Bugs-To: landscape-client@packages.debian.org\n"
11"POT-Creation-Date: 2012-03-07 17:07+0100\n"11"POT-Creation-Date: 2012-06-01 18:35-0300\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"14"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -152,7 +152,7 @@
152#. Type: string152#. Type: string
153#. Description153#. Description
154#: ../landscape-client.templates:11001154#: ../landscape-client.templates:11001
155msgid "Initial tags for first registration"155msgid "Initial tags for first registration:"
156msgstr ""156msgstr ""
157157
158#. Type: string158#. Type: string
@@ -187,9 +187,31 @@
187#. DO NOT USE commas (,) in Choices translations otherwise187#. DO NOT USE commas (,) in Choices translations otherwise
188#. this will break the choices shown to users188#. this will break the choices shown to users
189#: ../landscape-common.templates:1001189#: ../landscape-common.templates:1001
190msgid ""190msgid "Do not display sysinfo on login"
191"Do not display sysinfo on login, Cache sysinfo in /etc/motd, Run sysinfo on "191msgstr ""
192"every login"192
193#. Type: select
194#. Choices
195#. Translators beware! the following three strings form a single
196#. Choices menu. - Every one of these strings has to fit in a standard
197#. 80 characters console, as the fancy screen setup takes up some space
198#. try to keep below ~71 characters.
199#. DO NOT USE commas (,) in Choices translations otherwise
200#. this will break the choices shown to users
201#: ../landscape-common.templates:1001
202msgid "Cache sysinfo in /etc/motd"
203msgstr ""
204
205#. Type: select
206#. Choices
207#. Translators beware! the following three strings form a single
208#. Choices menu. - Every one of these strings has to fit in a standard
209#. 80 characters console, as the fancy screen setup takes up some space
210#. try to keep below ~71 characters.
211#. DO NOT USE commas (,) in Choices translations otherwise
212#. this will break the choices shown to users
213#: ../landscape-common.templates:1001
214msgid "Run sysinfo on every login"
193msgstr ""215msgstr ""
194216
195#. Type: select217#. Type: select
196218
=== modified file 'debian/rules'
--- debian/rules 2012-03-28 10:59:58 +0000
+++ debian/rules 2012-06-04 14:08:28 +0000
@@ -9,7 +9,7 @@
9endif9endif
1010
11dh_extra_flags = -plandscape-common -plandscape-client11dh_extra_flags = -plandscape-common -plandscape-client
12ifneq (,$(filter $(dist_release),precise))12ifeq (,$(filter $(dist_release),hardy lucid natty oneiric))
13 # We want landscape-client-ui only from precise onward13 # We want landscape-client-ui only from precise onward
14 dh_extra_flags += -plandscape-client-ui -plandscape-client-ui-install14 dh_extra_flags += -plandscape-client-ui -plandscape-client-ui-install
15endif15endif
@@ -30,6 +30,10 @@
30landscape_common_substvars = debian/landscape-common.substvars30landscape_common_substvars = debian/landscape-common.substvars
31landscape_client_substvars = debian/landscape-client.substvars31landscape_client_substvars = debian/landscape-client.substvars
3232
33build-arch: build
34
35build-indep: build
36
33build: build-stamp37build: build-stamp
34build-stamp:38build-stamp:
35 dh_testdir39 dh_testdir
@@ -69,10 +73,7 @@
69# do nothing73# do nothing
70#74#
71binary-arch: build install75binary-arch: build install
72 # That's not present in Ubuntu releases we still support, so76 dh_lintian
73 # we're just installing the lintian overrides file by hand
74 # for now.
75 #dh_lintian
76 dh_testdir77 dh_testdir
77 dh_testroot78 dh_testroot
78 dh_installdocs79 dh_installdocs
@@ -82,8 +83,10 @@
82 dh_installinit -- start 45 2 3 4 5 . stop 15 0 1 6 .83 dh_installinit -- start 45 2 3 4 5 . stop 15 0 1 6 .
83 dh_installlogrotate84 dh_installlogrotate
84 dh_installdebconf85 dh_installdebconf
86 dh_strip
85 dh_compress87 dh_compress
86 dh_fixperms88 dh_fixperms
89 dh_shlibdeps
8790
88ifneq (,$(findstring $(dist_release),hardy))91ifneq (,$(findstring $(dist_release),hardy))
89 # We depend on bug-fixed versions of python-dbus and pycurl on hardy92 # We depend on bug-fixed versions of python-dbus and pycurl on hardy
@@ -100,7 +103,7 @@
100ifeq (,$(filter $(dist_release),hardy lucid))103ifeq (,$(filter $(dist_release),hardy lucid))
101 # Starting natty, no more hal or dbus104 # Starting natty, no more hal or dbus
102 echo "extra:Depends=libpam-modules (>= 1.0.1-9ubuntu3)" >> $(landscape_common_substvars)105 echo "extra:Depends=libpam-modules (>= 1.0.1-9ubuntu3)" >> $(landscape_common_substvars)
103 echo "extra:Depends=python-pycurl, gir1.2-gudev-1.0 (>= 165-0ubuntu2)" >> $(landscape_client_substvars)106 echo "extra:Depends=python-pycurl, gir1.2-gudev-1.0 (>= 165-0ubuntu2), python-gi" >> $(landscape_client_substvars)
104 echo "extra:Suggests=python-dbus, hal" >> $(landscape_client_substvars)107 echo "extra:Suggests=python-dbus, hal" >> $(landscape_client_substvars)
105endif108endif
106109
107110
=== added directory 'debian/source'
=== added file 'debian/source.lintian-overrides'
--- debian/source.lintian-overrides 1970-01-01 00:00:00 +0000
+++ debian/source.lintian-overrides 2012-06-04 14:08:28 +0000
@@ -0,0 +1,16 @@
1# we use dh_python or dh_python2 depending on the ubuntu release
2# the package is being built on, this is detected dynamically
3# in the rules file
4landscape-client source: dh_python-is-obsolete
5
6# the package has to build on lucid, where the standards version
7# is 3.8.2
8landscape-client source: ancient-standards-version
9
10# it's a bug that should be fixed in quantal
11landscape-client source: unknown-field-in-dsc original-maintainer
12
13# this is only used in a very specific client upgrade from
14# the dbus to the amp version
15landscape-client: start-stop-daemon-in-maintainer-script postinst:130
16
017
=== added file 'debian/source/format'
--- debian/source/format 1970-01-01 00:00:00 +0000
+++ debian/source/format 2012-06-04 14:08:28 +0000
@@ -0,0 +1,1 @@
13.0 (quilt)
02
=== added file 'debian/watch'
--- debian/watch 1970-01-01 00:00:00 +0000
+++ debian/watch 2012-06-04 14:08:28 +0000
@@ -0,0 +1,2 @@
1version=3
2https://launchpad.net/landscape-client/+download https://launchpad.net/landscape-client/[^/]+/[^/]+/\+download/landscape-client-(.*)\.tar\.(?:bz2|gz)
03
=== modified file 'landscape/__init__.py'
--- landscape/__init__.py 2012-03-28 10:59:58 +0000
+++ landscape/__init__.py 2012-06-04 14:08:28 +0000
@@ -1,7 +1,7 @@
1import sys1import sys
22
3DEBIAN_REVISION = ""3DEBIAN_REVISION = ""
4UPSTREAM_VERSION = "12.04.2"4UPSTREAM_VERSION = "12.05"
5VERSION = "%s%s" % (UPSTREAM_VERSION, DEBIAN_REVISION)5VERSION = "%s%s" % (UPSTREAM_VERSION, DEBIAN_REVISION)
66
7# The "server-api" field of outgoing messages will be set to this value, and7# The "server-api" field of outgoing messages will be set to this value, and
@@ -32,7 +32,11 @@
32# * Add "policy" field to "change-packages"32# * Add "policy" field to "change-packages"
33# * Add new "change-package-locks" client accepted message type.33# * Add new "change-package-locks" client accepted message type.
34#34#
35CLIENT_API = "3.3"35# 3.4:
36# * Add "hold" field to "change-packages"
37# * Add "remove-hold" field to "change-packages"
38
39CLIENT_API = "3.4"
3640
37from twisted.python import util41from twisted.python import util
3842
3943
=== modified file 'landscape/constants.py'
--- landscape/constants.py 2012-03-21 16:15:49 +0000
+++ landscape/constants.py 2012-06-04 14:08:28 +0000
@@ -12,6 +12,7 @@
12SUCCESS_RESULT = 112SUCCESS_RESULT = 1
13ERROR_RESULT = 10013ERROR_RESULT = 100
14DEPENDENCY_ERROR_RESULT = 10114DEPENDENCY_ERROR_RESULT = 101
15CLIENT_VERSION_ERROR_RESULT = 102
15POLICY_STRICT = 016POLICY_STRICT = 0
16POLICY_ALLOW_INSTALLS = 117POLICY_ALLOW_INSTALLS = 1
17POLICY_ALLOW_ALL_CHANGES = 218POLICY_ALLOW_ALL_CHANGES = 2
@@ -25,8 +26,8 @@
25# package reporter.26# package reporter.
26# 2. We lost some package data, for example by a deb archive becoming27# 2. We lost some package data, for example by a deb archive becoming
27# inaccessible for a while. The earliest we can reasonably assume that to be28# inaccessible for a while. The earliest we can reasonably assume that to be
28# resolved is in 60 minutes, when the smart cronjob runs again.29# resolved is in 60 minutes, when the package reporter runs again.
2930
30# So we'll give the problem one chance to resolve itself, by only waiting for31# So we'll give the problem one chance to resolve itself, by only waiting for
31# one run of smart update.32# one run of apt-update.
32UNKNOWN_PACKAGE_DATA_TIMEOUT = 70 * 6033UNKNOWN_PACKAGE_DATA_TIMEOUT = 70 * 60
3334
=== modified file 'landscape/deployment.py'
--- landscape/deployment.py 2012-03-19 09:33:34 +0000
+++ landscape/deployment.py 2012-06-04 14:08:28 +0000
@@ -282,7 +282,7 @@
282 - C{quiet} (C{False})282 - C{quiet} (C{False})
283 - C{log_dir} (C{"/var/log/landscape"})283 - C{log_dir} (C{"/var/log/landscape"})
284 - C{log_level} (C{"info"})284 - C{log_level} (C{"info"})
285 - C{url} (C{"http://landcape.canonical.com/message-system"})285 - C{url} (C{"http://landscape.canonical.com/message-system"})
286 - C{ping_url} (C{"http://landscape.canonical.com/ping"})286 - C{ping_url} (C{"http://landscape.canonical.com/ping"})
287 - C{ssl_public_key}287 - C{ssl_public_key}
288 - C{server_autodiscover} (C{"false"})288 - C{server_autodiscover} (C{"false"})
289289
=== modified file 'landscape/manager/aptsources.py'
--- landscape/manager/aptsources.py 2012-03-19 09:33:34 +0000
+++ landscape/manager/aptsources.py 2012-06-04 14:08:28 +0000
@@ -156,8 +156,8 @@
156 """Once the repositories are modified, trigger a reporter run."""156 """Once the repositories are modified, trigger a reporter run."""
157 reporter = find_reporter_command()157 reporter = find_reporter_command()
158158
159 # Force a smart-update run, because the sources.list has changed159 # Force an apt-update run, because the sources.list has changed
160 args = ["--force-smart-update"]160 args = ["--force-apt-update"]
161161
162 if self.registry.config.config is not None:162 if self.registry.config.config is not None:
163 args.append("--config=%s" % self.registry.config.config)163 args.append("--config=%s" % self.registry.config.config)
164164
=== modified file 'landscape/manager/tests/test_aptsources.py'
--- landscape/manager/tests/test_aptsources.py 2012-03-19 09:33:34 +0000
+++ landscape/manager/tests/test_aptsources.py 2012-06-04 14:08:28 +0000
@@ -388,7 +388,7 @@
388388
389 def _run_process(command, args, env={}, path=None, uid=None, gid=None):389 def _run_process(command, args, env={}, path=None, uid=None, gid=None):
390 self.assertEqual(find_reporter_command(), command)390 self.assertEqual(find_reporter_command(), command)
391 self.assertEqual(["--force-smart-update", "--config=%s" %391 self.assertEqual(["--force-apt-update", "--config=%s" %
392 self.manager.config.config], args)392 self.manager.config.config], args)
393 deferred.callback(("ok", "", 0))393 deferred.callback(("ok", "", 0))
394 return deferred394 return deferred
395395
=== modified file 'landscape/monitor/packagemonitor.py'
--- landscape/monitor/packagemonitor.py 2012-03-19 09:33:34 +0000
+++ landscape/monitor/packagemonitor.py 2012-06-04 14:08:28 +0000
@@ -58,7 +58,7 @@
5858
59 class FakeFacade(object):59 class FakeFacade(object):
60 """60 """
61 A fake facade to workaround the issue that the SmartFacade61 A fake facade to workaround the issue that the AptFacade
62 essentially allows only once instance per process.62 essentially allows only once instance per process.
63 """63 """
6464
6565
=== modified file 'landscape/package/changer.py'
--- landscape/package/changer.py 2012-03-19 09:33:34 +0000
+++ landscape/package/changer.py 2012-06-04 14:08:28 +0000
@@ -9,8 +9,8 @@
9from twisted.internet.defer import maybeDeferred, succeed9from twisted.internet.defer import maybeDeferred, succeed
1010
11from landscape.constants import (11from landscape.constants import (
12 SUCCESS_RESULT, ERROR_RESULT, DEPENDENCY_ERROR_RESULT, POLICY_STRICT,12 SUCCESS_RESULT, ERROR_RESULT, DEPENDENCY_ERROR_RESULT,
13 POLICY_ALLOW_INSTALLS, POLICY_ALLOW_ALL_CHANGES,13 POLICY_STRICT, POLICY_ALLOW_INSTALLS, POLICY_ALLOW_ALL_CHANGES,
14 UNKNOWN_PACKAGE_DATA_TIMEOUT)14 UNKNOWN_PACKAGE_DATA_TIMEOUT)
1515
16from landscape.lib.fs import create_file16from landscape.lib.fs import create_file
@@ -18,7 +18,7 @@
18from landscape.package.taskhandler import (18from landscape.package.taskhandler import (
19 PackageTaskHandler, PackageTaskHandlerConfiguration, PackageTaskError,19 PackageTaskHandler, PackageTaskHandlerConfiguration, PackageTaskError,
20 run_task_handler)20 run_task_handler)
21from landscape.manager.manager import FAILED, SUCCEEDED21from landscape.manager.manager import FAILED
2222
2323
24class UnknownPackageData(Exception):24class UnknownPackageData(Exception):
@@ -38,7 +38,7 @@
38 """Value object to hold the results of change packages operation.38 """Value object to hold the results of change packages operation.
3939
40 @ivar code: The result code of the requested changes.40 @ivar code: The result code of the requested changes.
41 @ivar text: The output from Smart.41 @ivar text: The output from Apt.
42 @ivar installs: Possible additional packages that need to be installed42 @ivar installs: Possible additional packages that need to be installed
43 in order to fulfill the request.43 in order to fulfill the request.
44 @ivar removals: Possible additional packages that need to be removed44 @ivar removals: Possible additional packages that need to be removed
@@ -80,10 +80,6 @@
80 # Nothing was done80 # Nothing was done
81 return81 return
8282
83 # In order to let the reporter run smart-update cleanly,
84 # we have to deinitialize Smart, so that the write lock
85 # gets released
86 self._facade.deinit()
87 if os.getuid() == 0:83 if os.getuid() == 0:
88 os.setgid(grp.getgrnam("landscape").gr_gid)84 os.setgid(grp.getgrnam("landscape").gr_gid)
89 os.setuid(pwd.getpwnam("landscape").pw_uid)85 os.setuid(pwd.getpwnam("landscape").pw_uid)
@@ -103,8 +99,6 @@
103 return result.addErrback(self.unknown_package_data_error, task)99 return result.addErrback(self.unknown_package_data_error, task)
104 if message["type"] == "change-package-locks":100 if message["type"] == "change-package-locks":
105 return self.handle_change_package_locks(message)101 return self.handle_change_package_locks(message)
106 if message["type"] == "change-package-holds":
107 return self.handle_change_package_holds(message)
108102
109 def unknown_package_data_error(self, failure, task):103 def unknown_package_data_error(self, failure, task):
110 """Handle L{UnknownPackageData} data errors.104 """Handle L{UnknownPackageData} data errors.
@@ -145,7 +139,7 @@
145 self._facade.clear_channels()139 self._facade.clear_channels()
146140
147 def init_channels(self, binaries=()):141 def init_channels(self, binaries=()):
148 """Initialize the Smart channels as needed.142 """Initialize the Apt channels as needed.
149143
150 @param binaries: A possibly empty list of 3-tuples of the form144 @param binaries: A possibly empty list of 3-tuples of the form
151 (hash, id, deb), holding the hash, the id and the content of145 (hash, id, deb), holding the hash, the id and the content of
@@ -168,12 +162,16 @@
168162
169 self._facade.ensure_channels_reloaded()163 self._facade.ensure_channels_reloaded()
170164
171 def mark_packages(self, upgrade=False, install=(), remove=(), reset=True):165 def mark_packages(self, upgrade=False, install=(), remove=(),
166 hold=(), remove_hold=(), reset=True):
172 """Mark packages for upgrade, installation or removal.167 """Mark packages for upgrade, installation or removal.
173168
174 @param upgrade: If C{True} mark all installed packages for upgrade.169 @param upgrade: If C{True} mark all installed packages for upgrade.
175 @param install: A list of package ids to be marked for installation.170 @param install: A list of package ids to be marked for installation.
176 @param remove: A list of package ids to be marked for removal.171 @param remove: A list of package ids to be marked for removal.
172 @param hold: A list of package ids to be marked for holding.
173 @param remove_hold: A list of package ids to be marked to have a hold
174 removed.
177 @param reset: If C{True} all existing marks will be reset.175 @param reset: If C{True} all existing marks will be reset.
178 """176 """
179 if reset:177 if reset:
@@ -182,16 +180,19 @@
182 if upgrade:180 if upgrade:
183 self._facade.mark_global_upgrade()181 self._facade.mark_global_upgrade()
184182
185 for ids, mark_func in [(install, self._facade.mark_install),183 for mark_function, mark_ids in [
186 (remove, self._facade.mark_remove)]:184 (self._facade.mark_install, install),
187 for id in ids:185 (self._facade.mark_remove, remove),
188 hash = self._store.get_id_hash(id)186 (self._facade.mark_hold, hold),
187 (self._facade.mark_remove_hold, remove_hold)]:
188 for mark_id in mark_ids:
189 hash = self._store.get_id_hash(mark_id)
189 if hash is None:190 if hash is None:
190 raise UnknownPackageData(id)191 raise UnknownPackageData(mark_id)
191 package = self._facade.get_package_by_hash(hash)192 package = self._facade.get_package_by_hash(hash)
192 if package is None:193 if package is None:
193 raise UnknownPackageData(hash)194 raise UnknownPackageData(hash)
194 mark_func(package)195 mark_function(package)
195196
196 def change_packages(self, policy):197 def change_packages(self, policy):
197 """Perform the requested changes.198 """Perform the requested changes.
@@ -202,10 +203,9 @@
202 @return: A L{ChangePackagesResult} holding the details about the203 @return: A L{ChangePackagesResult} holding the details about the
203 outcome of the requested changes.204 outcome of the requested changes.
204 """205 """
205 # Delay importing these so that we don't import Smart unless206 # Delay importing these so that we don't import Apt unless
206 # we really need to.207 # we really need to.
207 from landscape.package.facade import (208 from landscape.package.facade import DependencyError, TransactionError
208 DependencyError, TransactionError, SmartError)
209209
210 result = ChangePackagesResult()210 result = ChangePackagesResult()
211 count = 0211 count = 0
@@ -213,7 +213,7 @@
213 count += 1213 count += 1
214 try:214 try:
215 result.text = self._facade.perform_changes()215 result.text = self._facade.perform_changes()
216 except (TransactionError, SmartError), exception:216 except TransactionError, exception:
217 result.code = ERROR_RESULT217 result.code = ERROR_RESULT
218 result.text = exception.args[0]218 result.text = exception.args[0]
219 except DependencyError, exception:219 except DependencyError, exception:
@@ -264,10 +264,11 @@
264 """Handle a C{change-packages} message."""264 """Handle a C{change-packages} message."""
265265
266 self.init_channels(message.get("binaries", ()))266 self.init_channels(message.get("binaries", ()))
267 self.mark_packages(message.get("upgrade-all", False),267 self.mark_packages(upgrade=message.get("upgrade-all", False),
268 message.get("install", ()),268 install=message.get("install", ()),
269 message.get("remove", ()))269 remove=message.get("remove", ()),
270270 hold=message.get("hold", ()),
271 remove_hold=message.get("remove-hold", ()))
271 result = self.change_packages(message.get("policy", POLICY_STRICT))272 result = self.change_packages(message.get("policy", POLICY_STRICT))
272 self._clear_binaries()273 self._clear_binaries()
273274
@@ -289,99 +290,16 @@
289 def handle_change_package_locks(self, message):290 def handle_change_package_locks(self, message):
290 """Handle a C{change-package-locks} message.291 """Handle a C{change-package-locks} message.
291292
292 Create and delete package locks as requested by the given C{message}.293 Package locks aren't supported anymore.
293 """294 """
294295
295 if not self._facade.supports_package_locks:296 response = {
296 response = {297 "type": "operation-result",
297 "type": "operation-result",298 "operation-id": message.get("operation-id"),
298 "operation-id": message.get("operation-id"),299 "status": FAILED,
299 "status": FAILED,300 "result-text": "This client doesn't support package locks.",
300 "result-text": "This client doesn't support package locks.",301 "result-code": 1}
301 "result-code": 1}302 return self._broker.send_message(response, True)
302 return self._broker.send_message(response, True)
303
304 for lock in message.get("create", ()):
305 self._facade.set_package_lock(*lock)
306 for lock in message.get("delete", ()):
307 self._facade.remove_package_lock(*lock)
308 self._facade.save_config()
309
310 response = {"type": "operation-result",
311 "operation-id": message.get("operation-id"),
312 "status": SUCCEEDED,
313 "result-text": "Package locks successfully changed.",
314 "result-code": 0}
315
316 logging.info("Queuing message with change package locks results to "
317 "exchange urgently.")
318 return self._broker.send_message(response, True)
319
320 def _send_change_package_holds_response(self, response):
321 """Log that a package holds result is sent and send the response."""
322 logging.info("Queuing message with change package holds results to "
323 "exchange urgently.")
324 return self._broker.send_message(response, True)
325
326 def handle_change_package_holds(self, message):
327 """Handle a C{change-package-holds} message.
328
329 Create and delete package holds as requested by the given C{message}.
330 """
331 if not self._facade.supports_package_holds:
332 response = {
333 "type": "operation-result",
334 "operation-id": message.get("operation-id"),
335 "status": FAILED,
336 "result-text": "This client doesn't support package holds.",
337 "result-code": 1}
338 return self._send_change_package_holds_response(response)
339
340 not_installed = set()
341 holds_to_create = message.get("create", [])
342 versions_to_create = set()
343 for id in holds_to_create:
344 hash = self._store.get_id_hash(id)
345 hold_version = self._facade.get_package_by_hash(hash)
346 if (hold_version
347 and self._facade.is_package_installed(hold_version)):
348 versions_to_create.add((hold_version.package, hold_version))
349 else:
350 not_installed.add(str(id))
351 holds_to_remove = message.get("delete", [])
352 versions_to_remove = set()
353 for id in holds_to_remove:
354 hash = self._store.get_id_hash(id)
355 hold_version = self._facade.get_package_by_hash(hash)
356 if (hold_version
357 and self._facade.is_package_installed(hold_version)):
358 versions_to_remove.add((hold_version.package, hold_version))
359
360 if not_installed:
361 response = {
362 "type": "operation-result",
363 "operation-id": message.get("operation-id"),
364 "status": FAILED,
365 "result-text": "Package holds not changed, since the" +
366 " following packages are not installed: %s" % (
367 ", ".join(sorted(not_installed))),
368 "result-code": 1}
369 return self._send_change_package_holds_response(response)
370
371 for package, hold_version in versions_to_create:
372 self._facade.set_package_hold(hold_version)
373 for package, hold_version in versions_to_remove:
374 self._facade.remove_package_hold(hold_version)
375
376 self._facade.reload_channels()
377
378 response = {"type": "operation-result",
379 "operation-id": message.get("operation-id"),
380 "status": SUCCEEDED,
381 "result-text": "Package holds successfully changed.",
382 "result-code": 0}
383
384 return self._send_change_package_holds_response(response)
385303
386 @staticmethod304 @staticmethod
387 def find_command():305 def find_command():
388306
=== modified file 'landscape/package/facade.py'
--- landscape/package/facade.py 2012-03-21 16:15:49 +0000
+++ landscape/package/facade.py 2012-06-04 14:08:28 +0000
@@ -5,15 +5,6 @@
5from cStringIO import StringIO5from cStringIO import StringIO
6from operator import attrgetter6from operator import attrgetter
77
8has_smart = True
9try:
10 import smart
11 from smart.transaction import (
12 Transaction, PolicyInstall, PolicyUpgrade, PolicyRemove, Failed)
13 from smart.const import INSTALL, REMOVE, UPGRADE, ALWAYS, NEVER
14except ImportError:
15 has_smart = False
16
17# Importing apt throws a FutureWarning on hardy, that we don't want to8# Importing apt throws a FutureWarning on hardy, that we don't want to
18# see.9# see.
19import warnings10import warnings
@@ -24,19 +15,13 @@
24import apt_inst15import apt_inst
25import apt_pkg16import apt_pkg
2617
27has_new_enough_apt = True
28from aptsources.sourceslist import SourcesList18from aptsources.sourceslist import SourcesList
29try:19from apt.progress.text import AcquireProgress
30 from apt.progress.text import AcquireProgress20from apt.progress.base import InstallProgress
31 from apt.progress.base import InstallProgress
32except ImportError:
33 AcquireProgress = object
34 InstallProgress = object
35 has_new_enough_apt = False
3621
37from landscape.lib.fs import append_file, create_file, read_file22from landscape.lib.fs import append_file, create_file, read_file
38from landscape.constants import UBUNTU_PATH23from landscape.constants import UBUNTU_PATH
39from landscape.package.skeleton import build_skeleton, build_skeleton_apt24from landscape.package.skeleton import build_skeleton_apt
4025
4126
42class TransactionError(Exception):27class TransactionError(Exception):
@@ -54,10 +39,6 @@
54 ", ".join([str(package) for package in self.packages]))39 ", ".join([str(package) for package in self.packages]))
5540
5641
57class SmartError(Exception):
58 """Raised when Smart fails in an undefined way."""
59
60
61class ChannelError(Exception):42class ChannelError(Exception):
62 """Raised when channels fail to load."""43 """Raised when channels fail to load."""
6344
@@ -108,8 +89,6 @@
108 database.89 database.
109 """90 """
11091
111 supports_package_holds = True
112 supports_package_locks = False
113 _dpkg_status = "/var/lib/dpkg/status"92 _dpkg_status = "/var/lib/dpkg/status"
11493
115 def __init__(self, root=None):94 def __init__(self, root=None):
@@ -127,6 +106,8 @@
127 self._version_installs = []106 self._version_installs = []
128 self._global_upgrade = False107 self._global_upgrade = False
129 self._version_removals = []108 self._version_removals = []
109 self._version_hold_creations = []
110 self._version_hold_removals = []
130 self.refetch_package_index = False111 self.refetch_package_index = False
131112
132 def _ensure_dir_structure(self):113 def _ensure_dir_structure(self):
@@ -150,9 +131,6 @@
150 os.makedirs(full_path)131 os.makedirs(full_path)
151 return full_path132 return full_path
152133
153 def deinit(self):
154 """This method exists solely to be compatible with C{SmartFacade}."""
155
156 def get_packages(self):134 def get_packages(self):
157 """Get all the packages available in the channels."""135 """Get all the packages available in the channels."""
158 return self._hash2pkg.itervalues()136 return self._hash2pkg.itervalues()
@@ -167,17 +145,6 @@
167 if (self.is_package_installed(version)145 if (self.is_package_installed(version)
168 and self._is_package_held(version.package))]146 and self._is_package_held(version.package))]
169147
170 def get_package_locks(self):
171 """Return all set package locks.
172
173 @return: A C{list} of ternary tuples, contaning the name, relation
174 and version details for each lock currently set on the system.
175
176 XXX: This method isn't implemented yet. It's here to make the
177 transition to Apt in the package reporter easier.
178 """
179 return []
180
181 def get_package_holds(self):148 def get_package_holds(self):
182 """Return the name of all the packages that are on hold."""149 """Return the name of all the packages that are on hold."""
183 return [version.package.name for version in self.get_locked_packages()]150 return [version.package.name for version in self.get_locked_packages()]
@@ -204,7 +171,8 @@
204171
205 @param version: The version of the package to unhold.172 @param version: The version of the package to unhold.
206 """173 """
207 if not self._is_package_held(version.package):174 if (not self.is_package_installed(version) or
175 not self._is_package_held(version.package)):
208 return176 return
209 self._set_dpkg_selections(version.package.name + " install")177 self._set_dpkg_selections(version.package.name + " install")
210178
@@ -556,50 +524,125 @@
556 all_info.append(info + or_divider.join(relation_infos))524 all_info.append(info + or_divider.join(relation_infos))
557 return "\n".join(all_info)525 return "\n".join(all_info)
558526
559 def perform_changes(self):527 def _set_frontend_noninteractive(self):
560 """Perform the pending package operations."""528 """
561 # Try to enforce non-interactivity529 Set the environment to avoid attempts by apt to interact with a user.
530 """
562 os.environ["DEBIAN_FRONTEND"] = "noninteractive"531 os.environ["DEBIAN_FRONTEND"] = "noninteractive"
563 os.environ["APT_LISTCHANGES_FRONTEND"] = "none"532 os.environ["APT_LISTCHANGES_FRONTEND"] = "none"
564 os.environ["APT_LISTBUGS_FRONTEND"] = "none"533 os.environ["APT_LISTBUGS_FRONTEND"] = "none"
534
535 def _default_path_when_missing(self):
536 """
537 If no PATH is set in the environment, use the Ubuntu default PATH.
538
539 When the client is launched from the landscape-client-settings-ui the
540 PATH variable is incorrectly set, this method rectifies that.
541 """
565 # dpkg will fail if no path is set.542 # dpkg will fail if no path is set.
566 if "PATH" not in os.environ:543 if "PATH" not in os.environ:
567 os.environ["PATH"] = UBUNTU_PATH544 os.environ["PATH"] = UBUNTU_PATH
545
546 def _setup_dpkg_for_changes(self):
547 """
548 Setup environment and apt options for successful package operations.
549 """
550 self._set_frontend_noninteractive()
551 self._default_path_when_missing()
568 apt_pkg.config.clear("DPkg::options")552 apt_pkg.config.clear("DPkg::options")
569 apt_pkg.config.set("DPkg::options::", "--force-confold")553 apt_pkg.config.set("DPkg::options::", "--force-confold")
570554
555 def _perform_hold_changes(self):
556 """
557 Perform pending hold operations on packages.
558 """
559 hold_changes = (len(self._version_hold_creations) > 0 or
560 len(self._version_hold_removals) > 0)
561 if not hold_changes:
562 return None
563 not_installed = [version for version in
564 self._version_hold_creations
565 if not self.is_package_installed(version)]
566 if not_installed:
567 raise TransactionError(
568 "Cannot perform the changes, since the following " +
569 "packages are not installed: %s" % ", ".join(
570 [version.package.name
571 for version in sorted(not_installed)]))
572
573 for version in self._version_hold_creations:
574 self.set_package_hold(version)
575
576 for version in self._version_hold_removals:
577 self.remove_package_hold(version)
578
579 return "Package holds successfully changed."
580
581 def _commit_package_changes(self):
582 """
583 Commit cached APT operations and give feedback on the results as a
584 string.
585 """
586 fetch_output = StringIO()
587 # Redirect stdout and stderr to a file. We need to work with the
588 # file descriptors, rather than sys.stdout/stderr, since dpkg is
589 # run in a subprocess.
590 fd, install_output_path = tempfile.mkstemp()
591 old_stdout = os.dup(1)
592 old_stderr = os.dup(2)
593 os.dup2(fd, 1)
594 os.dup2(fd, 2)
595 install_progress = LandscapeInstallProgress()
596 try:
597 self._cache.commit(
598 fetch_progress=LandscapeAcquireProgress(fetch_output),
599 install_progress=install_progress)
600 if not install_progress.dpkg_exited:
601 raise SystemError("dpkg didn't exit cleanly.")
602 except SystemError, error:
603 result_text = (
604 fetch_output.getvalue() + read_file(install_output_path))
605 raise TransactionError(error.args[0] +
606 "\n\nPackage operation log:\n" +
607 result_text)
608 else:
609 result_text = (
610 fetch_output.getvalue() + read_file(install_output_path))
611 finally:
612 # Restore stdout and stderr.
613 os.dup2(old_stdout, 1)
614 os.dup2(old_stderr, 2)
615 os.remove(install_output_path)
616 return result_text
617
618 def _preprocess_installs(self, fixer):
619 for version in self._version_installs:
620 # Set the candidate version, so that the version we want to
621 # install actually is the one getting installed.
622 version.package.candidate = version
623 # Set auto_fix=False to avoid removing the package we asked to
624 # install when we need to resolve dependencies.
625 version.package.mark_install(auto_fix=False)
626 fixer.clear(version.package._pkg)
627 fixer.protect(version.package._pkg)
628
629 def _preprocess_removes(self, fixer):
571 held_package_names = set()630 held_package_names = set()
631
572 package_installs = set(632 package_installs = set(
573 version.package for version in self._version_installs)633 version.package for version in self._version_installs)
634
574 package_upgrades = set(635 package_upgrades = set(
575 version.package for version in self._version_removals636 version.package for version in self._version_removals
576 if version.package in package_installs)637 if version.package in package_installs)
577 version_changes = self._version_installs[:]638
578 version_changes.extend(self._version_removals)
579 if not version_changes and not self._global_upgrade:
580 return None
581 fixer = apt_pkg.ProblemResolver(self._cache._depcache)
582 already_broken_packages = self._get_broken_packages()
583 for version in self._version_installs:
584 # Set the candidate version, so that the version we want to
585 # install actually is the one getting installed.
586 version.package.candidate = version
587 version.package.mark_install(auto_fix=False)
588 # If we need to resolve dependencies, try avoiding having
589 # the package we asked to be installed from being removed.
590 # (This is what would have been done if auto_fix would have
591 # been True.
592 fixer.clear(version.package._pkg)
593 fixer.protect(version.package._pkg)
594 if self._global_upgrade:
595 self._cache.upgrade(dist_upgrade=True)
596 for version in self._version_removals:639 for version in self._version_removals:
597 if self._is_package_held(version.package):640 if self._is_package_held(version.package):
598 held_package_names.add(version.package.name)641 held_package_names.add(version.package.name)
599 if version.package in package_upgrades:642 if version.package in package_upgrades:
600 # The server requests the old version to be removed for643 # The server requests the old version to be removed for
601 # upgrades, since Smart worked that way. For Apt we have644 # upgrades, since Smart worked that way. For Apt we have
602 # to take care not to mark upgraded packages for # removal.645 # to take care not to mark upgraded packages for removal.
603 continue646 continue
604 version.package.mark_delete(auto_fix=False)647 version.package.mark_delete(auto_fix=False)
605 # Configure the resolver in the same way648 # Configure the resolver in the same way
@@ -614,50 +657,65 @@
614 "Can't perform the changes, since the following packages" +657 "Can't perform the changes, since the following packages" +
615 " are held: %s" % ", ".join(sorted(held_package_names)))658 " are held: %s" % ", ".join(sorted(held_package_names)))
616659
660 def _preprocess_global_upgrade(self):
661 if self._global_upgrade:
662 self._cache.upgrade(dist_upgrade=True)
663
664 def _resolve_broken_packages(self, fixer, already_broken_packages):
665 """
666 Attempt to automatically resolve problems with broken packages.
667 """
617 now_broken_packages = self._get_broken_packages()668 now_broken_packages = self._get_broken_packages()
618 if now_broken_packages != already_broken_packages:669 if now_broken_packages != already_broken_packages:
619 try:670 try:
620 fixer.resolve(True)671 fixer.resolve(True)
621 except SystemError, error:672 except SystemError, error:
622 raise TransactionError(673 raise TransactionError(error.args[0] + "\n" +
623 error.args[0] + "\n" + self._get_unmet_dependency_info())674 self._get_unmet_dependency_info())
675
676 def _preprocess_package_changes(self):
677 version_changes = self._version_installs[:]
678 version_changes.extend(self._version_removals)
679 if (not version_changes and not self._global_upgrade):
680 return []
681 already_broken_packages = self._get_broken_packages()
682 fixer = apt_pkg.ProblemResolver(self._cache._depcache)
683 self._preprocess_installs(fixer)
684 self._preprocess_global_upgrade()
685 self._preprocess_removes(fixer)
686 self._resolve_broken_packages(fixer, already_broken_packages)
687 return version_changes
688
689 def _perform_package_changes(self):
690 """
691 Perform pending install/remove/upgrade operations.
692 """
693 version_changes = self._preprocess_package_changes()
624 if not self._check_changes(version_changes):694 if not self._check_changes(version_changes):
625 return None695 return None
626 fetch_output = StringIO()696 return self._commit_package_changes()
627 # Redirect stdout and stderr to a file. We need to work with the697
628 # file descriptors, rather than sys.stdout/stderr, since dpkg is698 def perform_changes(self):
629 # run in a subprocess.699 """
630 fd, install_output_path = tempfile.mkstemp()700 Perform the pending package operations.
631 old_stdout = os.dup(1)701 """
632 old_stderr = os.dup(2)702 self._setup_dpkg_for_changes()
633 os.dup2(fd, 1)703 hold_result_text = self._perform_hold_changes()
634 os.dup2(fd, 2)704 package_result_text = self._perform_package_changes()
635 install_progress = LandscapeInstallProgress()705 results = []
636 try:706 if package_result_text is not None:
637 self._cache.commit(707 results.append(package_result_text)
638 fetch_progress=LandscapeAcquireProgress(fetch_output),708 if hold_result_text is not None:
639 install_progress=install_progress)709 results.append(hold_result_text)
640 if not install_progress.dpkg_exited:710 if len(results) > 0:
641 raise SystemError("dpkg didn't exit cleanly.")711 return " ".join(results)
642 except SystemError, error:
643 result_text = (
644 fetch_output.getvalue() + read_file(install_output_path))
645 raise TransactionError(
646 error.args[0] + "\n\nPackage operation log:\n" + result_text)
647 else:
648 result_text = (
649 fetch_output.getvalue() + read_file(install_output_path))
650 finally:
651 # Restore stdout and stderr.
652 os.dup2(old_stdout, 1)
653 os.dup2(old_stderr, 2)
654 os.remove(install_output_path)
655 return result_text
656712
657 def reset_marks(self):713 def reset_marks(self):
658 """Clear the pending package operations."""714 """Clear the pending package operations."""
659 del self._version_installs[:]715 del self._version_installs[:]
660 del self._version_removals[:]716 del self._version_removals[:]
717 del self._version_hold_removals[:]
718 del self._version_hold_creations[:]
661 self._global_upgrade = False719 self._global_upgrade = False
662 self._cache.clear()720 self._cache.clear()
663721
@@ -673,403 +731,10 @@
673 """Mark the package for removal."""731 """Mark the package for removal."""
674 self._version_removals.append(version)732 self._version_removals.append(version)
675733
676734 def mark_hold(self, version):
677class SmartFacade(object):735 """Mark the package to be held."""
678 """Wrapper for tasks using Smart.736 self._version_hold_creations.append(version)
679737
680 This object wraps Smart features, in a way that makes using and testing738 def mark_remove_hold(self, version):
681 these features slightly more comfortable.739 """Mark the package to have its hold removed."""
682740 self._version_hold_removals.append(version)
683 @param smart_init_kwargs: A dictionary that can be used to pass specific
684 keyword parameters to to L{smart.init}.
685 """
686
687 _deb_package_type = None
688 supports_package_holds = False
689 supports_package_locks = True
690
691 def __init__(self, smart_init_kwargs={}, sysconf_args=None):
692 if not has_smart:
693 raise RuntimeError(
694 "Smart needs to be installed if SmartFacade is used.")
695 self._smart_init_kwargs = smart_init_kwargs.copy()
696 self._smart_init_kwargs.setdefault("interface", "landscape")
697 self._sysconfig_args = sysconf_args or {}
698 self._reset()
699
700 def _reset(self):
701 # This attribute is initialized lazily in the _get_ctrl() method.
702 self._ctrl = None
703 self._pkg2hash = {}
704 self._hash2pkg = {}
705 self._marks = {}
706 self._caching = ALWAYS
707 self._channels_reloaded = False
708
709 def deinit(self):
710 """Deinitialize the Facade and the Smart library."""
711 if self._ctrl:
712 smart.deinit()
713 self._reset()
714
715 def _get_ctrl(self):
716 if self._ctrl is None:
717 if self._smart_init_kwargs.get("interface") == "landscape":
718 from landscape.package.interface import (
719 install_landscape_interface)
720 install_landscape_interface()
721 self._ctrl = smart.init(**self._smart_init_kwargs)
722 for key, value in self._sysconfig_args.items():
723 smart.sysconf.set(key, value, soft=True)
724 smart.initDistro(self._ctrl)
725 smart.initPlugins()
726 smart.sysconf.set("pm-iface-output", True, soft=True)
727 smart.sysconf.set("deb-non-interactive", True, soft=True)
728
729 # We can't import it before hand because reaching .deb.* depends
730 # on initialization (yeah, sucky).
731 from smart.backends.deb.base import DebPackage
732 self._deb_package_type = DebPackage
733
734 self.smart_initialized()
735 return self._ctrl
736
737 def smart_initialized(self):
738 """Hook called when the Smart library is initialized."""
739
740 def ensure_channels_reloaded(self):
741 """Reload the channels if they haven't been reloaded yet."""
742 if self._channels_reloaded:
743 return
744 self._channels_reloaded = True
745 self.reload_channels()
746
747 def reload_channels(self, force_reload_binaries=False):
748 """
749 Reload Smart channels, getting all the cache (packages) in memory.
750
751 @raise: L{ChannelError} if Smart fails to reload the channels.
752 """
753 ctrl = self._get_ctrl()
754
755 try:
756 reload_result = ctrl.reloadChannels(caching=self._caching)
757 except smart.Error:
758 failed = True
759 else:
760 # Raise an error only if we are trying to download remote lists
761 failed = not reload_result and self._caching == NEVER
762 if failed:
763 raise ChannelError("Smart failed to reload channels (%s)"
764 % smart.sysconf.get("channels"))
765
766 self._hash2pkg.clear()
767 self._pkg2hash.clear()
768
769 for pkg in self.get_packages():
770 hash = self.get_package_skeleton(pkg, False).get_hash()
771 self._hash2pkg[hash] = pkg
772 self._pkg2hash[pkg] = hash
773
774 self.channels_reloaded()
775
776 def channels_reloaded(self):
777 """Hook called after Smart channels are reloaded."""
778
779 def get_package_skeleton(self, pkg, with_info=True):
780 """Return a skeleton for the provided package.
781
782 The skeleton represents the basic structure of the package.
783
784 @param pkg: Package to build skeleton from.
785 @param with_info: If True, the skeleton will include information
786 useful for sending data to the server. Such information isn't
787 necessary if the skeleton will be used to build a hash.
788
789 @return: a L{PackageSkeleton} object.
790 """
791 return build_skeleton(pkg, with_info)
792
793 def get_package_hash(self, pkg):
794 """Return a hash from the given package.
795
796 @param pkg: a L{smart.backends.deb.base.DebPackage} objects
797 """
798 return self._pkg2hash.get(pkg)
799
800 def get_package_hashes(self):
801 """Get the hashes of all the packages available in the channels."""
802 return self._pkg2hash.values()
803
804 def get_packages(self):
805 """
806 Get all the packages available in the channels.
807
808 @return: a C{list} of L{smart.backends.deb.base.DebPackage} objects
809 """
810 return [pkg for pkg in self._get_ctrl().getCache().getPackages()
811 if isinstance(pkg, self._deb_package_type)]
812
813 def get_locked_packages(self):
814 """Get all packages in the channels matching the set locks."""
815 return smart.pkgconf.filterByFlag("lock", self.get_packages())
816
817 def get_packages_by_name(self, name):
818 """
819 Get all available packages matching the provided name.
820
821 @return: a C{list} of L{smart.backends.deb.base.DebPackage} objects
822 """
823 return [pkg for pkg in self._get_ctrl().getCache().getPackages(name)
824 if isinstance(pkg, self._deb_package_type)]
825
826 def get_package_by_hash(self, hash):
827 """
828 Get all available packages matching the provided hash.
829
830 @return: a C{list} of L{smart.backends.deb.base.DebPackage} objects
831 """
832 return self._hash2pkg.get(hash)
833
834 def mark_install(self, pkg):
835 self._marks[pkg] = INSTALL
836
837 def mark_remove(self, pkg):
838 self._marks[pkg] = REMOVE
839
840 def mark_upgrade(self, pkg):
841 self._marks[pkg] = UPGRADE
842
843 def mark_global_upgrade(self):
844 """Upgrade all installed packages."""
845 for package in self.get_packages():
846 if self.is_package_installed(package):
847 self.mark_upgrade(package)
848
849 def reset_marks(self):
850 self._marks.clear()
851
852 def perform_changes(self):
853 ctrl = self._get_ctrl()
854 cache = ctrl.getCache()
855
856 transaction = Transaction(cache)
857
858 policy = PolicyInstall
859
860 only_remove = True
861 for pkg, oper in self._marks.items():
862 if oper == UPGRADE:
863 policy = PolicyUpgrade
864 if oper != REMOVE:
865 only_remove = False
866 transaction.enqueue(pkg, oper)
867
868 if only_remove:
869 policy = PolicyRemove
870
871 transaction.setPolicy(policy)
872
873 try:
874 transaction.run()
875 except Failed, e:
876 raise TransactionError(e.args[0])
877 changeset = transaction.getChangeSet()
878
879 if not changeset:
880 return None # Nothing to do.
881
882 missing = []
883 for pkg, op in changeset.items():
884 if self._marks.get(pkg) != op:
885 missing.append(pkg)
886 if missing:
887 raise DependencyError(missing)
888
889 try:
890 self._ctrl.commitChangeSet(changeset)
891 except smart.Error, e:
892 raise TransactionError(e.args[0])
893
894 output = smart.iface.get_output_for_landscape()
895 failed = smart.iface.has_failed_for_landscape()
896
897 smart.iface.reset_for_landscape()
898
899 if failed:
900 raise SmartError(output)
901 return output
902
903 def reload_cache(self):
904 cache = self._get_ctrl().getCache()
905 cache.reset()
906 cache.load()
907
908 def get_arch(self):
909 """
910 Get the host dpkg architecture.
911 """
912 self._get_ctrl()
913 from smart.backends.deb.loader import DEBARCH
914 return DEBARCH
915
916 def set_arch(self, arch):
917 """
918 Set the host dpkg architecture.
919
920 To take effect it must be called before L{reload_channels}.
921
922 @param arch: the dpkg architecture to use (e.g. C{"i386"})
923 """
924 self._get_ctrl()
925 smart.sysconf.set("deb-arch", arch)
926
927 # XXX workaround Smart setting DEBARCH statically in the
928 # smart.backends.deb.base module
929 import smart.backends.deb.loader as loader
930 loader.DEBARCH = arch
931
932 def set_caching(self, mode):
933 """
934 Set Smart's caching mode.
935
936 @param mode: The caching mode to pass to Smart's C{reloadChannels}
937 when calling L{reload_channels} (e.g C{smart.const.NEVER} or
938 C{smart.const.ALWAYS}).
939 """
940 self._caching = mode
941
942 def reset_channels(self):
943 """Remove all configured Smart channels."""
944 self._get_ctrl()
945 smart.sysconf.set("channels", {}, soft=True)
946
947 def add_channel(self, alias, channel):
948 """
949 Add a Smart channel.
950
951 This method can be called more than once to set multiple channels.
952 To take effect it must be called before L{reload_channels}.
953
954 @param alias: A string identifying the channel to be added.
955 @param channel: A C{dict} holding information about the channel to
956 add (see the Smart API for details about valid keys and values).
957 """
958 channels = self.get_channels()
959 channels.update({alias: channel})
960 smart.sysconf.set("channels", channels, soft=True)
961
962 def add_channel_apt_deb(self, url, codename, components):
963 """Add a Smart channel of type C{"apt-deb"}.
964
965 @see: L{add_channel}
966 """
967 alias = codename
968 channel = {"baseurl": url, "distribution": codename,
969 "components": components, "type": "apt-deb"}
970 self.add_channel(alias, channel)
971
972 def add_channel_deb_dir(self, path):
973 """Add a Smart channel of type C{"deb-dir"}.
974
975 @see: L{add_channel}
976 """
977 alias = path
978 channel = {"path": path, "type": "deb-dir"}
979 self.add_channel(alias, channel)
980
981 def clear_channels(self):
982 """Clear channels.
983
984 This method exists to be compatible with AptFacade. Smart
985 doesn't need to clear its channels.
986 """
987
988 def get_channels(self):
989 """
990 @return: A C{dict} of all configured channels.
991 """
992 self._get_ctrl()
993 return smart.sysconf.get("channels")
994
995 def get_package_locks(self):
996 """Return all set package locks.
997
998 @return: A C{list} of ternary tuples, contaning the name, relation
999 and version details for each lock currently set on the system.
1000 """
1001 self._get_ctrl()
1002 locks = []
1003 locks_by_name = smart.pkgconf.getFlagTargets("lock")
1004 for name in locks_by_name:
1005 for condition in locks_by_name[name]:
1006 relation = condition[0] or ""
1007 version = condition[1] or ""
1008 locks.append((name, relation, version))
1009 return locks
1010
1011 def _validate_lock_condition(self, relation, version):
1012 if relation and not version:
1013 raise RuntimeError("Package lock version not provided")
1014 if version and not relation:
1015 raise RuntimeError("Package lock relation not provided")
1016
1017 def set_package_lock(self, name, relation=None, version=None):
1018 """Set a new package lock.
1019
1020 Any package matching the given name and possibly the given version
1021 condition will be locked.
1022
1023 @param name: The name a package must match in order to be locked.
1024 @param relation: Optionally, the relation of the version condition the
1025 package must satisfy in order to be considered as locked.
1026 @param version: Optionally, the version associated with C{relation}.
1027
1028 @note: If used at all, the C{relation} and C{version} parameter must be
1029 both provided.
1030 """
1031 self._validate_lock_condition(relation, version)
1032 self._get_ctrl()
1033 smart.pkgconf.setFlag("lock", name, relation, version)
1034
1035 def remove_package_lock(self, name, relation=None, version=None):
1036 """Remove a package lock."""
1037 self._validate_lock_condition(relation, version)
1038 self._get_ctrl()
1039 smart.pkgconf.clearFlag("lock", name=name, relation=relation,
1040 version=version)
1041
1042 def save_config(self):
1043 """Flush the current smart configuration to disk."""
1044 control = self._get_ctrl()
1045 control.saveSysConf()
1046
1047 def is_package_installed(self, package):
1048 """Is the package installed?"""
1049 return package.installed
1050
1051 def is_package_available(self, package):
1052 """Is the package available for installation?"""
1053 for loader in package.loaders:
1054 # Is the package also in a non-installed
1055 # loader? IOW, "available".
1056 if not loader.getInstalled():
1057 return True
1058 return False
1059
1060 def is_package_upgrade(self, package):
1061 """Is the package an upgrade for another installed package?"""
1062 is_upgrade = False
1063 for upgrade in package.upgrades:
1064 for provides in upgrade.providedby:
1065 for provides_package in provides.packages:
1066 if provides_package.installed:
1067 is_upgrade = True
1068 break
1069 else:
1070 continue
1071 break
1072 else:
1073 continue
1074 break
1075 return is_upgrade
1076741
=== removed file 'landscape/package/interface.py'
--- landscape/package/interface.py 2012-03-19 09:33:34 +0000
+++ landscape/package/interface.py 1970-01-01 00:00:00 +0000
@@ -1,84 +0,0 @@
1import logging
2import types
3import sys
4
5try:
6 import smart.interfaces
7 from smart.interface import Interface
8 from smart.const import ERROR, WARNING, INFO, DEBUG
9except ImportError:
10 # Smart is optional if AptFacade is being used.
11 Interface = object
12
13
14class LandscapeInterface(Interface):
15
16 __output = ""
17 __failed = False
18
19 def reset_for_landscape(self):
20 """Reset output and failed flag."""
21 self.__failed = False
22 self.__output = u""
23
24 def get_output_for_landscape(self):
25 """showOutput() is cached, and returned by this method."""
26 return self.__output
27
28 def has_failed_for_landscape(self):
29 """Return true if any error() messages were logged."""
30 return self.__failed
31
32 def error(self, msg):
33 self.__failed = True
34 # Calling these logging.* functions here instead of message()
35 # below will output the message or not depending on the debug
36 # level set in landscape-client, rather than the one set in
37 # Smart's configuration.
38 logging.error("[Smart] %s", msg)
39 super(LandscapeInterface, self).error(msg)
40
41 def warning(self, msg):
42 logging.warning("[Smart] %s", msg)
43 super(LandscapeInterface, self).warning(msg)
44
45 def info(self, msg):
46 logging.info("[Smart] %s", msg)
47 super(LandscapeInterface, self).info(msg)
48
49 def debug(self, msg):
50 logging.debug("[Smart] %s", msg)
51 super(LandscapeInterface, self).debug(msg)
52
53 def message(self, level, msg):
54 prefix = {ERROR: "ERROR", WARNING: "WARNING",
55 INFO: "INFO", DEBUG: "DEBUG"}.get(level)
56 self.showOutput("%s: %s\n" % (prefix, msg))
57
58 def showOutput(self, output):
59 if not isinstance(output, unicode):
60 try:
61 output = output.decode("utf-8")
62 except UnicodeDecodeError:
63 output = output.decode("ascii", "replace")
64 self.__output += output
65
66
67class LandscapeInterfaceModule(types.ModuleType):
68
69 def __init__(self):
70 super(LandscapeInterfaceModule, self).__init__("landscape")
71
72 def create(self, ctrl, command=None, argv=None):
73 return LandscapeInterface(ctrl)
74
75
76def install_landscape_interface():
77 if "smart.interfaces.landscape" not in sys.modules:
78 # Plug the interface in a place Smart will recognize.
79 smart.interfaces.landscape = LandscapeInterfaceModule()
80 sys.modules["smart.interfaces.landscape"] = smart.interfaces.landscape
81
82
83def uninstall_landscape_interface():
84 sys.modules.pop("smart.interfaces.landscape", None)
850
=== modified file 'landscape/package/releaseupgrader.py'
--- landscape/package/releaseupgrader.py 2011-07-18 15:16:18 +0000
+++ landscape/package/releaseupgrader.py 2012-06-04 14:08:28 +0000
@@ -301,8 +301,8 @@
301301
302 reporter = find_reporter_command()302 reporter = find_reporter_command()
303303
304 # Force a smart-update run, because the sources.list has changed304 # Force an apt-update run, because the sources.list has changed
305 args = ["--force-smart-update"]305 args = ["--force-apt-update"]
306306
307 if self._config.config is not None:307 if self._config.config is not None:
308 args.append("--config=%s" % self._config.config)308 args.append("--config=%s" % self._config.config)
309309
=== modified file 'landscape/package/reporter.py'
--- landscape/package/reporter.py 2012-03-19 09:33:34 +0000
+++ landscape/package/reporter.py 2012-06-04 14:08:28 +0000
@@ -12,7 +12,6 @@
12from landscape.lib.fs import touch_file12from landscape.lib.fs import touch_file
13from landscape.lib import bpickle13from landscape.lib import bpickle
1414
15from landscape.package.facade import AptFacade
16from landscape.package.taskhandler import (15from landscape.package.taskhandler import (
17 PackageTaskHandlerConfiguration, PackageTaskHandler, run_task_handler)16 PackageTaskHandlerConfiguration, PackageTaskHandler, run_task_handler)
18from landscape.package.store import UnknownHashIDRequest, FakePackageStore17from landscape.package.store import UnknownHashIDRequest, FakePackageStore
@@ -31,9 +30,9 @@
31 reporter-specific options.30 reporter-specific options.
32 """31 """
33 parser = super(PackageReporterConfiguration, self).make_parser()32 parser = super(PackageReporterConfiguration, self).make_parser()
34 parser.add_option("--force-smart-update", default=False,33 parser.add_option("--force-apt-update", default=False,
35 action="store_true",34 action="store_true",
36 help="Force running smart-update.")35 help="Force running apt-update.")
37 return parser36 return parser
3837
3938
@@ -41,15 +40,14 @@
41 """Report information about the system packages.40 """Report information about the system packages.
4241
43 @cvar queue_name: Name of the task queue to pick tasks from.42 @cvar queue_name: Name of the task queue to pick tasks from.
44 @cvar smart_update_interval: Time interval in minutes to pass to43 @cvar apt_update_interval: Don't update the APT index more often
45 the C{--after} command line option of C{smart-update}.44 than the given interval in minutes.
46 """45 """
47 config_factory = PackageReporterConfiguration46 config_factory = PackageReporterConfiguration
4847
49 queue_name = "reporter"48 queue_name = "reporter"
5049
51 smart_update_interval = 6050 apt_update_interval = 60
52 smart_update_filename = "/usr/lib/landscape/smart-update"
53 apt_update_filename = "/usr/lib/landscape/apt-update"51 apt_update_filename = "/usr/lib/landscape/apt-update"
54 sources_list_filename = "/etc/apt/sources.list"52 sources_list_filename = "/etc/apt/sources.list"
55 sources_list_directory = "/etc/apt/sources.list.d"53 sources_list_directory = "/etc/apt/sources.list.d"
@@ -57,13 +55,7 @@
57 def run(self):55 def run(self):
58 result = Deferred()56 result = Deferred()
5957
60 if isinstance(self._facade, AptFacade):58 result.addCallback(lambda x: self.run_apt_update())
61 # Update APT cache if APT facade is enabled.
62 result.addCallback(lambda x: self.run_apt_update())
63 else:
64 # Run smart-update before anything else, to make sure that
65 # the SmartFacade will load freshly updated channels
66 result.addCallback(lambda x: self.run_smart_update())
6759
68 # If the appropriate hash=>id db is not there, fetch it60 # If the appropriate hash=>id db is not there, fetch it
69 result.addCallback(lambda x: self.fetch_hash_id_db())61 result.addCallback(lambda x: self.fetch_hash_id_db())
@@ -183,44 +175,6 @@
183175
184 return False176 return False
185177
186 def run_smart_update(self):
187 """Run smart-update and log a warning in case of non-zero exit code.
188
189 @return: a deferred returning (out, err, code)
190 """
191 if self._config.force_smart_update or self._apt_sources_have_changed():
192 args = ()
193 else:
194 args = ("--after", str(self.smart_update_interval))
195 result = spawn_process(self.smart_update_filename, args=args)
196
197 def callback((out, err, code)):
198 # smart-update --after N will exit with error code 1 when it
199 # doesn't actually run the update code because to enough time
200 # has passed yet, but we don't actually consider it a failure.
201 smart_failed = False
202 if code != 0 and code != 1:
203 smart_failed = True
204 if code == 1 and err.strip() != "":
205 smart_failed = True
206 if smart_failed:
207 logging.warning("'%s' exited with status %d (%s)" % (
208 self.smart_update_filename, code, err))
209 logging.debug("'%s' exited with status %d (out='%s', err='%s'" % (
210 self.smart_update_filename, code, out, err))
211 touch_file(self._config.update_stamp_filename)
212 if not smart_failed and not self._facade.get_channels():
213 code = 1
214 err = "There are no APT sources configured in %s or %s." % (
215 self.sources_list_filename, self.sources_list_directory)
216 deferred = self._broker.call_if_accepted(
217 "package-reporter-result", self.send_result, code, err)
218 deferred.addCallback(lambda ignore: (out, err, code))
219 return deferred
220
221 result.addCallback(callback)
222 return result
223
224 def _apt_update_timeout_expired(self, interval):178 def _apt_update_timeout_expired(self, interval):
225 """Check if the apt-update timeout has passed."""179 """Check if the apt-update timeout has passed."""
226 stamp = self._config.update_stamp_filename180 stamp = self._config.update_stamp_filename
@@ -237,8 +191,8 @@
237191
238 @return: a deferred returning (out, err, code)192 @return: a deferred returning (out, err, code)
239 """193 """
240 if (self._config.force_smart_update or self._apt_sources_have_changed()194 if (self._config.force_apt_update or self._apt_sources_have_changed()
241 or self._apt_update_timeout_expired(self.smart_update_interval)):195 or self._apt_update_timeout_expired(self.apt_update_interval)):
242196
243 result = spawn_process(self.apt_update_filename)197 result = spawn_process(self.apt_update_filename)
244198
@@ -323,7 +277,6 @@
323 self._store.clear_available_upgrades()277 self._store.clear_available_upgrades()
324 self._store.clear_installed()278 self._store.clear_installed()
325 self._store.clear_locked()279 self._store.clear_locked()
326 self._store.clear_package_locks()
327280
328 # Don't clear the hash_id_requests table because the messages281 # Don't clear the hash_id_requests table because the messages
329 # associated with the existing requests might still have to be282 # associated with the existing requests might still have to be
@@ -405,7 +358,7 @@
405 def request_unknown_hashes(self):358 def request_unknown_hashes(self):
406 """Detect available packages for which we have no hash=>id mappings.359 """Detect available packages for which we have no hash=>id mappings.
407360
408 This method will verify if there are packages that Smart knows361 This method will verify if there are packages that APT knows
409 about but for which we don't have an id yet (no hash => id362 about but for which we don't have an id yet (no hash => id
410 translation), and deliver a message (unknown-package-hashes)363 translation), and deliver a message (unknown-package-hashes)
411 to request them.364 to request them.
@@ -466,16 +419,13 @@
466 reactor.419 reactor.
467 """420 """
468421
469 def changes_detected(results):422 def changes_detected(result):
470 # Release all smart locks, in case the changer runs after us.423 if result:
471 self._facade.deinit()
472 if True in results:
473 # Something has changed, notify the broker.424 # Something has changed, notify the broker.
474 return self._broker.fire_event("package-data-changed")425 return self._broker.fire_event("package-data-changed")
475426
476 result = gather_results([self.detect_packages_changes(),427 deferred = self.detect_packages_changes()
477 self.detect_package_locks_changes()])428 return deferred.addCallback(changes_detected)
478 return result.addCallback(changes_detected)
479429
480 def detect_packages_changes(self):430 def detect_packages_changes(self):
481 """Detect changes in the universe of known packages.431 """Detect changes in the universe of known packages.
@@ -609,53 +559,6 @@
609559
610 return result560 return result
611561
612 def detect_package_locks_changes(self):
613 """Detect changes in known package locks.
614
615 This method will verify if there are package locks that:
616
617 - are now set, and were not;
618 - were previously set but are not anymore;
619
620 In all cases, the server is notified of the new situation
621 with a "packages" message.
622
623 @return: A deferred resulting in C{True} if package lock changes were
624 detected with respect to the previous run, or C{False} otherwise.
625 """
626 old_package_locks = set(self._store.get_package_locks())
627 current_package_locks = set(self._facade.get_package_locks())
628
629 set_package_locks = current_package_locks - old_package_locks
630 unset_package_locks = old_package_locks - current_package_locks
631
632 message = {}
633 if set_package_locks:
634 message["created"] = sorted(set_package_locks)
635 if unset_package_locks:
636 message["deleted"] = sorted(unset_package_locks)
637
638 if not message:
639 return succeed(False)
640
641 message["type"] = "package-locks"
642 result = self.send_message(message)
643
644 logging.info("Queuing message with changes in known package locks:"
645 " %d created, %d deleted." %
646 (len(set_package_locks), len(unset_package_locks)))
647
648 def update_currently_known(result):
649 if set_package_locks:
650 self._store.add_package_locks(set_package_locks)
651 if unset_package_locks:
652 self._store.remove_package_locks(unset_package_locks)
653 return True
654
655 result.addCallback(update_currently_known)
656
657 return result
658
659562
660class FakeGlobalReporter(PackageReporter):563class FakeGlobalReporter(PackageReporter):
661 """564 """
662565
=== modified file 'landscape/package/skeleton.py'
--- landscape/package/skeleton.py 2012-03-19 09:33:34 +0000
+++ landscape/package/skeleton.py 2012-06-04 14:08:28 +0000
@@ -47,54 +47,6 @@
47 return digest.digest()47 return digest.digest()
4848
4949
50def build_skeleton(pkg, with_info=False, with_unicode=False):
51 if not build_skeleton.inited:
52 build_skeleton.inited = True
53 global DebPackage, DebNameProvides, DebOrDepends
54
55 # Importing from backends depends on smart.init().
56 from smart.backends.deb.base import (
57 DebPackage, DebNameProvides, DebOrDepends)
58
59 if not isinstance(pkg, DebPackage):
60 raise PackageTypeError()
61
62 if with_unicode:
63 skeleton = PackageSkeleton(DEB_PACKAGE, unicode(pkg.name),
64 unicode(pkg.version))
65 else:
66 skeleton = PackageSkeleton(DEB_PACKAGE, pkg.name, pkg.version)
67 relations = set()
68 for relation in pkg.provides:
69 if isinstance(relation, DebNameProvides):
70 relations.add((DEB_NAME_PROVIDES, str(relation)))
71 else:
72 relations.add((DEB_PROVIDES, str(relation)))
73 for relation in pkg.requires:
74 if isinstance(relation, DebOrDepends):
75 relations.add((DEB_OR_REQUIRES, str(relation)))
76 else:
77 relations.add((DEB_REQUIRES, str(relation)))
78 for relation in pkg.upgrades:
79 relations.add((DEB_UPGRADES, str(relation)))
80 for relation in pkg.conflicts:
81 relations.add((DEB_CONFLICTS, str(relation)))
82
83 skeleton.relations = sorted(relations)
84
85 if with_info:
86 info = pkg.loaders.keys()[0].getInfo(pkg)
87 skeleton.section = info.getGroup()
88 skeleton.summary = info.getSummary()
89 skeleton.description = info.getDescription()
90 skeleton.size = sum(info.getSize(url) for url in info.getURLs())
91 skeleton.installed_size = info.getInstalledSize()
92
93 return skeleton
94
95build_skeleton.inited = False
96
97
98def relation_to_string(relation_tuple):50def relation_to_string(relation_tuple):
99 """Convert an apt relation to a string representation.51 """Convert an apt relation to a string representation.
10052
10153
=== modified file 'landscape/package/store.py'
--- landscape/package/store.py 2012-03-19 09:33:34 +0000
+++ landscape/package/store.py 2012-06-04 14:08:28 +0000
@@ -249,40 +249,6 @@
249 cursor.execute("DELETE FROM locked")249 cursor.execute("DELETE FROM locked")
250250
251 @with_cursor251 @with_cursor
252 def get_package_locks(self, cursor):
253 """Get all package locks."""
254 cursor.execute("SELECT name, relation, version FROM package_locks")
255 return [(row[0], row[1], row[2]) for row in cursor.fetchall()]
256
257 @with_cursor
258 def add_package_locks(self, cursor, locks):
259 """Add a list of package locks to the store.
260
261 @param locks: A C{list} of ternary tuples each one contains the
262 name, the relation and the version of the package lock to be added.
263 """
264 for name, relation, version in locks:
265 cursor.execute("REPLACE INTO package_locks VALUES (?, ?, ?)",
266 (name, relation or "", version or "",))
267
268 @with_cursor
269 def remove_package_locks(self, cursor, locks):
270 """Remove a list of package locks from the store.
271
272 @param locks: A C{list} of ternary tuples each one contains the name,
273 the relation and the version of the package lock to be removed.
274 """
275 for name, relation, version in locks:
276 cursor.execute("DELETE FROM package_locks WHERE name=? AND "
277 "relation=? AND version=?",
278 (name, relation or "", version or ""))
279
280 @with_cursor
281 def clear_package_locks(self, cursor):
282 """Remove all package locks."""
283 cursor.execute("DELETE FROM package_locks")
284
285 @with_cursor
286 def add_hash_id_request(self, cursor, hashes):252 def add_hash_id_request(self, cursor, hashes):
287 hashes = list(hashes)253 hashes = list(hashes)
288 cursor.execute("INSERT INTO hash_id_request (hashes, timestamp)"254 cursor.execute("INSERT INTO hash_id_request (hashes, timestamp)"
@@ -458,9 +424,6 @@
458 # try block.424 # try block.
459 cursor = db.cursor()425 cursor = db.cursor()
460 try:426 try:
461 cursor.execute("CREATE TABLE package_locks"
462 " (name TEXT NOT NULL, relation TEXT, version TEXT,"
463 " UNIQUE(name, relation, version))")
464 cursor.execute("CREATE TABLE locked"427 cursor.execute("CREATE TABLE locked"
465 " (id INTEGER PRIMARY KEY)")428 " (id INTEGER PRIMARY KEY)")
466 cursor.execute("CREATE TABLE available"429 cursor.execute("CREATE TABLE available"
467430
=== modified file 'landscape/package/taskhandler.py'
--- landscape/package/taskhandler.py 2012-03-19 09:33:34 +0000
+++ landscape/package/taskhandler.py 2012-06-04 14:08:28 +0000
@@ -254,19 +254,15 @@
254 words = re.findall("[A-Z][a-z]+", cls.__name__)254 words = re.findall("[A-Z][a-z]+", cls.__name__)
255 init_logging(config, "-".join(word.lower() for word in words))255 init_logging(config, "-".join(word.lower() for word in words))
256256
257 # Setup our umask for Smart to use, this needs to setup file permissions to257 # Setup our umask for Apt to use, this needs to setup file permissions to
258 # 0644 so...258 # 0644 so...
259 os.umask(022)259 os.umask(022)
260260
261 package_store = cls.package_store_class(config.store_filename)261 package_store = cls.package_store_class(config.store_filename)
262 # Delay importing of the facades so that we don't262 # Delay importing of the facades so that we don't
263 # import Smart unless we need to.263 # import Apt unless we need to.
264 from landscape.package.facade import (264 from landscape.package.facade import AptFacade
265 AptFacade, SmartFacade, has_new_enough_apt)265 package_facade = AptFacade()
266 if has_new_enough_apt:
267 package_facade = AptFacade()
268 else:
269 package_facade = SmartFacade()
270266
271 def finish():267 def finish():
272 connector.disconnect()268 connector.disconnect()
273269
=== modified file 'landscape/package/tests/helpers.py'
--- landscape/package/tests/helpers.py 2012-03-19 09:33:34 +0000
+++ landscape/package/tests/helpers.py 2012-06-04 14:08:28 +0000
@@ -3,12 +3,6 @@
3import textwrap3import textwrap
4import time4import time
55
6try:
7 import smart
8except ImportError:
9 # Smart is optional if AptFacade is being used.
10 pass
11
12import apt_inst6import apt_inst
13import apt_pkg7import apt_pkg
148
@@ -127,39 +121,6 @@
127 test_case.facade.add_channel_deb_dir(test_case.repository_dir)121 test_case.facade.add_channel_deb_dir(test_case.repository_dir)
128122
129123
130class SmartHelper(object):
131
132 def set_up(self, test_case):
133 test_case.smart_dir = test_case.makeDir()
134 test_case.smart_config = test_case.makeFile("")
135 test_case.repository_dir = test_case.makeDir()
136 create_simple_repository(test_case.repository_dir)
137
138 def tear_down(self, test_case):
139 if smart.iface.object:
140 smart.deinit()
141
142
143class SmartFacadeHelper(SmartHelper):
144
145 def set_up(self, test_case):
146 super(SmartFacadeHelper, self).set_up(test_case)
147
148 from landscape.package.facade import SmartFacade
149
150 class Facade(SmartFacade):
151 repository_dir = test_case.repository_dir
152
153 def smart_initialized(self):
154 self.reset_channels()
155 self.add_channel_deb_dir(test_case.repository_dir)
156
157 test_case.Facade = Facade
158 test_case.facade = Facade({"datadir": test_case.smart_dir,
159 "configfile": test_case.smart_config},
160 {"sync-apt-sources": False})
161
162
163PKGNAME1 = "name1_version1-release1_all.deb"124PKGNAME1 = "name1_version1-release1_all.deb"
164PKGNAME2 = "name2_version2-release2_all.deb"125PKGNAME2 = "name2_version2-release2_all.deb"
165PKGNAME3 = "name3_version3-release3_all.deb"126PKGNAME3 = "name3_version3-release3_all.deb"
166127
=== modified file 'landscape/package/tests/test_changer.py'
--- landscape/package/tests/test_changer.py 2012-03-21 16:15:49 +0000
+++ landscape/package/tests/test_changer.py 2012-06-04 14:08:28 +0000
@@ -6,12 +6,6 @@
66
7from twisted.internet.defer import Deferred7from twisted.internet.defer import Deferred
88
9try:
10 from smart.cache import Provides
11except ImportError:
12 # Smart is optional if AptFacade is being used.
13 pass
14
15from landscape.lib.fs import create_file, read_file, touch_file9from landscape.lib.fs import create_file, read_file, touch_file
16from landscape.package.changer import (10from landscape.package.changer import (
17 PackageChanger, main, find_changer_command, UNKNOWN_PACKAGE_DATA_TIMEOUT,11 PackageChanger, main, find_changer_command, UNKNOWN_PACKAGE_DATA_TIMEOUT,
@@ -19,19 +13,107 @@
19 POLICY_ALLOW_ALL_CHANGES, ERROR_RESULT)13 POLICY_ALLOW_ALL_CHANGES, ERROR_RESULT)
20from landscape.package.store import PackageStore14from landscape.package.store import PackageStore
21from landscape.package.facade import (15from landscape.package.facade import (
22 DependencyError, TransactionError, SmartError, has_new_enough_apt)16 DependencyError, TransactionError)
23from landscape.package.changer import (17from landscape.package.changer import (
24 PackageChangerConfiguration, ChangePackagesResult)18 PackageChangerConfiguration, ChangePackagesResult)
25from landscape.tests.mocker import ANY19from landscape.tests.mocker import ANY
26from landscape.tests.helpers import (20from landscape.tests.helpers import (
27 LandscapeTest, BrokerServiceHelper)21 LandscapeTest, BrokerServiceHelper)
28from landscape.package.tests.helpers import (22from landscape.package.tests.helpers import (
29 SmartFacadeHelper, HASH1, HASH2, HASH3, PKGDEB1, PKGDEB2, PKGNAME2,23 HASH1, HASH2, HASH3, PKGDEB1, PKGDEB2,
30 AptFacadeHelper, SimpleRepositoryHelper)24 AptFacadeHelper, SimpleRepositoryHelper)
31from landscape.manager.manager import FAILED, SUCCEEDED25from landscape.manager.manager import FAILED
3226
3327
34class PackageChangerTestMixin(object):28class AptPackageChangerTest(LandscapeTest):
29
30 helpers = [AptFacadeHelper, SimpleRepositoryHelper, BrokerServiceHelper]
31
32 def setUp(self):
33
34 def set_up(ignored):
35
36 self.store = PackageStore(self.makeFile())
37 self.config = PackageChangerConfiguration()
38 self.config.data_path = self.makeDir()
39 os.mkdir(self.config.package_directory)
40 os.mkdir(self.config.binaries_path)
41 touch_file(self.config.update_stamp_filename)
42 self.changer = PackageChanger(
43 self.store, self.facade, self.remote, self.config)
44 service = self.broker_service
45 service.message_store.set_accepted_types(["change-packages-result",
46 "operation-result"])
47
48 result = super(AptPackageChangerTest, self).setUp()
49 return result.addCallback(set_up)
50
51 def set_pkg1_installed(self):
52 """Return the hash of a package that is installed."""
53 self._add_system_package("foo")
54 self.facade.reload_channels()
55 [foo] = self.facade.get_packages_by_name("foo")
56 return self.facade.get_package_hash(foo)
57
58 def set_pkg2_satisfied(self):
59 """Return the hash of a package that can be installed."""
60 self._add_package_to_deb_dir(self.repository_dir, "bar")
61 self.facade.reload_channels()
62 [bar] = self.facade.get_packages_by_name("bar")
63 return self.facade.get_package_hash(bar)
64
65 def set_pkg1_and_pkg2_satisfied(self):
66 """Make a package depend on another package.
67
68 Return the hashes of the two packages.
69 """
70 self._add_package_to_deb_dir(
71 self.repository_dir, "foo", control_fields={"Depends": "bar"})
72 self._add_package_to_deb_dir(self.repository_dir, "bar")
73 self.facade.reload_channels()
74 [foo] = self.facade.get_packages_by_name("foo")
75 [bar] = self.facade.get_packages_by_name("bar")
76 return (
77 self.facade.get_package_hash(foo),
78 self.facade.get_package_hash(bar))
79
80 def set_pkg2_upgrades_pkg1(self):
81 """Make it so that one package upgrades another.
82
83 Return the hashes of the two packages.
84 """
85 self._add_system_package("foo", version="1.0")
86 self._add_package_to_deb_dir(self.repository_dir, "foo", version="2.0")
87 self.facade.reload_channels()
88 foo_1, foo_2 = sorted(self.facade.get_packages_by_name("foo"))
89 return (
90 self.facade.get_package_hash(foo_1),
91 self.facade.get_package_hash(foo_2))
92
93 def remove_pkg2(self):
94 """Remove package name2 from its repository."""
95 packages_file = os.path.join(self.repository_dir, "Packages")
96 packages_contents = read_file(packages_file)
97 packages_contents = "\n\n".join(
98 [stanza for stanza in packages_contents.split("\n\n")
99 if "Package: name2" not in stanza])
100 create_file(packages_file, packages_contents)
101
102 def get_transaction_error_message(self):
103 """Return part of the apt transaction error message."""
104 return "Unable to correct problems"
105
106 def get_binaries_channels(self, binaries_path):
107 """Return the channels that will be used for the binaries."""
108 return [{"baseurl": "file://%s" % binaries_path,
109 "components": "",
110 "distribution": "./",
111 "type": "deb"}]
112
113 def get_package_name(self, version):
114 """Return the name of the package."""
115 return version.package.name
116
35117
36 def disable_clear_channels(self):118 def disable_clear_channels(self):
37 """Disable clear_channels(), so that it doesn't remove test setup.119 """Disable clear_channels(), so that it doesn't remove test setup.
@@ -441,7 +523,7 @@
441523
442 def test_tasks_are_isolated_cache(self):524 def test_tasks_are_isolated_cache(self):
443 """525 """
444 The package (apt/smart) cache should be reset between task runs.526 The package (APT) cache should be reset between task runs.
445 In this test, we try to run two different operations, first527 In this test, we try to run two different operations, first
446 installing package 2, then removing package 1. Both tasks will528 installing package 2, then removing package 1. Both tasks will
447 fail for lack of superuser privileges. If the package cache529 fail for lack of superuser privileges. If the package cache
@@ -533,7 +615,7 @@
533 """615 """
534 Besides asking for individual changes, the server may also request616 Besides asking for individual changes, the server may also request
535 the client to perform a global upgrade. This would be the equivalent617 the client to perform a global upgrade. This would be the equivalent
536 of a "smart upgrade" command being executed in the command line.618 of a "apt-get upgrade" command being executed in the command line.
537 """619 """
538 hash1, hash2 = self.set_pkg2_upgrades_pkg1()620 hash1, hash2 = self.set_pkg2_upgrades_pkg1()
539 self.store.set_hash_ids({hash1: 1, hash2: 2})621 self.store.set_hash_ids({hash1: 1, hash2: 2})
@@ -638,8 +720,7 @@
638 """720 """
639 After the package changer has run, we want the package-reporter to run721 After the package changer has run, we want the package-reporter to run
640 to report the recent changes. If we're running as root, we want to722 to report the recent changes. If we're running as root, we want to
641 change to the "landscape" user and "landscape" group. We also want to723 change to the "landscape" user and "landscape" group.
642 deinitialize Smart to let the reporter run smart-update cleanly.
643 """724 """
644725
645 # We are running as root726 # We are running as root
@@ -647,13 +728,8 @@
647 getuid_mock()728 getuid_mock()
648 self.mocker.result(0)729 self.mocker.result(0)
649730
650 # The order matters (first smart then gid and finally uid)
651 self.mocker.order()731 self.mocker.order()
652732
653 # Deinitialize smart
654 facade_mock = self.mocker.patch(self.facade)
655 facade_mock.deinit()
656
657 # We want to return a known gid733 # We want to return a known gid
658 grnam_mock = self.mocker.replace("grp.getgrnam")734 grnam_mock = self.mocker.replace("grp.getgrnam")
659 grnam_mock("landscape")735 grnam_mock("landscape")
@@ -810,27 +886,6 @@
810 "type": "change-packages-result"}])886 "type": "change-packages-result"}])
811 return result.addCallback(got_result)887 return result.addCallback(got_result)
812888
813 def test_smart_error_with_unicode_data(self):
814 self.store.set_hash_ids({HASH1: 1})
815 self.store.add_task("changer",
816 {"type": "change-packages", "install": [1],
817 "operation-id": 123})
818
819 def raise_error(self):
820 raise SmartError(u"áéíóú")
821 self.replace_perform_changes(raise_error)
822 self.disable_clear_channels()
823
824 result = self.changer.handle_tasks()
825
826 def got_result(result):
827 self.assertMessages(self.get_pending_messages(),
828 [{"operation-id": 123,
829 "result-code": 100,
830 "result-text": u"áéíóú",
831 "type": "change-packages-result"}])
832 return result.addCallback(got_result)
833
834 def test_update_stamp_exists(self):889 def test_update_stamp_exists(self):
835 """890 """
836 L{PackageChanger.update_exists} returns C{True} if the891 L{PackageChanger.update_exists} returns C{True} if the
@@ -892,360 +947,6 @@
892 self.assertFalse(os.path.exists(existing_deb_path))947 self.assertFalse(os.path.exists(existing_deb_path))
893948
894949
895class SmartPackageChangerTest(LandscapeTest, PackageChangerTestMixin):
896
897 helpers = [SmartFacadeHelper, BrokerServiceHelper]
898
899 def setUp(self):
900
901 def set_up(ignored):
902
903 self.store = PackageStore(self.makeFile())
904 self.config = PackageChangerConfiguration()
905 self.config.data_path = self.makeDir()
906 os.mkdir(self.config.package_directory)
907 os.mkdir(self.config.binaries_path)
908 touch_file(self.config.update_stamp_filename)
909 self.changer = PackageChanger(
910 self.store, self.facade, self.remote, self.config)
911 service = self.broker_service
912 service.message_store.set_accepted_types(["change-packages-result",
913 "operation-result"])
914
915 result = super(SmartPackageChangerTest, self).setUp()
916 return result.addCallback(set_up)
917
918 def set_pkg1_installed(self):
919 previous = self.Facade.channels_reloaded
920
921 def callback(self):
922 previous(self)
923 self.get_packages_by_name("name1")[0].installed = True
924 self.Facade.channels_reloaded = callback
925 return HASH1
926
927 def set_pkg2_upgrades_pkg1(self):
928 previous = self.Facade.channels_reloaded
929
930 def callback(self):
931 from smart.backends.deb.base import DebUpgrades
932 previous(self)
933 [pkg2] = self.get_packages_by_name("name2")
934 pkg2.upgrades += (DebUpgrades("name1", "=", "version1-release1"),)
935 self.reload_cache() # Relink relations.
936 self.Facade.channels_reloaded = callback
937 self.set_pkg2_satisfied()
938 self.set_pkg1_installed()
939 return HASH1, HASH2
940
941 def set_pkg2_satisfied(self):
942 previous = self.Facade.channels_reloaded
943
944 def callback(self):
945 previous(self)
946 [pkg2] = self.get_packages_by_name("name2")
947 pkg2.requires = ()
948 self.reload_cache() # Relink relations.
949 self.Facade.channels_reloaded = callback
950 return HASH2
951
952 def set_pkg1_and_pkg2_satisfied(self):
953 previous = self.Facade.channels_reloaded
954
955 def callback(self):
956 previous(self)
957
958 provide1 = Provides("prerequirename1", "prerequireversion1")
959 provide2 = Provides("requirename1", "requireversion1")
960 [pkg2] = self.get_packages_by_name("name2")
961 pkg2.provides += (provide1, provide2)
962
963 provide1 = Provides("prerequirename2", "prerequireversion2")
964 provide2 = Provides("requirename2", "requireversion2")
965 [pkg1] = self.get_packages_by_name("name1")
966 pkg1.provides += (provide1, provide2)
967
968 # Ask Smart to reprocess relationships.
969 self.reload_cache()
970 self.Facade.channels_reloaded = callback
971 return HASH1, HASH2
972
973 def remove_pkg2(self):
974 os.remove(os.path.join(self.repository_dir, PKGNAME2))
975
976 def get_transaction_error_message(self):
977 return "requirename1 = requireversion1"
978
979 def get_binaries_channels(self, binaries_path):
980 return {binaries_path: {"type": "deb-dir",
981 "path": binaries_path}}
982
983 def get_package_name(self, package):
984 return package.name
985
986 def test_change_package_locks(self):
987 """
988 The L{PackageChanger.handle_tasks} method appropriately creates and
989 deletes package locks as requested by the C{change-package-locks}
990 message.
991 """
992 self.facade.set_package_lock("bar")
993 self.store.add_task("changer", {"type": "change-package-locks",
994 "create": [("foo", ">=", "1.0")],
995 "delete": [("bar", None, None)],
996 "operation-id": 123})
997
998 def assert_result(result):
999 self.facade.deinit()
1000 self.assertEqual(self.facade.get_package_locks(),
1001 [("foo", ">=", "1.0")])
1002 self.assertIn("Queuing message with change package locks results "
1003 "to exchange urgently.", self.logfile.getvalue())
1004 self.assertMessages(self.get_pending_messages(),
1005 [{"type": "operation-result",
1006 "operation-id": 123,
1007 "status": SUCCEEDED,
1008 "result-text": "Package locks successfully"
1009 " changed.",
1010 "result-code": 0}])
1011
1012 result = self.changer.handle_tasks()
1013 return result.addCallback(assert_result)
1014
1015 def test_change_package_locks_create_with_already_existing(self):
1016 """
1017 The L{PackageChanger.handle_tasks} method gracefully handles requests
1018 for creating package locks that already exist.
1019 """
1020 self.facade.set_package_lock("foo")
1021 self.store.add_task("changer", {"type": "change-package-locks",
1022 "create": [("foo", None, None)],
1023 "operation-id": 123})
1024
1025 def assert_result(result):
1026 self.facade.deinit()
1027 self.assertEqual(self.facade.get_package_locks(),
1028 [("foo", "", "")])
1029 self.assertMessages(self.get_pending_messages(),
1030 [{"type": "operation-result",
1031 "operation-id": 123,
1032 "status": SUCCEEDED,
1033 "result-text": "Package locks successfully"
1034 " changed.",
1035 "result-code": 0}])
1036
1037 result = self.changer.handle_tasks()
1038 return result.addCallback(assert_result)
1039
1040 def test_change_package_locks_delete_without_already_existing(self):
1041 """
1042 The L{PackageChanger.handle_tasks} method gracefully handles requests
1043 for deleting package locks that don't exist.
1044 """
1045 self.store.add_task("changer", {"type": "change-package-locks",
1046 "delete": [("foo", ">=", "1.0")],
1047 "operation-id": 123})
1048
1049 def assert_result(result):
1050 self.facade.deinit()
1051 self.assertEqual(self.facade.get_package_locks(), [])
1052 self.assertMessages(self.get_pending_messages(),
1053 [{"type": "operation-result",
1054 "operation-id": 123,
1055 "status": SUCCEEDED,
1056 "result-text": "Package locks successfully"
1057 " changed.",
1058 "result-code": 0}])
1059
1060 result = self.changer.handle_tasks()
1061 return result.addCallback(assert_result)
1062
1063 def test_dpkg_error(self):
1064 """
1065 Verify that errors emitted by dpkg are correctly reported to
1066 the server as problems.
1067
1068 This test is to make sure that Smart reports the problem
1069 correctly. It doesn't make sense for AptFacade, since there we
1070 don't call dpkg.
1071 """
1072 self.log_helper.ignore_errors(".*dpkg")
1073
1074 installed_hash = self.set_pkg1_installed()
1075 self.store.set_hash_ids({installed_hash: 1})
1076 self.store.add_task("changer",
1077 {"type": "change-packages", "remove": [1],
1078 "operation-id": 123})
1079
1080 result = self.changer.handle_tasks()
1081
1082 def got_result(result):
1083 messages = self.get_pending_messages()
1084 self.assertEqual(len(messages), 1, "Too many messages")
1085 message = messages[0]
1086 self.assertEqual(message["operation-id"], 123)
1087 self.assertEqual(message["result-code"], 100)
1088 self.assertEqual(message["type"], "change-packages-result")
1089 text = message["result-text"]
1090 # We can't test the actual content of the message because the dpkg
1091 # error can be localized
1092 self.assertIn("\n[remove] name1_version1-release1\ndpkg: ", text)
1093 self.assertIn("ERROR", text)
1094 self.assertIn("(2)", text)
1095 return result.addCallback(got_result)
1096
1097 def test_change_package_holds(self):
1098 """
1099 If C{SmartFacade} is used, the L{PackageChanger.handle_tasks}
1100 method fails the activity, since it can't add or remove dpkg holds.
1101 """
1102 self.facade.reload_channels()
1103 self.store.add_task("changer", {"type": "change-package-holds",
1104 "create": [1],
1105 "delete": [2],
1106 "operation-id": 123})
1107
1108 def assert_result(result):
1109 self.assertIn("Queuing message with change package holds results "
1110 "to exchange urgently.", self.logfile.getvalue())
1111 self.assertMessages(
1112 self.get_pending_messages(),
1113 [{"type": "operation-result",
1114 "operation-id": 123,
1115 "status": FAILED,
1116 "result-text": "This client doesn't support package holds.",
1117 "result-code": 1}])
1118
1119 result = self.changer.handle_tasks()
1120 return result.addCallback(assert_result)
1121
1122 def test_global_upgrade(self):
1123 """
1124 Besides asking for individual changes, the server may also request
1125 the client to perform a global upgrade. This would be the equivalent
1126 of a "smart upgrade" command being executed in the command line.
1127
1128 This test should be run for both C{AptFacade} and
1129 C{SmartFacade}, but due to the smart test setting up that two
1130 packages with different names upgrade each other, the message
1131 doesn't correctly report that the old version should be
1132 uninstalled. The test is still useful, since it shows that the
1133 message contains the changes that smart says are needed.
1134
1135 Making the test totally correct is a lot of work, that is not
1136 worth doing, since we're removing smart soon.
1137 """
1138 hash1, hash2 = self.set_pkg2_upgrades_pkg1()
1139 self.store.set_hash_ids({hash1: 1, hash2: 2})
1140
1141 self.store.add_task("changer",
1142 {"type": "change-packages", "upgrade-all": True,
1143 "operation-id": 123})
1144
1145 result = self.changer.handle_tasks()
1146
1147 def got_result(result):
1148 self.assertMessages(self.get_pending_messages(),
1149 [{"operation-id": 123,
1150 "must-install": [2],
1151 "result-code": 101,
1152 "type": "change-packages-result"}])
1153
1154 return result.addCallback(got_result)
1155
1156
1157class AptPackageChangerTest(LandscapeTest, PackageChangerTestMixin):
1158
1159 if not has_new_enough_apt:
1160 skip = "Can't use AptFacade on hardy"
1161
1162 helpers = [AptFacadeHelper, SimpleRepositoryHelper, BrokerServiceHelper]
1163
1164 def setUp(self):
1165
1166 def set_up(ignored):
1167
1168 self.store = PackageStore(self.makeFile())
1169 self.config = PackageChangerConfiguration()
1170 self.config.data_path = self.makeDir()
1171 os.mkdir(self.config.package_directory)
1172 os.mkdir(self.config.binaries_path)
1173 touch_file(self.config.update_stamp_filename)
1174 self.changer = PackageChanger(
1175 self.store, self.facade, self.remote, self.config)
1176 service = self.broker_service
1177 service.message_store.set_accepted_types(["change-packages-result",
1178 "operation-result"])
1179
1180 result = super(AptPackageChangerTest, self).setUp()
1181 return result.addCallback(set_up)
1182
1183 def set_pkg1_installed(self):
1184 """Return the hash of a package that is installed."""
1185 self._add_system_package("foo")
1186 self.facade.reload_channels()
1187 [foo] = self.facade.get_packages_by_name("foo")
1188 return self.facade.get_package_hash(foo)
1189
1190 def set_pkg2_satisfied(self):
1191 """Return the hash of a package that can be installed."""
1192 self._add_package_to_deb_dir(self.repository_dir, "bar")
1193 self.facade.reload_channels()
1194 [bar] = self.facade.get_packages_by_name("bar")
1195 return self.facade.get_package_hash(bar)
1196
1197 def set_pkg1_and_pkg2_satisfied(self):
1198 """Make a package depend on another package.
1199
1200 Return the hashes of the two packages.
1201 """
1202 self._add_package_to_deb_dir(
1203 self.repository_dir, "foo", control_fields={"Depends": "bar"})
1204 self._add_package_to_deb_dir(self.repository_dir, "bar")
1205 self.facade.reload_channels()
1206 [foo] = self.facade.get_packages_by_name("foo")
1207 [bar] = self.facade.get_packages_by_name("bar")
1208 return (
1209 self.facade.get_package_hash(foo),
1210 self.facade.get_package_hash(bar))
1211
1212 def set_pkg2_upgrades_pkg1(self):
1213 """Make it so that one package upgrades another.
1214
1215 Return the hashes of the two packages.
1216 """
1217 self._add_system_package("foo", version="1.0")
1218 self._add_package_to_deb_dir(self.repository_dir, "foo", version="2.0")
1219 self.facade.reload_channels()
1220 foo_1, foo_2 = sorted(self.facade.get_packages_by_name("foo"))
1221 return (
1222 self.facade.get_package_hash(foo_1),
1223 self.facade.get_package_hash(foo_2))
1224
1225 def remove_pkg2(self):
1226 """Remove package name2 from its repository."""
1227 packages_file = os.path.join(self.repository_dir, "Packages")
1228 packages_contents = read_file(packages_file)
1229 packages_contents = "\n\n".join(
1230 [stanza for stanza in packages_contents.split("\n\n")
1231 if "Package: name2" not in stanza])
1232 create_file(packages_file, packages_contents)
1233
1234 def get_transaction_error_message(self):
1235 """Return part of the apt transaction error message."""
1236 return "Unable to correct problems"
1237
1238 def get_binaries_channels(self, binaries_path):
1239 """Return the channels that will be used for the binaries."""
1240 return [{"baseurl": "file://%s" % binaries_path,
1241 "components": "",
1242 "distribution": "./",
1243 "type": "deb"}]
1244
1245 def get_package_name(self, version):
1246 """Return the name of the package."""
1247 return version.package.name
1248
1249 def test_binaries_available_in_cache(self):950 def test_binaries_available_in_cache(self):
1250 """951 """
1251 If binaries are included in the changes-packages message, those952 If binaries are included in the changes-packages message, those
@@ -1275,7 +976,7 @@
1275 def test_change_package_holds(self):976 def test_change_package_holds(self):
1276 """977 """
1277 The L{PackageChanger.handle_tasks} method appropriately creates and978 The L{PackageChanger.handle_tasks} method appropriately creates and
1278 deletes package holds as requested by the C{change-package-holds}979 deletes package holds as requested by the C{change-packages}
1279 message.980 message.
1280 """981 """
1281 self._add_system_package("foo")982 self._add_system_package("foo")
@@ -1292,23 +993,22 @@
1292 old_mtime = time.time() - 10993 old_mtime = time.time() - 10
1293 os.utime(self.facade._dpkg_status, (old_mtime, old_mtime))994 os.utime(self.facade._dpkg_status, (old_mtime, old_mtime))
1294 self.facade.reload_channels()995 self.facade.reload_channels()
1295 self.store.add_task("changer", {"type": "change-package-holds",996 self.store.add_task("changer", {"type": "change-packages",
1296 "create": [foo.package.id],997 "hold": [foo.package.id],
1297 "delete": [bar.package.id],998 "remove-hold": [bar.package.id],
1298 "operation-id": 123})999 "operation-id": 123})
12991000
1300 def assert_result(result):1001 def assert_result(result):
1301 self.facade.reload_channels()1002 self.facade.reload_channels()
1302 self.assertEqual(["foo"], self.facade.get_package_holds())1003 self.assertEqual(["foo"], self.facade.get_package_holds())
1303 self.assertIn("Queuing message with change package holds results "1004 self.assertIn("Queuing response with change package results "
1304 "to exchange urgently.", self.logfile.getvalue())1005 "to exchange urgently.", self.logfile.getvalue())
1305 self.assertMessages(1006 self.assertMessages(
1306 self.get_pending_messages(),1007 self.get_pending_messages(),
1307 [{"type": "operation-result",1008 [{"type": "change-packages-result",
1308 "operation-id": 123,1009 "operation-id": 123,
1309 "status": SUCCEEDED,
1310 "result-text": "Package holds successfully changed.",1010 "result-text": "Package holds successfully changed.",
1311 "result-code": 0}])1011 "result-code": 1}])
13121012
1313 result = self.changer.handle_tasks()1013 result = self.changer.handle_tasks()
1314 return result.addCallback(assert_result)1014 return result.addCallback(assert_result)
@@ -1316,7 +1016,7 @@
1316 def test_create_package_holds_with_identical_version(self):1016 def test_create_package_holds_with_identical_version(self):
1317 """1017 """
1318 The L{PackageChanger.handle_tasks} method appropriately creates1018 The L{PackageChanger.handle_tasks} method appropriately creates
1319 holds as requested by the C{change-package-holds} message even1019 holds as requested by the C{change-packages} message even
1320 when versions from two different packages are the same.1020 when versions from two different packages are the same.
1321 """1021 """
1322 self._add_system_package("foo", version="1.1")1022 self._add_system_package("foo", version="1.1")
@@ -1327,12 +1027,13 @@
1327 [foo] = self.facade.get_packages_by_name("foo")1027 [foo] = self.facade.get_packages_by_name("foo")
1328 [bar] = self.facade.get_packages_by_name("bar")1028 [bar] = self.facade.get_packages_by_name("bar")
1329 self.facade.reload_channels()1029 self.facade.reload_channels()
1330 self.store.add_task("changer", {"type": "change-package-holds",1030 self.store.add_task("changer", {"type": "change-packages",
1331 "create": [foo.package.id,1031 "hold": [foo.package.id,
1332 bar.package.id],1032 bar.package.id],
1333 "operation-id": 123})1033 "operation-id": 123})
13341034
1335 def assert_result(result):1035 def assert_result(result):
1036 self.facade.reload_channels()
1336 self.assertEqual(["foo", "bar"], self.facade.get_package_holds())1037 self.assertEqual(["foo", "bar"], self.facade.get_package_holds())
13371038
1338 result = self.changer.handle_tasks()1039 result = self.changer.handle_tasks()
@@ -1341,7 +1042,7 @@
1341 def test_delete_package_holds_with_identical_version(self):1042 def test_delete_package_holds_with_identical_version(self):
1342 """1043 """
1343 The L{PackageChanger.handle_tasks} method appropriately deletes1044 The L{PackageChanger.handle_tasks} method appropriately deletes
1344 holds as requested by the C{change-package-holds} message even1045 holds as requested by the C{change-packages} message even
1345 when versions from two different packages are the same.1046 when versions from two different packages are the same.
1346 """1047 """
1347 self._add_system_package("foo", version="1.1")1048 self._add_system_package("foo", version="1.1")
@@ -1354,12 +1055,13 @@
1354 self.facade.set_package_hold(foo)1055 self.facade.set_package_hold(foo)
1355 self.facade.set_package_hold(bar)1056 self.facade.set_package_hold(bar)
1356 self.facade.reload_channels()1057 self.facade.reload_channels()
1357 self.store.add_task("changer", {"type": "change-package-holds",1058 self.store.add_task("changer", {"type": "change-packages",
1358 "delete": [foo.package.id,1059 "remove-hold": [foo.package.id,
1359 bar.package.id],1060 bar.package.id],
1360 "operation-id": 123})1061 "operation-id": 123})
13611062
1362 def assert_result(result):1063 def assert_result(result):
1064 self.facade.reload_channels()
1363 self.assertEqual([], self.facade.get_package_holds())1065 self.assertEqual([], self.facade.get_package_holds())
13641066
1365 result = self.changer.handle_tasks()1067 result = self.changer.handle_tasks()
@@ -1367,7 +1069,7 @@
13671069
1368 def test_change_package_holds_create_already_held(self):1070 def test_change_package_holds_create_already_held(self):
1369 """1071 """
1370 If the C{change-package-holds} message requests to add holds for1072 If the C{change-packages} message requests to add holds for
1371 packages that are already held, the activity succeeds, since the1073 packages that are already held, the activity succeeds, since the
1372 end result is that the requested package holds are there.1074 end result is that the requested package holds are there.
1373 """1075 """
@@ -1377,29 +1079,28 @@
1377 [foo] = self.facade.get_packages_by_name("foo")1079 [foo] = self.facade.get_packages_by_name("foo")
1378 self.facade.set_package_hold(foo)1080 self.facade.set_package_hold(foo)
1379 self.facade.reload_channels()1081 self.facade.reload_channels()
1380 self.store.add_task("changer", {"type": "change-package-holds",1082 self.store.add_task("changer", {"type": "change-packages",
1381 "create": [foo.package.id],1083 "hold": [foo.package.id],
1382 "operation-id": 123})1084 "operation-id": 123})
13831085
1384 def assert_result(result):1086 def assert_result(result):
1385 self.facade.reload_channels()1087 self.facade.reload_channels()
1386 self.assertEqual(["foo"], self.facade.get_package_holds())1088 self.assertEqual(["foo"], self.facade.get_package_holds())
1387 self.assertIn("Queuing message with change package holds results "1089 self.assertIn("Queuing response with change package results "
1388 "to exchange urgently.", self.logfile.getvalue())1090 "to exchange urgently.", self.logfile.getvalue())
1389 self.assertMessages(1091 self.assertMessages(
1390 self.get_pending_messages(),1092 self.get_pending_messages(),
1391 [{"type": "operation-result",1093 [{"type": "change-packages-result",
1392 "operation-id": 123,1094 "operation-id": 123,
1393 "status": SUCCEEDED,
1394 "result-text": "Package holds successfully changed.",1095 "result-text": "Package holds successfully changed.",
1395 "result-code": 0}])1096 "result-code": 1}])
13961097
1397 result = self.changer.handle_tasks()1098 result = self.changer.handle_tasks()
1398 return result.addCallback(assert_result)1099 return result.addCallback(assert_result)
13991100
1400 def test_change_package_holds_create_other_version_installed(self):1101 def test_change_package_holds_create_other_version_installed(self):
1401 """1102 """
1402 If the C{change-package-holds} message requests to add holds for1103 If the C{change-packages} message requests to add holds for
1403 packages that have a different version installed than the one1104 packages that have a different version installed than the one
1404 being requested to hold, the activity fails.1105 being requested to hold, the activity fails.
14051106
@@ -1420,30 +1121,29 @@
1420 self.facade.get_package_hash(bar1): 3,1121 self.facade.get_package_hash(bar1): 3,
1421 self.facade.get_package_hash(bar2): 4})1122 self.facade.get_package_hash(bar2): 4})
1422 self.facade.reload_channels()1123 self.facade.reload_channels()
1423 self.store.add_task("changer", {"type": "change-package-holds",1124 self.store.add_task("changer", {"type": "change-packages",
1424 "create": [2, 3],1125 "hold": [2, 3],
1425 "operation-id": 123})1126 "operation-id": 123})
14261127
1427 def assert_result(result):1128 def assert_result(result):
1428 self.facade.reload_channels()1129 self.facade.reload_channels()
1429 self.assertEqual([], self.facade.get_package_holds())1130 self.assertEqual([], self.facade.get_package_holds())
1430 self.assertIn("Queuing message with change package holds results "1131 self.assertIn("Queuing response with change package results "
1431 "to exchange urgently.", self.logfile.getvalue())1132 "to exchange urgently.", self.logfile.getvalue())
1432 self.assertMessages(1133 self.assertMessages(
1433 self.get_pending_messages(),1134 self.get_pending_messages(),
1434 [{"type": "operation-result",1135 [{"type": "change-packages-result",
1435 "operation-id": 123,1136 "operation-id": 123,
1436 "status": FAILED,1137 "result-text": "Cannot perform the changes, since the" +
1437 "result-text": "Package holds not changed, since the" +1138 " following packages are not installed: foo",
1438 " following packages are not installed: 2",1139 "result-code": 100}])
1439 "result-code": 1}])
14401140
1441 result = self.changer.handle_tasks()1141 result = self.changer.handle_tasks()
1442 return result.addCallback(assert_result)1142 return result.addCallback(assert_result)
14431143
1444 def test_change_package_holds_create_not_installed(self):1144 def test_change_package_holds_create_not_installed(self):
1445 """1145 """
1446 If the C{change-package-holds} message requests to add holds for1146 If the C{change-packages} message requests to add holds for
1447 packages that aren't installed, the whole activity is failed. If1147 packages that aren't installed, the whole activity is failed. If
1448 multiple holds are specified, those won't be added. There's no1148 multiple holds are specified, those won't be added. There's no
1449 difference between a package that is available in some1149 difference between a package that is available in some
@@ -1460,62 +1160,71 @@
1460 [foo] = self.facade.get_packages_by_name("foo")1160 [foo] = self.facade.get_packages_by_name("foo")
1461 [bar] = self.facade.get_packages_by_name("bar")1161 [bar] = self.facade.get_packages_by_name("bar")
1462 [baz] = self.facade.get_packages_by_name("baz")1162 [baz] = self.facade.get_packages_by_name("baz")
1463 self.store.add_task("changer", {"type": "change-package-holds",1163 self.store.add_task("changer", {"type": "change-packages",
1464 "create": [foo.package.id,1164 "hold": [foo.package.id,
1465 bar.package.id,1165 bar.package.id,
1466 baz.package.id],1166 baz.package.id],
1467 "operation-id": 123})1167 "operation-id": 123})
14681168
1469 def assert_result(result):1169 def assert_result(result):
1470 self.facade.reload_channels()1170 self.facade.reload_channels()
1471 self.assertEqual([], self.facade.get_package_holds())1171 self.assertEqual([], self.facade.get_package_holds())
1472 self.assertIn("Queuing message with change package holds results "1172 self.assertIn("Queuing response with change package results "
1473 "to exchange urgently.", self.logfile.getvalue())1173 "to exchange urgently.", self.logfile.getvalue())
1474 self.assertMessages(1174 self.assertMessages(
1475 self.get_pending_messages(),1175 self.get_pending_messages(),
1476 [{"type": "operation-result",1176 [{"type": "change-packages-result",
1477 "operation-id": 123,1177 "operation-id": 123,
1478 "status": FAILED,1178 "result-text": "Cannot perform the changes, since the "
1479 "result-text": "Package holds not changed, since the "
1480 "following packages are not installed: "1179 "following packages are not installed: "
1481 "%s, %s" % tuple(sorted([bar.package.id,1180 "%s, %s" % tuple(sorted([bar.package.name,
1482 baz.package.id])),1181 baz.package.name])),
1483 "result-code": 1}])1182 "result-code": 100}])
14841183
1485 result = self.changer.handle_tasks()1184 result = self.changer.handle_tasks()
1486 return result.addCallback(assert_result)1185 return result.addCallback(assert_result)
14871186
1488 def test_change_package_holds_create_unknown_hash(self):1187 def test_change_package_holds_create_unknown_hash(self):
1489 """1188 """
1490 If the C{change-package-holds} message requests to add holds for1189 If the C{change-packages} message requests to add holds for
1491 packages that the client doesn't know about, it's being treated1190 packages that the client doesn't know about results in a not yet
1492 as the packages not being installed.1191 synchronized message and a failure of the operation.
1493 """1192 """
1494 self.facade.reload_channels()1193
1495 self.store.add_task("changer", {"type": "change-package-holds",1194 self.store.add_task("changer",
1496 "create": [1],1195 {"type": "change-packages",
1497 "operation-id": 123})1196 "hold": [123],
14981197 "operation-id": 123})
1499 def assert_result(result):1198
1500 self.facade.reload_channels()1199 time_mock = self.mocker.replace("time.time")
1501 self.assertEqual([], self.facade.get_package_holds())1200 time_mock()
1502 self.assertIn("Queuing message with change package holds results "1201 self.mocker.result(time.time() + UNKNOWN_PACKAGE_DATA_TIMEOUT)
1503 "to exchange urgently.", self.logfile.getvalue())1202 self.mocker.count(1, None)
1504 self.assertMessages(1203 self.mocker.replay()
1505 self.get_pending_messages(),1204
1506 [{"type": "operation-result",1205 try:
1507 "operation-id": 123,1206 result = self.changer.handle_tasks()
1508 "status": FAILED,1207 self.mocker.verify()
1509 "result-text": "Package holds not changed, since the" +1208 finally:
1510 " following packages are not installed: 1",1209 # Reset it earlier so that Twisted has the true time function.
1511 "result-code": 1}])1210 self.mocker.reset()
15121211
1513 result = self.changer.handle_tasks()1212 self.assertIn("Package data not yet synchronized with server (123)",
1514 return result.addCallback(assert_result)1213 self.logfile.getvalue())
1214
1215 def got_result(result):
1216 message = {"type": "change-packages-result",
1217 "operation-id": 123,
1218 "result-code": 100,
1219 "result-text": "Package data has changed. "
1220 "Please retry the operation."}
1221 self.assertMessages(self.get_pending_messages(), [message])
1222 self.assertEqual(self.store.get_next_task("changer"), None)
1223 return result.addCallback(got_result)
15151224
1516 def test_change_package_holds_delete_not_held(self):1225 def test_change_package_holds_delete_not_held(self):
1517 """1226 """
1518 If the C{change-package-holds} message requests to remove holds1227 If the C{change-packages} message requests to remove holds
1519 for packages that aren't held, the activity succeeds if the1228 for packages that aren't held, the activity succeeds if the
1520 right version is installed, since the end result is that the1229 right version is installed, since the end result is that the
1521 hold is removed.1230 hold is removed.
@@ -1524,29 +1233,28 @@
1524 self.facade.reload_channels()1233 self.facade.reload_channels()
1525 self._hash_packages_by_name(self.facade, self.store, "foo")1234 self._hash_packages_by_name(self.facade, self.store, "foo")
1526 [foo] = self.facade.get_packages_by_name("foo")1235 [foo] = self.facade.get_packages_by_name("foo")
1527 self.store.add_task("changer", {"type": "change-package-holds",1236 self.store.add_task("changer", {"type": "change-packages",
1528 "delete": [foo.package.id],1237 "remove-hold": [foo.package.id],
1529 "operation-id": 123})1238 "operation-id": 123})
15301239
1531 def assert_result(result):1240 def assert_result(result):
1532 self.facade.reload_channels()1241 self.facade.reload_channels()
1533 self.assertEqual([], self.facade.get_package_holds())1242 self.assertEqual([], self.facade.get_package_holds())
1534 self.assertIn("Queuing message with change package holds results "1243 self.assertIn("Queuing response with change package results "
1535 "to exchange urgently.", self.logfile.getvalue())1244 "to exchange urgently.", self.logfile.getvalue())
1536 self.assertMessages(1245 self.assertMessages(
1537 self.get_pending_messages(),1246 self.get_pending_messages(),
1538 [{"type": "operation-result",1247 [{"type": "change-packages-result",
1539 "operation-id": 123,1248 "operation-id": 123,
1540 "status": SUCCEEDED,
1541 "result-text": "Package holds successfully changed.",1249 "result-text": "Package holds successfully changed.",
1542 "result-code": 0}])1250 "result-code": 1}])
15431251
1544 result = self.changer.handle_tasks()1252 result = self.changer.handle_tasks()
1545 return result.addCallback(assert_result)1253 return result.addCallback(assert_result)
15461254
1547 def test_change_package_holds_delete_different_version_held(self):1255 def test_change_package_holds_delete_different_version_held(self):
1548 """1256 """
1549 If the C{change-package-holds} message requests to remove holds1257 If the C{change-packages} message requests to remove holds
1550 for packages that aren't held, the activity succeeds if the1258 for packages that aren't held, the activity succeeds if the
1551 right version is installed, since the end result is that the1259 right version is installed, since the end result is that the
1552 hold is removed.1260 hold is removed.
@@ -1556,60 +1264,34 @@
1556 self.repository_dir, "foo", version="2.0")1264 self.repository_dir, "foo", version="2.0")
1557 self.facade.reload_channels()1265 self.facade.reload_channels()
1558 [foo1, foo2] = sorted(self.facade.get_packages_by_name("foo"))1266 [foo1, foo2] = sorted(self.facade.get_packages_by_name("foo"))
1559 self.facade.set_package_hold(foo1)
1560 self.store.set_hash_ids({self.facade.get_package_hash(foo1): 1,1267 self.store.set_hash_ids({self.facade.get_package_hash(foo1): 1,
1561 self.facade.get_package_hash(foo2): 2})1268 self.facade.get_package_hash(foo2): 2})
1269 self.facade.mark_install(foo1)
1270 self.facade.mark_hold(foo1)
1271 self.facade.perform_changes()
1562 self.facade.reload_channels()1272 self.facade.reload_channels()
1563 self.store.add_task("changer", {"type": "change-package-holds",1273 self.store.add_task("changer", {"type": "change-packages",
1564 "delete": [2],1274 "remove-hold": [2],
1565 "operation-id": 123})1275 "operation-id": 123})
15661276
1567 def assert_result(result):1277 def assert_result(result):
1568 self.facade.reload_channels()1278 self.facade.reload_channels()
1569 self.assertEqual(["foo"], self.facade.get_package_holds())1279 self.assertEqual(["foo"], self.facade.get_package_holds())
1570 self.assertIn("Queuing message with change package holds results "1280 self.assertIn("Queuing response with change package results "
1571 "to exchange urgently.", self.logfile.getvalue())1281 "to exchange urgently.", self.logfile.getvalue())
1572 self.assertMessages(1282 self.assertMessages(
1573 self.get_pending_messages(),1283 self.get_pending_messages(),
1574 [{"type": "operation-result",1284 [{"type": "change-packages-result",
1575 "operation-id": 123,1285 "operation-id": 123,
1576 "status": SUCCEEDED,1286 "result-text": "Package holds successfully changed.",
1577 "result-text": "Package holds successfully changed.",1287 "result-code": 1}])
1578 "result-code": 0}])
1579
1580 result = self.changer.handle_tasks()
1581 return result.addCallback(assert_result)
1582
1583 def test_change_package_holds_delete_unknown_hash(self):
1584 """
1585 If the C{change-package-holds} message requests to remove holds
1586 for packages that aren't known by the client, the activity
1587 succeeds, since the end result is that the package isn't
1588 held at that version.
1589 """
1590 self.store.add_task("changer", {"type": "change-package-holds",
1591 "delete": [1],
1592 "operation-id": 123})
1593
1594 def assert_result(result):
1595 self.facade.reload_channels()
1596 self.assertEqual([], self.facade.get_package_holds())
1597 self.assertIn("Queuing message with change package holds results "
1598 "to exchange urgently.", self.logfile.getvalue())
1599 self.assertMessages(
1600 self.get_pending_messages(),
1601 [{"type": "operation-result",
1602 "operation-id": 123,
1603 "status": SUCCEEDED,
1604 "result-text": "Package holds successfully changed.",
1605 "result-code": 0}])
16061288
1607 result = self.changer.handle_tasks()1289 result = self.changer.handle_tasks()
1608 return result.addCallback(assert_result)1290 return result.addCallback(assert_result)
16091291
1610 def test_change_package_holds_delete_not_installed(self):1292 def test_change_package_holds_delete_not_installed(self):
1611 """1293 """
1612 If the C{change-package-holds} message requests to remove holds1294 If the C{change-packages} message requests to remove holds
1613 for packages that aren't installed, the activity succeeds, since1295 for packages that aren't installed, the activity succeeds, since
1614 the end result is still that the package isn't held at the1296 the end result is still that the package isn't held at the
1615 requested version.1297 requested version.
@@ -1618,31 +1300,30 @@
1618 self.facade.reload_channels()1300 self.facade.reload_channels()
1619 self._hash_packages_by_name(self.facade, self.store, "foo")1301 self._hash_packages_by_name(self.facade, self.store, "foo")
1620 [foo] = self.facade.get_packages_by_name("foo")1302 [foo] = self.facade.get_packages_by_name("foo")
1621 self.store.add_task("changer", {"type": "change-package-holds",1303 self.store.add_task("changer", {"type": "change-packages",
1622 "delete": [foo.package.id],1304 "remove-hold": [foo.package.id],
1623 "operation-id": 123})1305 "operation-id": 123})
16241306
1625 def assert_result(result):1307 def assert_result(result):
1626 self.facade.reload_channels()1308 self.facade.reload_channels()
1627 self.assertEqual([], self.facade.get_package_holds())1309 self.assertEqual([], self.facade.get_package_holds())
1628 self.assertIn("Queuing message with change package holds results "1310 self.assertIn("Queuing response with change package results "
1629 "to exchange urgently.", self.logfile.getvalue())1311 "to exchange urgently.", self.logfile.getvalue())
1630 self.assertMessages(1312 self.assertMessages(
1631 self.get_pending_messages(),1313 self.get_pending_messages(),
1632 [{"type": "operation-result",1314 [{"type": "change-packages-result",
1633 "operation-id": 123,1315 "operation-id": 123,
1634 "status": SUCCEEDED,
1635 "result-text": "Package holds successfully changed.",1316 "result-text": "Package holds successfully changed.",
1636 "result-code": 0}])1317 "result-code": 1}])
16371318
1638 result = self.changer.handle_tasks()1319 result = self.changer.handle_tasks()
1639 return result.addCallback(assert_result)1320 return result.addCallback(assert_result)
16401321
1641 def test_change_package_locks(self):1322 def test_change_package_locks(self):
1642 """1323 """
1643 If C{AptFacade} is used, the L{PackageChanger.handle_tasks}1324 The L{PackageChanger.handle_tasks} method fails
1644 method fails the activity, since it can't add or remove locks because1325 change-package-locks activities, since it can't add or remove
1645 apt doesn't support this.1326 locks because apt doesn't support this.
1646 """1327 """
1647 self.store.add_task("changer", {"type": "change-package-locks",1328 self.store.add_task("changer", {"type": "change-package-locks",
1648 "create": [("foo", ">=", "1.0")],1329 "create": [("foo", ">=", "1.0")],
16491330
=== modified file 'landscape/package/tests/test_facade.py'
--- landscape/package/tests/test_facade.py 2012-03-21 16:15:49 +0000
+++ landscape/package/tests/test_facade.py 2012-06-04 14:08:28 +0000
@@ -1,40 +1,22 @@
1import time
2import os1import os
3import re
4import sys
5import textwrap2import textwrap
6import tempfile3import tempfile
74
8try:
9 import smart
10 from smart.control import Control
11 from smart.cache import Provides
12 from smart.const import NEVER, ALWAYS
13except ImportError:
14 # Smart is optional if AptFacade is being used.
15 pass
16
17import apt_pkg5import apt_pkg
18from apt.package import Package6from apt.package import Package
19from aptsources.sourceslist import SourcesList7from aptsources.sourceslist import SourcesList
208
21from twisted.internet import reactor
22from twisted.internet.defer import Deferred
23from twisted.internet.utils import getProcessOutputAndValue
24
25from landscape.constants import UBUNTU_PATH9from landscape.constants import UBUNTU_PATH
26from landscape.lib.fs import read_file, create_file10from landscape.lib.fs import read_file, create_file
27from landscape.package import facade as facade_module
28from landscape.package.facade import (11from landscape.package.facade import (
29 TransactionError, DependencyError, ChannelError, SmartError, AptFacade,12 TransactionError, DependencyError, ChannelError, AptFacade)
30 has_new_enough_apt)
3113
32from landscape.tests.mocker import ANY14from landscape.tests.mocker import ANY
33from landscape.tests.helpers import LandscapeTest, EnvironSaverHelper15from landscape.tests.helpers import LandscapeTest, EnvironSaverHelper
34from landscape.package.tests.helpers import (16from landscape.package.tests.helpers import (
35 SmartFacadeHelper, HASH1, HASH2, HASH3, PKGNAME1, PKGNAME2, PKGNAME3,17 HASH1, HASH2, HASH3, PKGNAME1, PKGNAME2, PKGNAME3,
36 PKGNAME4, PKGDEB4, PKGDEB1, PKGNAME_MINIMAL, PKGDEB_MINIMAL,18 PKGDEB1, PKGNAME_MINIMAL, PKGDEB_MINIMAL,
37 create_full_repository, create_deb, AptFacadeHelper,19 create_deb, AptFacadeHelper,
38 create_simple_repository)20 create_simple_repository)
3921
4022
@@ -60,9 +42,6 @@
6042
61class AptFacadeTest(LandscapeTest):43class AptFacadeTest(LandscapeTest):
6244
63 if not has_new_enough_apt:
64 skip = "Can't use AptFacade on hardy"
65
66 helpers = [AptFacadeHelper, EnvironSaverHelper]45 helpers = [AptFacadeHelper, EnvironSaverHelper]
6746
68 def version_sortkey(self, version):47 def version_sortkey(self, version):
@@ -1564,16 +1543,24 @@
1564 self._add_package_to_deb_dir(deb_dir, "bar", version="1.5")1543 self._add_package_to_deb_dir(deb_dir, "bar", version="1.5")
1565 self._add_system_package("baz")1544 self._add_system_package("baz")
1566 self.facade.add_channel_apt_deb("file://%s" % deb_dir, "./")1545 self.facade.add_channel_apt_deb("file://%s" % deb_dir, "./")
1546 self._add_system_package("quux", version="1.0")
1547 self._add_system_package("wibble", version="1.0")
1567 self.facade.reload_channels()1548 self.facade.reload_channels()
1568 [foo] = self.facade.get_packages_by_name("foo")1549 [foo] = self.facade.get_packages_by_name("foo")
1569 self.facade.mark_install(foo)1550 self.facade.mark_install(foo)
1570 self.facade.mark_global_upgrade()1551 self.facade.mark_global_upgrade()
1571 [baz] = self.facade.get_packages_by_name("baz")1552 [baz] = self.facade.get_packages_by_name("baz")
1572 self.facade.mark_remove(baz)1553 self.facade.mark_remove(baz)
1554 [quux] = self.facade.get_packages_by_name("quux")
1555 self.facade.mark_hold(quux)
1556 [wibble] = self.facade.get_packages_by_name("wibble")
1557 self.facade.mark_remove_hold(wibble)
1573 self.facade.reset_marks()1558 self.facade.reset_marks()
1574 self.assertEqual(self.facade._version_installs, [])1559 self.assertEqual(self.facade._version_installs, [])
1575 self.assertEqual(self.facade._version_removals, [])1560 self.assertEqual(self.facade._version_removals, [])
1576 self.assertFalse(self.facade._global_upgrade)1561 self.assertFalse(self.facade._global_upgrade)
1562 self.assertEqual(self.facade._version_hold_creations, [])
1563 self.assertEqual(self.facade._version_hold_removals, [])
1577 self.assertEqual(self.facade.perform_changes(), None)1564 self.assertEqual(self.facade.perform_changes(), None)
15781565
1579 def test_reset_marks_resets_cache(self):1566 def test_reset_marks_resets_cache(self):
@@ -2349,50 +2336,81 @@
2349 self.assertEqual(2336 self.assertEqual(
2350 ["baz", "foo"], sorted(self.facade.get_package_holds()))2337 ["baz", "foo"], sorted(self.facade.get_package_holds()))
23512338
2352 def test_set_package_hold(self):2339 def test_mark_hold_and_perform_hold_changes(self):
2353 """2340 """
2354 C{set_package_hold} marks a package to be on hold.2341 Test that L{perform_hold_changes} holds packages that have previously
2355 """2342 been marked for hold.
2356 self._add_system_package("foo")2343 """
2357 self.facade.reload_channels()2344 self._add_system_package("foo")
2358 [foo] = self.facade.get_packages_by_name("foo")2345 self.facade.reload_channels()
2359 self.facade.set_package_hold(foo)2346 [foo] = self.facade.get_packages_by_name("foo")
2360 self.facade.reload_channels()2347 self.facade.mark_hold(foo)
23612348 self.assertEqual("Package holds successfully changed.",
2362 self.assertEqual(["foo"], self.facade.get_package_holds())2349 self.facade._perform_hold_changes())
23632350 self.facade.reload_channels()
2364 def test_set_package_hold_existing_hold(self):2351 self.assertEqual(["foo"], self.facade.get_package_holds())
2365 """2352
2366 If a package is already hel, C{set_package_hold} doesn't return2353 def test_mark_hold(self):
2367 an error.2354 """
2368 """2355 C{mark_hold} marks a package to be held.
2369 self._add_system_package(2356 """
2370 "foo", control_fields={"Status": "hold ok installed"})2357 self._add_system_package("foo")
2371 self.facade.reload_channels()2358 self.facade.reload_channels()
2372 [foo] = self.facade.get_packages_by_name("foo")2359 [foo] = self.facade.get_packages_by_name("foo")
2373 self.facade.set_package_hold(foo)2360 self.facade.mark_hold(foo)
2374 self.facade.reload_channels()2361 self.facade.perform_changes()
23752362 self.facade.reload_channels()
2376 self.assertEqual(["foo"], self.facade.get_package_holds())2363 self.assertEqual(["foo"], self.facade.get_package_holds())
23772364
2378 def test_remove_package_hold(self):2365 def test_two_holds_with_the_same_version_id(self):
2379 """2366 """
2380 C{remove_package_hold} marks a package not to be on hold.2367 Test C{mark_hold} can distinguish between two different packages with
2381 """2368 the same version number (the version number is used to make the unique
2382 self._add_system_package(2369 hash for the package version).
2383 "foo", control_fields={"Status": "hold ok installed"})2370 """
2384 self.facade.reload_channels()2371 self._add_system_package("foo", version="1.0")
2385 [foo] = self.facade.get_packages_by_name("foo")2372 self._add_system_package("bar", version="1.0")
2386 self.facade.remove_package_hold(foo)2373 self.facade.reload_channels()
2374 [foo] = self.facade.get_packages_by_name("foo")
2375 [bar] = self.facade.get_packages_by_name("bar")
2376 self.facade.mark_hold(foo)
2377 self.facade.mark_hold(bar)
2378 self.assertEqual(2, len(self.facade._version_hold_creations))
2379
2380 def test_mark_hold_existing_hold(self):
2381 """
2382 If a package is already held, C{mark_hold} and
2383 C{perform_changes} won't return an error.
2384 """
2385 self._add_system_package(
2386 "foo", control_fields={"Status": "hold ok installed"})
2387 self.facade.reload_channels()
2388 [foo] = self.facade.get_packages_by_name("foo")
2389 self.facade.mark_hold(foo)
2390 self.facade.perform_changes()
2391 self.facade.reload_channels()
2392
2393 self.assertEqual(["foo"], self.facade.get_package_holds())
2394
2395 def test_mark_remove_hold(self):
2396 """
2397 C{mark_remove_hold} marks a package as not held.
2398 """
2399 self._add_system_package(
2400 "foo", control_fields={"Status": "hold ok installed"})
2401 self.facade.reload_channels()
2402 [foo] = self.facade.get_packages_by_name("foo")
2403 self.facade.mark_remove_hold(foo)
2404 self.facade.perform_changes()
2387 self.facade.reload_channels()2405 self.facade.reload_channels()
23882406
2389 self.assertEqual([], self.facade.get_package_holds())2407 self.assertEqual([], self.facade.get_package_holds())
23902408
2391 def test_remove_package_hold_no_package(self):2409 def test_mark_remove_hold_no_package(self):
2392 """2410 """
2393 If a package doesn't exist, C{remove_package_hold} doesn't2411 If a package doesn't exist, C{mark_remove_hold} followed by
2394 return an error. It's up to the caller to make sure that the2412 C{perform_changes} doesn't return an error. It's up to the caller to
2395 package exist, if it's important.2413 make sure that the package exist, if it's important.
2396 """2414 """
2397 self._add_system_package("foo")2415 self._add_system_package("foo")
2398 deb_dir = self.makeDir()2416 deb_dir = self.makeDir()
@@ -2400,21 +2418,23 @@
2400 self.facade.add_channel_apt_deb("file://%s" % deb_dir, "./")2418 self.facade.add_channel_apt_deb("file://%s" % deb_dir, "./")
2401 self.facade.reload_channels()2419 self.facade.reload_channels()
2402 [bar] = self.facade.get_packages_by_name("bar")2420 [bar] = self.facade.get_packages_by_name("bar")
2403 self.facade.remove_package_hold(bar)2421 self.facade.mark_remove_hold(bar)
2422 self.facade.perform_changes()
2404 self.facade.reload_channels()2423 self.facade.reload_channels()
24052424
2406 self.assertEqual([], self.facade.get_package_holds())2425 self.assertEqual([], self.facade.get_package_holds())
24072426
2408 def test_remove_package_hold_no_hold(self):2427 def test_mark_remove_hold_no_hold(self):
2409 """2428 """
2410 If a package isn't held, the existing selection is retained when2429 If a package isn't held, the existing selection is retained when
2411 C{remove_package_hold} is called.2430 C{mark_remove_hold} and C{perform_changes} are called.
2412 """2431 """
2413 self._add_system_package(2432 self._add_system_package(
2414 "foo", control_fields={"Status": "deinstall ok installed"})2433 "foo", control_fields={"Status": "deinstall ok installed"})
2415 self.facade.reload_channels()2434 self.facade.reload_channels()
2416 [foo] = self.facade.get_packages_by_name("foo")2435 [foo] = self.facade.get_packages_by_name("foo")
2417 self.facade.remove_package_hold(foo)2436 self.facade.mark_remove_hold(foo)
2437 self.facade.perform_changes()
2418 self.facade.reload_channels()2438 self.facade.reload_channels()
24192439
2420 self.assertEqual([], self.facade.get_package_holds())2440 self.assertEqual([], self.facade.get_package_holds())
@@ -2430,670 +2450,3 @@
2430 test_wb_mark_install_upgrade_non_main_arch_dependency_error.skip = (2450 test_wb_mark_install_upgrade_non_main_arch_dependency_error.skip = (
2431 skip_message)2451 skip_message)
2432 test_wb_mark_install_upgrade_non_main_arch.skip = skip_message2452 test_wb_mark_install_upgrade_non_main_arch.skip = skip_message
2433
2434
2435class SmartFacadeTest(LandscapeTest):
2436
2437 helpers = [SmartFacadeHelper]
2438
2439 def test_needs_smart(self):
2440 """
2441 If the Smart python modules can't be imported, a C{RuntimeError}
2442 is raised when trying to create a C{SmartFacade}.
2443 """
2444
2445 def reset_has_smart():
2446 facade_module.has_smart = old_has_smart
2447
2448 self.addCleanup(reset_has_smart)
2449 old_has_smart = facade_module.has_smart
2450 facade_module.has_smart = False
2451
2452 self.assertRaises(RuntimeError, self.Facade)
2453
2454 def test_get_packages(self):
2455 self.facade.reload_channels()
2456 pkgs = self.facade.get_packages()
2457 self.assertEqual(sorted(pkg.name for pkg in pkgs),
2458 ["name1", "name2", "name3"])
2459
2460 def test_get_packages_wont_return_non_debian_packages(self):
2461 self.facade.reload_channels()
2462 ctrl_mock = self.mocker.patch(Control)
2463
2464 class StubPackage(object):
2465 pass
2466
2467 cache_mock = ctrl_mock.getCache()
2468 cache_mock.getPackages()
2469 self.mocker.result([StubPackage(), StubPackage()])
2470 self.mocker.replay()
2471 self.assertEqual(self.facade.get_packages(), [])
2472
2473 def test_get_packages_by_name(self):
2474 self.facade.reload_channels()
2475 pkgs = self.facade.get_packages_by_name("name1")
2476 self.assertEqual([pkg.name for pkg in pkgs], ["name1"])
2477 pkgs = self.facade.get_packages_by_name("name2")
2478 self.assertEqual([pkg.name for pkg in pkgs], ["name2"])
2479
2480 def test_get_packages_by_name_wont_return_non_debian_packages(self):
2481 self.facade.reload_channels()
2482 ctrl_mock = self.mocker.patch(Control)
2483
2484 class StubPackage(object):
2485 pass
2486
2487 cache_mock = ctrl_mock.getCache()
2488 cache_mock.getPackages("name")
2489 self.mocker.result([StubPackage(), StubPackage()])
2490 self.mocker.replay()
2491 self.assertEqual(self.facade.get_packages_by_name("name"), [])
2492
2493 def test_get_package_skeleton(self):
2494 self.facade.reload_channels()
2495 [pkg1] = self.facade.get_packages_by_name("name1")
2496 [pkg2] = self.facade.get_packages_by_name("name2")
2497 skeleton1 = self.facade.get_package_skeleton(pkg1)
2498 skeleton2 = self.facade.get_package_skeleton(pkg2)
2499 self.assertEqual(skeleton1.get_hash(), HASH1)
2500 self.assertEqual(skeleton2.get_hash(), HASH2)
2501
2502 def test_build_skeleton_with_info(self):
2503 self.facade.reload_channels()
2504 [pkg] = self.facade.get_packages_by_name("name1")
2505 skeleton = self.facade.get_package_skeleton(pkg, True)
2506 self.assertEqual(skeleton.section, "Group1")
2507 self.assertEqual(skeleton.summary, "Summary1")
2508 self.assertEqual(skeleton.description, "Description1")
2509 self.assertEqual(skeleton.size, 1038)
2510 self.assertEqual(skeleton.installed_size, 28672)
2511
2512 def test_get_package_hash(self):
2513 self.facade.reload_channels()
2514 [pkg] = self.facade.get_packages_by_name("name1")
2515 self.assertEqual(self.facade.get_package_hash(pkg), HASH1)
2516 [pkg] = self.facade.get_packages_by_name("name2")
2517 self.assertEqual(self.facade.get_package_hash(pkg), HASH2)
2518
2519 def test_get_package_hashes(self):
2520 self.facade.reload_channels()
2521 hashes = self.facade.get_package_hashes()
2522 self.assertEqual(sorted(hashes), sorted([HASH1, HASH2, HASH3]))
2523
2524 def test_get_package_by_hash(self):
2525 self.facade.reload_channels()
2526 pkg = self.facade.get_package_by_hash(HASH1)
2527 self.assertEqual(pkg.name, "name1")
2528 pkg = self.facade.get_package_by_hash(HASH2)
2529 self.assertEqual(pkg.name, "name2")
2530 pkg = self.facade.get_package_by_hash("none")
2531 self.assertEqual(pkg, None)
2532
2533 def test_reload_channels_clears_hash_cache(self):
2534 # Load hashes.
2535 self.facade.reload_channels()
2536
2537 # Hold a reference to packages.
2538 [pkg1] = self.facade.get_packages_by_name("name1")
2539 [pkg2] = self.facade.get_packages_by_name("name2")
2540 [pkg3] = self.facade.get_packages_by_name("name3")
2541 self.assertTrue(pkg1 and pkg2)
2542
2543 # Remove the package from the repository.
2544 os.unlink(os.path.join(self.repository_dir, PKGNAME1))
2545
2546 # Forcibly change the mtime of our repository, so that Smart
2547 # will consider it as changed (if the change is inside the
2548 # same second the directory's mtime will be the same)
2549 mtime = int(time.time() + 1)
2550 os.utime(self.repository_dir, (mtime, mtime))
2551
2552 # Reload channels.
2553 self.facade.reload_channels()
2554
2555 # Only packages with name2 and name3 should be loaded, and they're
2556 # not the same objects anymore.
2557 self.assertEqual(
2558 sorted([pkg.name for pkg in self.facade.get_packages()]),
2559 ["name2", "name3"])
2560 self.assertNotEquals(set(self.facade.get_packages()),
2561 set([pkg2, pkg3]))
2562
2563 # The hash cache shouldn't include either of the old packages.
2564 self.assertEqual(self.facade.get_package_hash(pkg1), None)
2565 self.assertEqual(self.facade.get_package_hash(pkg2), None)
2566 self.assertEqual(self.facade.get_package_hash(pkg3), None)
2567
2568 # Also, the hash for package1 shouldn't be present at all.
2569 self.assertEqual(self.facade.get_package_by_hash(HASH1), None)
2570
2571 # While HASH2 and HASH3 should point to the new packages.
2572 new_pkgs = self.facade.get_packages()
2573 self.assertTrue(self.facade.get_package_by_hash(HASH2)
2574 in new_pkgs)
2575 self.assertTrue(self.facade.get_package_by_hash(HASH3)
2576 in new_pkgs)
2577
2578 # Which are not the old packages.
2579 self.assertFalse(pkg2 in new_pkgs)
2580 self.assertFalse(pkg3 in new_pkgs)
2581
2582 def test_ensure_reload_channels(self):
2583 """
2584 The L{SmartFacade.ensure_channels_reloaded} can be called more
2585 than once, but channels will be reloaded only the first time.
2586 """
2587 self.assertEqual(len(self.facade.get_packages()), 0)
2588 self.facade.ensure_channels_reloaded()
2589 self.assertEqual(len(self.facade.get_packages()), 3)
2590
2591 # Calling it once more won't reload channels again.
2592 self.facade.get_packages_by_name("name1")[0].installed = True
2593 self.facade.ensure_channels_reloaded()
2594 self.assertTrue(self.facade.get_packages_by_name("name1")[0].installed)
2595
2596 def test_perform_changes_with_nothing_to_do(self):
2597 """perform_changes() should return None when there's nothing to do.
2598 """
2599 self.facade.reload_channels()
2600 self.assertEqual(self.facade.perform_changes(), None)
2601
2602 def test_reset_marks(self):
2603 """perform_changes() should return None when there's nothing to do.
2604 """
2605 self.facade.reload_channels()
2606 [pkg] = self.facade.get_packages_by_name("name1")
2607 self.facade.mark_install(pkg)
2608 self.facade.reset_marks()
2609 self.assertEqual(self.facade.perform_changes(), None)
2610
2611 def test_mark_install_transaction_error(self):
2612 """
2613 Mark package 'name1' for installation, and try to perform changes.
2614 It should fail because 'name1' depends on 'requirename1'.
2615 """
2616 self.facade.reload_channels()
2617
2618 [pkg] = self.facade.get_packages_by_name("name1")
2619 self.facade.mark_install(pkg)
2620 exception = self.assertRaises(TransactionError,
2621 self.facade.perform_changes)
2622 self.assertIn("requirename", exception.args[0])
2623
2624 def test_mark_install_dependency_error(self):
2625 """
2626 Now we artificially inject the needed dependencies of 'name1'
2627 in 'name2', but we don't mark 'name2' for installation, and
2628 that should make perform_changes() fail with a dependency
2629 error on the needed package.
2630 """
2631 self.facade.reload_channels()
2632
2633 provide1 = Provides("prerequirename1", "prerequireversion1")
2634 provide2 = Provides("requirename1", "requireversion1")
2635 [pkg2] = self.facade.get_packages_by_name("name2")
2636 pkg2.provides += (provide1, provide2)
2637
2638 # We have to satisfy *both* packages.
2639 provide1 = Provides("prerequirename2", "prerequireversion2")
2640 provide2 = Provides("requirename2", "requireversion2")
2641 [pkg1] = self.facade.get_packages_by_name("name1")
2642 pkg1.provides += (provide1, provide2)
2643
2644 # Ask Smart to reprocess relationships.
2645 self.facade.reload_cache()
2646
2647 self.assertEqual(pkg1.requires[0].providedby[0].packages[0], pkg2)
2648 self.assertEqual(pkg1.requires[1].providedby[0].packages[0], pkg2)
2649
2650 self.facade.mark_install(pkg1)
2651 try:
2652 self.facade.perform_changes()
2653 except DependencyError, exception:
2654 pass
2655 else:
2656 exception = None
2657 self.assertTrue(exception, "DependencyError not raised")
2658 self.assertEqual(exception.packages, [pkg2])
2659
2660 def test_mark_remove_dependency_error(self):
2661 """
2662 Besides making 'name1' satisfy 'name2' and the contrary. We'll
2663 mark both packages installed, so that we can get an error on
2664 removal.
2665 """
2666 self.facade.reload_channels()
2667
2668 provide1 = Provides("prerequirename1", "prerequireversion1")
2669 provide2 = Provides("requirename1", "requireversion1")
2670 [pkg2] = self.facade.get_packages_by_name("name2")
2671 pkg2.provides += (provide1, provide2)
2672
2673 # We have to satisfy *both* packages.
2674 provide1 = Provides("prerequirename2", "prerequireversion2")
2675 provide2 = Provides("requirename2", "requireversion2")
2676 [pkg1] = self.facade.get_packages_by_name("name1")
2677 pkg1.provides += (provide1, provide2)
2678
2679 # Ask Smart to reprocess relationships.
2680 self.facade.reload_cache()
2681
2682 pkg1.installed = True
2683 pkg2.installed = True
2684
2685 self.assertEqual(pkg1.requires[0].providedby[0].packages[0], pkg2)
2686 self.assertEqual(pkg1.requires[1].providedby[0].packages[0], pkg2)
2687
2688 self.facade.mark_remove(pkg2)
2689 try:
2690 output = self.facade.perform_changes()
2691 except DependencyError, exception:
2692 output = ""
2693 else:
2694 exception = None
2695 self.assertTrue(exception, "DependencyError not raised. Output: %s"
2696 % repr(output))
2697 self.assertEqual(exception.packages, [pkg1])
2698
2699 def test_mark_upgrade_dependency_error(self):
2700 """Artificially make pkg2 upgrade pkg1, and mark pkg1 for upgrade."""
2701
2702 # The backend only works after initialized.
2703 from smart.backends.deb.base import DebUpgrades, DebConflicts
2704
2705 self.facade.reload_channels()
2706
2707 [pkg1] = self.facade.get_packages_by_name("name1")
2708 [pkg2] = self.facade.get_packages_by_name("name2")
2709
2710 # Artificially make pkg2 be self-satisfied, and make it upgrade and
2711 # conflict with pkg1.
2712 pkg2.requires = []
2713 pkg2.upgrades = [DebUpgrades("name1", "=", "version1-release1")]
2714 pkg2.conflicts = [DebConflicts("name1", "=", "version1-release1")]
2715
2716 # pkg1 will also be self-satisfied.
2717 pkg1.requires = []
2718
2719 # Ask Smart to reprocess relationships.
2720 self.facade.reload_cache()
2721
2722 # Mark the pkg1 as installed. Must be done after reloading
2723 # the cache as reloading will reset it to the loader installed
2724 # status.
2725 pkg1.installed = True
2726
2727 # Check that the linkage worked.
2728 self.assertEqual(pkg2.upgrades[0].providedby[0].packages[0], pkg1)
2729
2730 # Perform the upgrade test.
2731 self.facade.mark_upgrade(pkg1)
2732 try:
2733 self.facade.perform_changes()
2734 except DependencyError, exception:
2735 pass
2736 else:
2737 exception = None
2738 self.assertTrue(exception, "DependencyError not raised")
2739
2740 # Both packages should be included in the dependency error. One
2741 # must be removed, and the other installed.
2742 self.assertEqual(set(exception.packages), set([pkg1, pkg2]))
2743
2744 def test_perform_changes_with_logged_error(self):
2745 self.log_helper.ignore_errors(".*dpkg")
2746
2747 self.facade.reload_channels()
2748
2749 [pkg] = self.facade.get_packages_by_name("name1")
2750 pkg.requires = ()
2751
2752 self.facade.reload_cache()
2753
2754 self.facade.mark_install(pkg)
2755
2756 try:
2757 output = self.facade.perform_changes()
2758 except SmartError, exception:
2759 output = ""
2760 else:
2761 exception = None
2762
2763 self.assertTrue(exception,
2764 "SmartError not raised. Output: %s" % repr(output))
2765 # We can't check the whole message because the dpkg error can be
2766 # localized. We can't use str(exception) either because it can contain
2767 # unicode
2768 self.assertIn("ERROR", exception.args[0])
2769 self.assertIn("(2)", exception.args[0])
2770 self.assertIn("\n[unpack] name1_version1-release1\ndpkg: ",
2771 exception.args[0])
2772
2773 def test_perform_changes_is_non_interactive(self):
2774 from smart.backends.deb.pm import DebPackageManager
2775
2776 self.facade.reload_channels()
2777
2778 [pkg] = self.facade.get_packages_by_name("name1")
2779 pkg.requires = ()
2780
2781 self.facade.reload_cache()
2782
2783 self.facade.mark_install(pkg)
2784
2785 environ = []
2786
2787 def check_environ(self, argv, output):
2788 environ.append(os.environ.get("DEBIAN_FRONTEND"))
2789 environ.append(os.environ.get("APT_LISTCHANGES_FRONTEND"))
2790 return 0
2791
2792 DebPackageManager.dpkg, olddpkg = check_environ, DebPackageManager.dpkg
2793
2794 try:
2795 self.facade.perform_changes()
2796 finally:
2797 DebPackageManager.dpkg = olddpkg
2798
2799 self.assertEqual(environ, ["noninteractive", "none",
2800 "noninteractive", "none"])
2801
2802 def test_perform_changes_with_policy_remove(self):
2803 """
2804 When requested changes are only about removing packages, we set
2805 the Smart transaction policy to C{PolicyRemove}.
2806 """
2807 create_deb(self.repository_dir, PKGNAME4, PKGDEB4)
2808 self.facade.reload_channels()
2809
2810 # Importing these modules fail if Smart is not initialized
2811 from smart.backends.deb.base import DebRequires
2812
2813 pkg1 = self.facade.get_package_by_hash(HASH1)
2814 pkg1.requires.append(DebRequires("name3", ">=", "version3-release3"))
2815
2816 pkg3 = self.facade.get_package_by_hash(HASH3)
2817
2818 # Ask Smart to reprocess relationships.
2819 self.facade.reload_cache()
2820
2821 pkg1.installed = True
2822 pkg3.installed = True
2823
2824 self.facade.mark_remove(pkg3)
2825 error = self.assertRaises(DependencyError, self.facade.perform_changes)
2826 [missing] = error.packages
2827 self.assertIdentical(pkg1, missing)
2828
2829 def test_perform_changes_with_commit_change_set_errors(self):
2830
2831 self.facade.reload_channels()
2832
2833 [pkg] = self.facade.get_packages_by_name("name1")
2834 pkg.requires = ()
2835
2836 self.facade.mark_install(pkg)
2837
2838 ctrl_mock = self.mocker.patch(Control)
2839 ctrl_mock.commitChangeSet(ANY)
2840 self.mocker.throw(smart.Error("commit error"))
2841 self.mocker.replay()
2842
2843 self.assertRaises(TransactionError, self.facade.perform_changes)
2844
2845 def test_deinit_cleans_the_state(self):
2846 self.facade.reload_channels()
2847 self.assertTrue(self.facade.get_package_by_hash(HASH1))
2848 self.facade.deinit()
2849 self.assertFalse(self.facade.get_package_by_hash(HASH1))
2850
2851 def test_deinit_deinits_smart(self):
2852 self.facade.reload_channels()
2853 self.assertTrue(smart.iface.object)
2854 self.facade.deinit()
2855 self.assertFalse(smart.iface.object)
2856
2857 def test_deinit_when_smart_wasnt_initialized(self):
2858 self.assertFalse(smart.iface.object)
2859 # Nothing bad should happen.
2860 self.facade.deinit()
2861
2862 def test_reload_channels_wont_consider_non_debian_packages(self):
2863
2864 class StubPackage(object):
2865 pass
2866
2867 pkg = StubPackage()
2868
2869 ctrl_mock = self.mocker.patch(Control)
2870 cache_mock = ctrl_mock.getCache()
2871 cache_mock.getPackages()
2872 self.mocker.result([pkg])
2873 self.mocker.replay()
2874
2875 self.facade.reload_channels()
2876 self.assertEqual(self.facade.get_package_hash(pkg), None)
2877
2878 def test_reload_channels_with_channel_error(self):
2879 """
2880 The L{SmartFacade.reload_channels} method raises a L{ChannelsError} if
2881 smart fails to load the configured channels.
2882 """
2883 ctrl_mock = self.mocker.patch(Control)
2884 ctrl_mock.reloadChannels(caching=ALWAYS)
2885 self.mocker.throw(smart.Error(u"Channel information is locked"))
2886 self.mocker.replay()
2887 self.assertRaises(ChannelError, self.facade.reload_channels)
2888
2889 def test_reset_add_get_channels(self):
2890
2891 channels = [("alias0", {"type": "test"}),
2892 ("alias1", {"type": "test"})]
2893
2894 self.facade.reset_channels()
2895
2896 self.assertEqual(self.facade.get_channels(), {})
2897
2898 self.facade.add_channel(*channels[0])
2899 self.facade.add_channel(*channels[1])
2900
2901 self.assertEqual(self.facade.get_channels(), dict(channels))
2902
2903 def test_add_apt_deb_channel(self):
2904 """
2905 The L{SmartFacade.add_channel_apt_deb} add a Smart channel of
2906 type C{"apt-deb"}.
2907 """
2908 self.facade.reset_channels()
2909 self.facade.add_channel_apt_deb("http://url/", "name", "component")
2910 self.assertEqual(self.facade.get_channels(),
2911 {"name": {"baseurl": "http://url/",
2912 "distribution": "name",
2913 "components": "component",
2914 "type": "apt-deb"}})
2915
2916 def test_add_deb_dir_channel(self):
2917 """
2918 The L{SmartFacade.add_channel_deb_dir} add a Smart channel of
2919 type C{"deb-dir"}.
2920 """
2921 self.facade.reset_channels()
2922 self.facade.add_channel_deb_dir("/my/repo")
2923 self.assertEqual(self.facade.get_channels(),
2924 {"/my/repo": {"path": "/my/repo",
2925 "type": "deb-dir"}})
2926
2927 def test_get_arch(self):
2928 """
2929 The L{SmartFacade.get_arch} should return the system dpkg
2930 architecture.
2931 """
2932 deferred = Deferred()
2933
2934 def do_test():
2935 result = getProcessOutputAndValue("/usr/bin/dpkg",
2936 ("--print-architecture",))
2937
2938 def callback((out, err, code)):
2939 self.assertEqual(self.facade.get_arch(), out.strip())
2940 result.addCallback(callback)
2941 result.chainDeferred(deferred)
2942
2943 reactor.callWhenRunning(do_test)
2944 return deferred
2945
2946 def test_set_arch_multiple_times(self):
2947
2948 repo = create_full_repository(self.makeDir())
2949
2950 self.facade.set_arch("i386")
2951 self.facade.reset_channels()
2952 self.facade.add_channel_apt_deb(repo.url, repo.codename,
2953 " ".join(repo.components))
2954 self.facade.reload_channels()
2955
2956 pkgs = self.facade.get_packages()
2957 self.assertEqual(len(pkgs), 2)
2958 self.assertEqual(pkgs[0].name, "syslinux")
2959 self.assertEqual(pkgs[1].name, "kairos")
2960
2961 self.facade.deinit()
2962 self.facade.set_arch("amd64")
2963 self.facade.reset_channels()
2964 self.facade.add_channel_apt_deb(repo.url, repo.codename,
2965 " ".join(repo.components))
2966 self.facade.reload_channels()
2967
2968 pkgs = self.facade.get_packages()
2969 self.assertEqual(len(pkgs), 2)
2970 self.assertEqual(pkgs[0].name, "libclthreads2")
2971 self.assertEqual(pkgs[1].name, "kairos")
2972
2973 def test_set_caching_with_reload_error(self):
2974
2975 alias = "alias"
2976 channel = {"type": "deb-dir",
2977 "path": "/does/not/exist"}
2978
2979 self.facade.reset_channels()
2980 self.facade.add_channel(alias, channel)
2981 self.facade.set_caching(NEVER)
2982
2983 self.assertRaises(ChannelError, self.facade.reload_channels)
2984 self.facade._channels = {}
2985
2986 ignore_re = re.compile("\[Smart\].*'alias'.*/does/not/exist")
2987
2988 self.log_helper.ignored_exception_regexes = [ignore_re]
2989
2990 def test_init_landscape_plugins(self):
2991 """
2992 The landscape plugin which helps managing proxies is loaded when smart
2993 is initialized: this sets a smart configuration variable and load the
2994 module.
2995 """
2996 self.facade.reload_channels()
2997 self.assertTrue(smart.sysconf.get("use-landscape-proxies"))
2998 self.assertIn("smart.plugins.landscape", sys.modules)
2999
3000 def test_get_package_locks_with_no_lock(self):
3001 """
3002 If no package locks are set, L{SmartFacade.get_package_locks} returns
3003 an empty C{list}.
3004 """
3005 self.assertEqual(self.facade.get_package_locks(), [])
3006
3007 def test_get_package_locks_with_one_lock(self):
3008 """
3009 If one lock is set, the list of locks contains one item.
3010 """
3011 self.facade.set_package_lock("name1", "<", "version1")
3012 self.assertEqual(self.facade.get_package_locks(),
3013 [("name1", "<", "version1")])
3014
3015 def test_get_package_locks_with_many_locks(self):
3016 """
3017 It's possible to have more than one package lock and several conditions
3018 for each of them.
3019 """
3020 self.facade.set_package_lock("name1", "<", "version1")
3021 self.facade.set_package_lock("name1", ">=", "version3")
3022 self.facade.set_package_lock("name2")
3023 self.assertEqual(sorted(self.facade.get_package_locks()),
3024 sorted([("name1", "<", "version1"),
3025 ("name1", ">=", "version3"),
3026 ("name2", "", "")]))
3027
3028 def test_set_package_lock(self):
3029 """
3030 It is possible to lock a package by simply specifying its name.
3031 """
3032 self.facade.set_package_lock("name1")
3033 self.facade.reload_channels()
3034 [package] = self.facade.get_locked_packages()
3035 self.assertEqual(package.name, "name1")
3036
3037 def test_set_package_lock_with_matching_condition(self):
3038 """
3039 It is possible to set a package lock specifying both a
3040 package name and version condition. Any matching package
3041 will be locked.
3042 """
3043 self.facade.set_package_lock("name1", "<", "version2")
3044 self.facade.reload_channels()
3045 [package] = self.facade.get_locked_packages()
3046 self.assertEqual(package.name, "name1")
3047
3048 def test_set_package_lock_with_non_matching_condition(self):
3049 """
3050 If the package lock conditions do not match any package,
3051 no package will be locked.
3052 """
3053 self.facade.set_package_lock("name1", "<", "version1")
3054 self.facade.reload_channels()
3055 self.assertEqual(self.facade.get_locked_packages(), [])
3056
3057 def test_set_package_lock_with_missing_version(self):
3058 """
3059 When specifing a relation for a package lock condition, a version
3060 must be provided as well.
3061 """
3062 error = self.assertRaises(RuntimeError, self.facade.set_package_lock,
3063 "name1", "<", "")
3064 self.assertEqual(str(error), "Package lock version not provided")
3065
3066 def test_set_package_lock_with_missing_relation(self):
3067 """
3068 When specifing a version for a package lock condition, a relation
3069 must be provided as well.
3070 """
3071 error = self.assertRaises(RuntimeError, self.facade.set_package_lock,
3072 "name1", "", "version1")
3073 self.assertEqual(str(error), "Package lock relation not provided")
3074
3075 def test_remove_package_lock(self):
3076 """
3077 It is possibly to remove a package lock without any version condition.
3078 """
3079 self.facade.set_package_lock("name1")
3080 self.facade.remove_package_lock("name1")
3081 self.assertEqual(self.facade.get_locked_packages(), [])
3082
3083 def test_remove_package_lock_with_condition(self):
3084 """
3085 It is possibly to remove a package lock with a version condition.
3086 """
3087 self.facade.set_package_lock("name1", "<", "version1")
3088 self.facade.remove_package_lock("name1", "<", "version1")
3089 self.assertEqual(self.facade.get_locked_packages(), [])
3090
3091 def test_save_config(self):
3092 """
3093 It is possible to lock a package by simply specifying its name.
3094 """
3095 self.facade.set_package_lock("python", "=>", "2.5")
3096 self.facade.save_config()
3097 self.facade.deinit()
3098 self.assertEqual(self.facade.get_package_locks(),
3099 [("python", "=>", "2.5")])
31002453
=== removed file 'landscape/package/tests/test_interface.py'
--- landscape/package/tests/test_interface.py 2011-07-18 15:16:18 +0000
+++ landscape/package/tests/test_interface.py 1970-01-01 00:00:00 +0000
@@ -1,34 +0,0 @@
1# -*- encoding: utf-8 -*-
2from landscape.package.interface import LandscapeInterface
3
4from landscape.tests.helpers import LandscapeTest
5from landscape.package.tests.helpers import SmartFacadeHelper
6
7
8class LandscapeInterfaceTest(LandscapeTest):
9
10 helpers = [SmartFacadeHelper]
11
12 def setUp(self):
13 super(LandscapeInterfaceTest, self).setUp()
14 self.facade.reload_channels()
15 self.iface = LandscapeInterface(None)
16
17 def test_message_with_unicode_and_utf8(self):
18 self.iface.info(u"áéíóú")
19 self.iface.info("áéíóú")
20 self.assertEqual(self.iface.get_output_for_landscape(),
21 u"INFO: áéíóú\nINFO: áéíóú\n")
22
23 def test_message_with_unicode_and_unknown_encoding(self):
24 self.iface.info(u"áéíóú")
25 self.iface.info("aeíou\xc3") # UTF-8 expects a byte after \xc3
26 c = u"\N{REPLACEMENT CHARACTER}"
27 self.assertEqual(self.iface.get_output_for_landscape(),
28 u"INFO: áéíóú\nINFO: ae%s%sou%s\n" % (c, c, c))
29
30 def test_output_with_unicode_and_utf8(self):
31 self.iface.showOutput(u"áéíóú")
32 self.iface.showOutput("áéíóú")
33 self.assertEqual(self.iface.get_output_for_landscape(),
34 u"áéíóúáéíóú")
350
=== modified file 'landscape/package/tests/test_releaseupgrader.py'
--- landscape/package/tests/test_releaseupgrader.py 2012-03-19 09:33:34 +0000
+++ landscape/package/tests/test_releaseupgrader.py 2012-06-04 14:08:28 +0000
@@ -655,7 +655,7 @@
655655
656 def check_result((out, err, code)):656 def check_result((out, err, code)):
657 self.assertFalse(os.path.exists(upgrade_tool_directory))657 self.assertFalse(os.path.exists(upgrade_tool_directory))
658 self.assertEqual(out, "--force-smart-update\n%s\n"658 self.assertEqual(out, "--force-apt-update\n%s\n"
659 % os.getcwd())659 % os.getcwd())
660 self.assertEqual(err, "")660 self.assertEqual(err, "")
661 self.assertEqual(code, 0)661 self.assertEqual(code, 0)
@@ -738,7 +738,7 @@
738 result = self.upgrader.finish()738 result = self.upgrader.finish()
739739
740 def check_result((out, err, code)):740 def check_result((out, err, code)):
741 self.assertEqual(out, "--force-smart-update "741 self.assertEqual(out, "--force-apt-update "
742 "--config=/some/config\n")742 "--config=/some/config\n")
743 self.assertEqual(err, "")743 self.assertEqual(err, "")
744 self.assertEqual(code, 0)744 self.assertEqual(code, 0)
745745
=== modified file 'landscape/package/tests/test_reporter.py'
--- landscape/package/tests/test_reporter.py 2012-03-19 09:33:34 +0000
+++ landscape/package/tests/test_reporter.py 2012-06-04 14:08:28 +0000
@@ -1,4 +1,3 @@
1import glob
2import sys1import sys
3import os2import os
4import unittest3import unittest
@@ -17,9 +16,9 @@
17 PackageReporter, HASH_ID_REQUEST_TIMEOUT, main, find_reporter_command,16 PackageReporter, HASH_ID_REQUEST_TIMEOUT, main, find_reporter_command,
18 PackageReporterConfiguration, FakeGlobalReporter, FakeReporter)17 PackageReporterConfiguration, FakeGlobalReporter, FakeReporter)
19from landscape.package import reporter18from landscape.package import reporter
20from landscape.package.facade import AptFacade, has_new_enough_apt19from landscape.package.facade import AptFacade
21from landscape.package.tests.helpers import (20from landscape.package.tests.helpers import (
22 SmartFacadeHelper, AptFacadeHelper, SimpleRepositoryHelper,21 AptFacadeHelper, SimpleRepositoryHelper,
23 HASH1, HASH2, HASH3, PKGNAME1)22 HASH1, HASH2, HASH3, PKGNAME1)
24from landscape.tests.helpers import (23from landscape.tests.helpers import (
25 LandscapeTest, BrokerServiceHelper, EnvironSaverHelper)24 LandscapeTest, BrokerServiceHelper, EnvironSaverHelper)
@@ -30,18 +29,63 @@
3029
31class PackageReporterConfigurationTest(unittest.TestCase):30class PackageReporterConfigurationTest(unittest.TestCase):
3231
33 def test_force_smart_update_option(self):32 def test_force_apt_update_option(self):
34 """33 """
35 The L{PackageReporterConfiguration} supports a '--force-smart-update'34 The L{PackageReporterConfiguration} supports a '--force-apt-update'
36 command line option.35 command line option.
37 """36 """
38 config = PackageReporterConfiguration()37 config = PackageReporterConfiguration()
39 self.assertFalse(config.force_smart_update)38 self.assertFalse(config.force_apt_update)
40 config.load(["--force-smart-update"])39 config.load(["--force-apt-update"])
41 self.assertTrue(config.force_smart_update)40 self.assertTrue(config.force_apt_update)
4241
4342
44class PackageReporterTestMixin(object):43class PackageReporterAptTest(LandscapeTest):
44
45 helpers = [AptFacadeHelper, SimpleRepositoryHelper, BrokerServiceHelper]
46
47 Facade = AptFacade
48
49 def setUp(self):
50
51 def set_up(ignored):
52 self.store = PackageStore(self.makeFile())
53 self.config = PackageReporterConfiguration()
54 self.reporter = PackageReporter(
55 self.store, self.facade, self.remote, self.config)
56 self.config.data_path = self.makeDir()
57 os.mkdir(self.config.package_directory)
58
59 result = super(PackageReporterAptTest, self).setUp()
60 return result.addCallback(set_up)
61
62 def _clear_repository(self):
63 """Remove all packages from self.repository."""
64 create_file(self.repository_dir + "/Packages", "")
65
66 def set_pkg1_upgradable(self):
67 """Make it so that package "name1" is considered to be upgradable.
68
69 Return the hash of the package that upgrades "name1".
70 """
71 self._add_package_to_deb_dir(
72 self.repository_dir, "name1", version="version2")
73 self.facade.reload_channels()
74 name1_upgrade = sorted(self.facade.get_packages_by_name("name1"))[1]
75 return self.facade.get_package_hash(name1_upgrade)
76
77 def set_pkg1_installed(self):
78 """Make it so that package "name1" is considered installed."""
79 self._install_deb_file(os.path.join(self.repository_dir, PKGNAME1))
80
81 def _make_fake_apt_update(self, out="output", err="error", code=0):
82 """Create a fake apt-update executable"""
83 self.reporter.apt_update_filename = self.makeFile(
84 "#!/bin/sh\n"
85 "echo -n %s\n"
86 "echo -n %s >&2\n"
87 "exit %d" % (out, err, code))
88 os.chmod(self.reporter.apt_update_filename, 0755)
4589
46 def test_set_package_ids_with_all_known(self):90 def test_set_package_ids_with_all_known(self):
47 self.store.add_hash_id_request(["hash1", "hash2"])91 self.store.add_hash_id_request(["hash1", "hash2"])
@@ -497,64 +541,6 @@
497541
498 return result542 return result
499543
500 def test_run_smart_update(self):
501 """
502 The L{PackageReporter.run_smart_update} method should run smart-update
503 with the proper arguments.
504 """
505 self.reporter.sources_list_filename = "/I/Dont/Exist"
506 self.reporter.sources_list_directory = "/I/Dont/Exist"
507 self.reporter.smart_update_filename = self.makeFile(
508 "#!/bin/sh\necho -n $@")
509 os.chmod(self.reporter.smart_update_filename, 0755)
510 debug_mock = self.mocker.replace("logging.debug")
511 debug_mock("'%s' exited with status 0 (out='--after %d', err=''" % (
512 self.reporter.smart_update_filename,
513 self.reporter.smart_update_interval))
514 warning_mock = self.mocker.replace("logging.warning")
515 self.expect(warning_mock(ANY)).count(0)
516 self.mocker.replay()
517 deferred = Deferred()
518
519 def do_test():
520
521 result = self.reporter.run_smart_update()
522
523 def callback((out, err, code)):
524 interval = self.reporter.smart_update_interval
525 self.assertEqual(err, "")
526 self.assertEqual(out, "--after %d" % interval)
527 self.assertEqual(code, 0)
528 result.addCallback(callback)
529 result.chainDeferred(deferred)
530
531 reactor.callWhenRunning(do_test)
532 return deferred
533
534 def test_run_smart_update_with_force_smart_update(self):
535 """
536 L{PackageReporter.run_smart_update} forces a smart-update run if
537 the '--force-smart-update' command line option was passed.
538
539 """
540 self.config.load(["--force-smart-update"])
541 self.reporter.smart_update_filename = self.makeFile(
542 "#!/bin/sh\necho -n $@")
543 os.chmod(self.reporter.smart_update_filename, 0755)
544
545 deferred = Deferred()
546
547 def do_test():
548 result = self.reporter.run_smart_update()
549
550 def callback((out, err, code)):
551 self.assertEqual(out, "")
552 result.addCallback(callback)
553 result.chainDeferred(deferred)
554
555 reactor.callWhenRunning(do_test)
556 return deferred
557
558 def test_wb_apt_sources_have_changed(self):544 def test_wb_apt_sources_have_changed(self):
559 """545 """
560 The L{PackageReporter._apt_sources_have_changed} method returns a bool546 The L{PackageReporter._apt_sources_have_changed} method returns a bool
@@ -581,244 +567,6 @@
581 content="deb http://foo ./")567 content="deb http://foo ./")
582 self.assertTrue(self.reporter._apt_sources_have_changed())568 self.assertTrue(self.reporter._apt_sources_have_changed())
583569
584 def test_run_smart_update_with_force_smart_update_if_sources_changed(self):
585 """
586 L{PackageReporter.run_smart_update} forces a smart-update run if
587 the APT sources.list file has changed.
588
589 """
590 self.assertEqual(self.reporter.sources_list_filename,
591 "/etc/apt/sources.list")
592 self.reporter.sources_list_filename = self.makeFile("deb ftp://url ./")
593 self.reporter.smart_update_filename = self.makeFile(
594 "#!/bin/sh\necho -n $@")
595 os.chmod(self.reporter.smart_update_filename, 0755)
596
597 deferred = Deferred()
598
599 def do_test():
600 result = self.reporter.run_smart_update()
601
602 def callback((out, err, code)):
603 # Smart update was called without the --after parameter
604 self.assertEqual(out, "")
605 result.addCallback(callback)
606 result.chainDeferred(deferred)
607
608 reactor.callWhenRunning(do_test)
609 return deferred
610
611 def test_run_smart_update_warns_about_failures(self):
612 """
613 The L{PackageReporter.run_smart_update} method should log a warning
614 in case smart-update terminates with a non-zero exit code other than 1.
615 """
616 self.reporter.smart_update_filename = self.makeFile(
617 "#!/bin/sh\necho -n error >&2\necho -n output\nexit 2")
618 os.chmod(self.reporter.smart_update_filename, 0755)
619 logging_mock = self.mocker.replace("logging.warning")
620 logging_mock("'%s' exited with status 2"
621 " (error)" % self.reporter.smart_update_filename)
622 self.mocker.replay()
623 deferred = Deferred()
624
625 def do_test():
626 result = self.reporter.run_smart_update()
627
628 def callback((out, err, code)):
629 self.assertEqual(out, "output")
630 self.assertEqual(err, "error")
631 self.assertEqual(code, 2)
632 result.addCallback(callback)
633 result.chainDeferred(deferred)
634
635 reactor.callWhenRunning(do_test)
636 return deferred
637
638 def test_run_smart_update_report_smart_failure(self):
639 """
640 If L{PackageReporter.run_smart_update} fails, a message is sent to the
641 server reporting the error, to be able to fix the problem centrally.
642 """
643 message_store = self.broker_service.message_store
644 message_store.set_accepted_types(["package-reporter-result"])
645 self.reporter.smart_update_filename = self.makeFile(
646 "#!/bin/sh\necho -n error >&2\necho -n output\nexit 2")
647 os.chmod(self.reporter.smart_update_filename, 0755)
648 deferred = Deferred()
649
650 def do_test():
651 result = self.reporter.run_smart_update()
652
653 def callback(ignore):
654 self.assertMessages(message_store.get_pending_messages(),
655 [{"type": "package-reporter-result",
656 "code": 2, "err": u"error"}])
657 result.addCallback(callback)
658 result.chainDeferred(deferred)
659
660 reactor.callWhenRunning(do_test)
661 return deferred
662
663 def test_run_smart_update_report_no_sources(self):
664 """
665 L{PackageReporter.run_smart_update} reports a failure if smart
666 succeeds but there are no APT sources defined. Smart doesn't
667 fail if there are no sources, but we fake a failure in order to
668 re-use the PackageReporterAlert on the server.
669 """
670 self.facade.reset_channels()
671 message_store = self.broker_service.message_store
672 message_store.set_accepted_types(["package-reporter-result"])
673 self.reporter.smart_update_filename = self.makeFile(
674 "#!/bin/sh\necho -n error >&2\necho -n output\nexit 0")
675 os.chmod(self.reporter.smart_update_filename, 0755)
676 deferred = Deferred()
677
678 def do_test():
679 result = self.reporter.run_smart_update()
680
681 def callback(ignore):
682 error = "There are no APT sources configured in %s or %s." % (
683 self.reporter.sources_list_filename,
684 self.reporter.sources_list_directory)
685 self.assertMessages(message_store.get_pending_messages(),
686 [{"type": "package-reporter-result",
687 "code": 1, "err": error}])
688 result.addCallback(callback)
689 result.chainDeferred(deferred)
690
691 reactor.callWhenRunning(do_test)
692 return deferred
693
694 def test_run_smart_update_report_smart_failure_no_sources(self):
695 """
696 If L{PackageReporter.run_smart_update} fails and there are no
697 APT sources configured, the Smart error takes precedence.
698 """
699 self.facade.reset_channels()
700 message_store = self.broker_service.message_store
701 message_store.set_accepted_types(["package-reporter-result"])
702 self.reporter.smart_update_filename = self.makeFile(
703 "#!/bin/sh\necho -n error >&2\necho -n output\nexit 2")
704 os.chmod(self.reporter.smart_update_filename, 0755)
705 deferred = Deferred()
706
707 def do_test():
708 result = self.reporter.run_smart_update()
709
710 def callback(ignore):
711 self.assertMessages(message_store.get_pending_messages(),
712 [{"type": "package-reporter-result",
713 "code": 2, "err": u"error"}])
714 result.addCallback(callback)
715 result.chainDeferred(deferred)
716
717 reactor.callWhenRunning(do_test)
718 return deferred
719
720 def test_run_smart_update_report_success(self):
721 """
722 L{PackageReporter.run_smart_update} also reports success to be able to
723 know the proper state of the client.
724 """
725 message_store = self.broker_service.message_store
726 message_store.set_accepted_types(["package-reporter-result"])
727 self.reporter.smart_update_filename = self.makeFile(
728 "#!/bin/sh\necho -n error >&2\necho -n output\nexit 0")
729 os.chmod(self.reporter.smart_update_filename, 0755)
730 deferred = Deferred()
731
732 def do_test():
733 result = self.reporter.run_smart_update()
734
735 def callback(ignore):
736 self.assertMessages(message_store.get_pending_messages(),
737 [{"type": "package-reporter-result",
738 "code": 0, "err": u"error"}])
739 result.addCallback(callback)
740 result.chainDeferred(deferred)
741
742 reactor.callWhenRunning(do_test)
743 return deferred
744
745 def test_run_smart_update_warns_exit_code_1_and_non_empty_stderr(self):
746 """
747 The L{PackageReporter.run_smart_update} method should log a warning
748 in case smart-update terminates with exit code 1 and non empty stderr.
749 """
750 self.reporter.smart_update_filename = self.makeFile(
751 "#!/bin/sh\necho -n \"error \" >&2\nexit 1")
752 os.chmod(self.reporter.smart_update_filename, 0755)
753 logging_mock = self.mocker.replace("logging.warning")
754 logging_mock("'%s' exited with status 1"
755 " (error )" % self.reporter.smart_update_filename)
756 self.mocker.replay()
757 deferred = Deferred()
758
759 def do_test():
760 result = self.reporter.run_smart_update()
761
762 def callback((out, err, code)):
763 self.assertEqual(out, "")
764 self.assertEqual(err, "error ")
765 self.assertEqual(code, 1)
766 result.addCallback(callback)
767 result.chainDeferred(deferred)
768
769 reactor.callWhenRunning(do_test)
770 return deferred
771
772 def test_run_smart_update_ignores_exit_code_1_and_empty_output(self):
773 """
774 The L{PackageReporter.run_smart_update} method should not log anything
775 in case smart-update terminates with exit code 1 and output containing
776 only a newline character.
777 """
778 self.reporter.smart_update_filename = self.makeFile(
779 "#!/bin/sh\necho\nexit 1")
780 os.chmod(self.reporter.smart_update_filename, 0755)
781 logging_mock = self.mocker.replace("logging.warning")
782 self.expect(logging_mock(ANY)).count(0)
783 self.mocker.replay()
784 deferred = Deferred()
785
786 def do_test():
787
788 result = self.reporter.run_smart_update()
789
790 def callback((out, err, code)):
791 self.assertEqual(out, "\n")
792 self.assertEqual(err, "")
793 self.assertEqual(code, 1)
794 result.addCallback(callback)
795 result.chainDeferred(deferred)
796
797 reactor.callWhenRunning(do_test)
798 return deferred
799
800 def test_run_smart_update_touches_stamp_file(self):
801 """
802 The L{PackageReporter.run_smart_update} method touches a stamp file
803 after running the smart-update wrapper.
804 """
805 self.reporter.sources_list_filename = "/I/Dont/Exist"
806 self.reporter.smart_update_filename = "/bin/true"
807 deferred = Deferred()
808
809 def do_test():
810
811 result = self.reporter.run_smart_update()
812
813 def callback(ignored):
814 self.assertTrue(
815 os.path.exists(self.config.update_stamp_filename))
816 result.addCallback(callback)
817 result.chainDeferred(deferred)
818
819 reactor.callWhenRunning(do_test)
820 return deferred
821
822 def test_remove_expired_hash_id_request(self):570 def test_remove_expired_hash_id_request(self):
823 request = self.store.add_hash_id_request(["hash1"])571 request = self.store.add_hash_id_request(["hash1"])
824 request.message_id = 9999572 request.message_id = 9999
@@ -1133,11 +881,7 @@
1133881
1134 upgrade_hash = self.set_pkg1_upgradable()882 upgrade_hash = self.set_pkg1_upgradable()
1135 self.set_pkg1_installed()883 self.set_pkg1_installed()
1136 # Don't reload for SmartFacade, since the hash of pkg2 will be884 self.facade.reload_channels()
1137 # changed, resulting in that name2 will be considered not
1138 # available..
1139 if isinstance(self.facade, AptFacade):
1140 self.facade.reload_channels()
1141885
1142 self.store.set_hash_ids(886 self.store.set_hash_ids(
1143 {HASH1: 1, upgrade_hash: 2, HASH3: 3})887 {HASH1: 1, upgrade_hash: 2, HASH3: 3})
@@ -1193,20 +937,13 @@
1193 result = self.reporter.detect_packages_changes()937 result = self.reporter.detect_packages_changes()
1194 return result.addCallback(got_result)938 return result.addCallback(got_result)
1195939
1196 def test_detect_changes_considers_packages_and_locks_changes(self):940 def test_detect_changes_considers_packages_changes(self):
1197 """941 """
1198 The L{PackageReporter.detect_changes} method considers both package and942 The L{PackageReporter.detect_changes} method package changes.
1199 package locks changes. It also releases smart locks by calling the
1200 L{SmartFacade.deinit} method.
1201 """943 """
1202 reporter_mock = self.mocker.patch(self.reporter)944 reporter_mock = self.mocker.patch(self.reporter)
1203 reporter_mock.detect_packages_changes()945 reporter_mock.detect_packages_changes()
1204 self.mocker.result(succeed(True))946 self.mocker.result(succeed(True))
1205 reporter_mock.detect_package_locks_changes()
1206 self.mocker.result(succeed(True))
1207
1208 facade_mock = self.mocker.patch(self.facade)
1209 facade_mock.deinit()
1210947
1211 self.mocker.replay()948 self.mocker.replay()
1212 return self.reporter.detect_changes()949 return self.reporter.detect_changes()
@@ -1219,8 +956,6 @@
1219 """956 """
1220 reporter_mock = self.mocker.patch(self.reporter)957 reporter_mock = self.mocker.patch(self.reporter)
1221 reporter_mock.detect_packages_changes()958 reporter_mock.detect_packages_changes()
1222 self.mocker.result(succeed(False))
1223 reporter_mock.detect_package_locks_changes()
1224 self.mocker.result(succeed(True))959 self.mocker.result(succeed(True))
1225 callback = self.mocker.mock()960 callback = self.mocker.mock()
1226 callback()961 callback()
@@ -1236,11 +971,7 @@
1236971
1237 results = [Deferred() for i in range(7)]972 results = [Deferred() for i in range(7)]
1238973
1239 # Either the Apt or Smart cache will be updated, not both.974 reporter_mock.run_apt_update()
1240 if isinstance(self.facade, AptFacade):
1241 reporter_mock.run_apt_update()
1242 else:
1243 reporter_mock.run_smart_update()
1244 self.mocker.result(results[0])975 self.mocker.result(results[0])
1245976
1246 reporter_mock.fetch_hash_id_db()977 reporter_mock.fetch_hash_id_db()
@@ -1306,16 +1037,19 @@
1306 This is done in the reporter so that we know it happens when1037 This is done in the reporter so that we know it happens when
1307 no other reporter is possibly running at the same time.1038 no other reporter is possibly running at the same time.
1308 """1039 """
1040 self._add_system_package("foo")
1041 self.facade.reload_channels()
1042 [foo] = self.facade.get_packages_by_name("foo")
1043 foo_hash = self.facade.get_package_hash(foo)
1044 self.facade.set_package_hold(foo)
1045 self.facade.reload_channels()
1309 message_store = self.broker_service.message_store1046 message_store = self.broker_service.message_store
1310 message_store.set_accepted_types(["package-locks"])1047 message_store.set_accepted_types(["package-locks"])
1311 self.store.set_hash_ids({HASH1: 3, HASH2: 4})1048 self.store.set_hash_ids({foo_hash: 3, HASH2: 4})
1312 self.store.add_available([1])1049 self.store.add_available([1])
1313 self.store.add_available_upgrades([2])1050 self.store.add_available_upgrades([2])
1314 self.store.add_installed([2])1051 self.store.add_installed([2])
1315 self.store.add_locked([3])1052 self.store.add_locked([3])
1316 self.store.add_package_locks([("name1", None, None)])
1317 if self.facade.supports_package_locks:
1318 self.facade.set_package_lock("name1")
1319 request1 = self.store.add_hash_id_request(["hash3"])1053 request1 = self.store.add_hash_id_request(["hash3"])
1320 request2 = self.store.add_hash_id_request(["hash4"])1054 request2 = self.store.add_hash_id_request(["hash4"])
13211055
@@ -1328,12 +1062,6 @@
1328 self.assertEqual(self.store.get_available_upgrades(), [2])1062 self.assertEqual(self.store.get_available_upgrades(), [2])
1329 self.assertEqual(self.store.get_available(), [1])1063 self.assertEqual(self.store.get_available(), [1])
1330 self.assertEqual(self.store.get_installed(), [2])1064 self.assertEqual(self.store.get_installed(), [2])
1331 # XXX: Don't check get_locked() and get_package_locks() until
1332 # package locks are implemented in AptFacade.
1333 if not isinstance(self.facade, AptFacade):
1334 self.assertEqual(self.store.get_locked(), [3])
1335 self.assertEqual(
1336 self.store.get_package_locks(), [("name1", "", "")])
1337 self.assertEqual(self.store.get_hash_id_request(request1.id).id,1065 self.assertEqual(self.store.get_hash_id_request(request1.id).id,
1338 request1.id)1066 request1.id)
13391067
@@ -1344,7 +1072,7 @@
1344 def check_result(result):1072 def check_result(result):
13451073
1346 # The hashes should not go away.1074 # The hashes should not go away.
1347 hash1 = self.store.get_hash_id(HASH1)1075 hash1 = self.store.get_hash_id(foo_hash)
1348 hash2 = self.store.get_hash_id(HASH2)1076 hash2 = self.store.get_hash_id(HASH2)
1349 self.assertEqual([hash1, hash2], [3, 4])1077 self.assertEqual([hash1, hash2], [3, 4])
13501078
@@ -1353,12 +1081,9 @@
13531081
1354 # After running the resychronize task, detect_packages_changes is1082 # After running the resychronize task, detect_packages_changes is
1355 # called, and the existing known hashes are made available.1083 # called, and the existing known hashes are made available.
1356 self.assertEqual(self.store.get_available(), [3, 4])1084 self.assertEqual(self.store.get_available(), [4])
1357 self.assertEqual(self.store.get_installed(), [])1085 self.assertEqual(self.store.get_installed(), [3])
1358 # XXX: Don't check get_locked() until package locks are1086 self.assertEqual(self.store.get_locked(), [3])
1359 # implemented in AptFacade.
1360 if not isinstance(self.facade, AptFacade):
1361 self.assertEqual(self.store.get_locked(), [3])
13621087
1363 # The two original hash id requests should be still there, and1088 # The two original hash id requests should be still there, and
1364 # a new hash id request should also be detected for HASH3.1089 # a new hash id request should also be detected for HASH3.
@@ -1371,314 +1096,14 @@
1371 elif request.id == request2.id:1096 elif request.id == request2.id:
1372 self.assertEqual(request.hashes, ["hash4"])1097 self.assertEqual(request.hashes, ["hash4"])
1373 elif not new_request_found:1098 elif not new_request_found:
1374 self.assertEqual(request.hashes, [HASH3])1099 self.assertEqual(request.hashes, [HASH3, HASH1])
1375 else:1100 else:
1376 self.fail("Unexpected hash-id request!")1101 self.fail("Unexpected hash-id request!")
1377 self.assertEqual(requests_count, 3)1102 self.assertEqual(requests_count, 3)
13781103
1379 # XXX: Don't check for package-locks messages until package
1380 # locks are implemented in AptFacade.
1381 if not isinstance(self.facade, AptFacade):
1382 self.assertMessages(message_store.get_pending_messages(),
1383 [{"type": "package-locks",
1384 "created": [("name1", "", "")]}])
1385
1386 deferred.addCallback(check_result)1104 deferred.addCallback(check_result)
1387 return deferred1105 return deferred
13881106
1389
1390class PackageReporterSmartTest(LandscapeTest, PackageReporterTestMixin):
1391
1392 helpers = [SmartFacadeHelper, BrokerServiceHelper]
1393
1394 def setUp(self):
1395
1396 def set_up(ignored):
1397 self.store = PackageStore(self.makeFile())
1398 self.config = PackageReporterConfiguration()
1399 self.reporter = PackageReporter(
1400 self.store, self.facade, self.remote, self.config)
1401 self.config.data_path = self.makeDir()
1402 os.mkdir(self.config.package_directory)
1403
1404 result = super(PackageReporterSmartTest, self).setUp()
1405 return result.addCallback(set_up)
1406
1407 def _clear_repository(self):
1408 """Remove all packages from self.repository."""
1409 for filename in glob.glob(self.repository_dir + "/*"):
1410 os.unlink(filename)
1411
1412 def set_pkg1_upgradable(self):
1413 """Make it so that package "name1" is considered to be upgradable.
1414
1415 Return the hash of the package that upgrades "name1".
1416 """
1417 previous = self.Facade.channels_reloaded
1418
1419 def callback(self):
1420 from smart.backends.deb.base import DebUpgrades
1421 previous(self)
1422 pkg2 = self.get_packages_by_name("name2")[0]
1423 pkg2.upgrades += (DebUpgrades("name1", "=", "version1-release1"),)
1424 self.reload_cache() # Relink relations.
1425 self.Facade.channels_reloaded = callback
1426 return HASH2
1427
1428 def set_pkg1_installed(self):
1429 """Make it so that package "name1" is considered installed."""
1430 previous = self.Facade.channels_reloaded
1431
1432 def callback(self):
1433 previous(self)
1434 self.get_packages_by_name("name1")[0].installed = True
1435 self.Facade.channels_reloaded = callback
1436
1437 def test_detect_packages_changes_with_locked(self):
1438 """
1439 If Smart indicates locked packages we didn't know about, report
1440 them to the server.
1441 """
1442 message_store = self.broker_service.message_store
1443 message_store.set_accepted_types(["packages"])
1444
1445 self.facade.set_package_lock("name1")
1446 self.facade.set_package_lock("name2", ">=", "version2")
1447
1448 self.store.set_hash_ids({HASH1: 1, HASH2: 2})
1449 self.store.add_available([1, 2])
1450
1451 def got_result(result):
1452 self.assertMessages(message_store.get_pending_messages(),
1453 [{"type": "packages", "locked": [1, 2]}])
1454 self.assertEqual(sorted(self.store.get_locked()), [1, 2])
1455
1456 result = self.reporter.detect_packages_changes()
1457 return result.addCallback(got_result)
1458
1459 def test_detect_packages_changes_with_locked_and_ranges(self):
1460 """
1461 Ranges are used when reporting changes to 3 or more locked packages
1462 having consecutive ids.
1463 """
1464 message_store = self.broker_service.message_store
1465 message_store.set_accepted_types(["packages"])
1466
1467 self.facade.set_package_lock("name1")
1468 self.facade.set_package_lock("name2", ">=", "version2")
1469 self.facade.set_package_lock("name3", "<", "version4")
1470
1471 self.store.set_hash_ids({HASH1: 1, HASH2: 2, HASH3: 3})
1472 self.store.add_available([1, 2, 3])
1473
1474 def got_result(result):
1475 self.assertMessages(message_store.get_pending_messages(),
1476 [{"type": "packages", "locked": [(1, 3)]}])
1477 self.assertEqual(sorted(self.store.get_locked()), [1, 2, 3])
1478
1479 result = self.reporter.detect_packages_changes()
1480 return result.addCallback(got_result)
1481
1482 def test_detect_packages_changes_with_locked_with_unknown_hash(self):
1483 """
1484 Locked packages whose hashes are unknown don't get reported.
1485 """
1486 self.facade.set_package_lock("name1")
1487
1488 def got_result(result):
1489 self.assertEqual(self.store.get_locked(), [])
1490
1491 result = self.reporter.detect_packages_changes()
1492 return result.addCallback(got_result)
1493
1494 def test_detect_packages_changes_with_locked_and_previously_known(self):
1495 """
1496 We don't report locked packages we already know about.
1497 """
1498 message_store = self.broker_service.message_store
1499 message_store.set_accepted_types(["packages"])
1500
1501 self.facade.set_package_lock("name1")
1502 self.facade.set_package_lock("name2", ">=", "version2")
1503
1504 self.store.set_hash_ids({HASH1: 1, HASH2: 2})
1505 self.store.add_available([1, 2])
1506 self.store.add_locked([1])
1507
1508 def got_result(result):
1509 self.assertMessages(message_store.get_pending_messages(),
1510 [{"type": "packages", "locked": [2]}])
1511
1512 self.assertEqual(sorted(self.store.get_locked()), [1, 2])
1513
1514 result = self.reporter.detect_packages_changes()
1515 return result.addCallback(got_result)
1516
1517 def test_detect_packages_changes_with_not_locked(self):
1518 """
1519 We report when a package was previously locked and isn't anymore.
1520 """
1521 message_store = self.broker_service.message_store
1522 message_store.set_accepted_types(["packages"])
1523
1524 self.store.set_hash_ids({HASH1: 1})
1525 self.store.add_available([1])
1526 self.store.add_locked([1])
1527
1528 def got_result(result):
1529 self.assertMessages(message_store.get_pending_messages(),
1530 [{"type": "packages", "not-locked": [1]}])
1531 self.assertEqual(self.store.get_locked(), [])
1532
1533 result = self.reporter.detect_packages_changes()
1534 return result.addCallback(got_result)
1535
1536 def test_detect_package_locks_changes_with_create_locks(self):
1537 """
1538 If Smart indicates package locks we didn't know about, report
1539 them to the server.
1540 """
1541 message_store = self.broker_service.message_store
1542 message_store.set_accepted_types(["package-locks"])
1543
1544 self.facade.set_package_lock("name")
1545
1546 logging_mock = self.mocker.replace("logging.info")
1547 logging_mock("Queuing message with changes in known package locks:"
1548 " 1 created, 0 deleted.")
1549 self.mocker.replay()
1550
1551 def got_result(result):
1552 self.assertMessages(message_store.get_pending_messages(),
1553 [{"type": "package-locks",
1554 "created": [("name", "", "")]}])
1555 self.assertEqual(self.store.get_package_locks(),
1556 [("name", "", "")])
1557
1558 result = self.reporter.detect_package_locks_changes()
1559 return result.addCallback(got_result)
1560
1561 def test_detect_package_locks_changes_with_already_known_locks(self):
1562 """
1563 We don't report changes about locks we already know about.
1564 """
1565 message_store = self.broker_service.message_store
1566 message_store.set_accepted_types(["package-locks"])
1567
1568 self.facade.set_package_lock("name1")
1569 self.facade.set_package_lock("name2", "<", "1.2")
1570
1571 self.store.add_package_locks([("name1", "", "")])
1572
1573 logging_mock = self.mocker.replace("logging.info")
1574 logging_mock("Queuing message with changes in known package locks:"
1575 " 1 created, 0 deleted.")
1576 self.mocker.replay()
1577
1578 def got_result(result):
1579 self.assertMessages(message_store.get_pending_messages(),
1580 [{"type": "package-locks",
1581 "created": [("name2", "<", "1.2")]}])
1582 self.assertEqual(sorted(self.store.get_package_locks()),
1583 [("name1", "", ""),
1584 ("name2", "<", "1.2")])
1585
1586 result = self.reporter.detect_package_locks_changes()
1587 return result.addCallback(got_result)
1588
1589 def test_detect_package_locks_changes_with_deleted_locks(self):
1590 """
1591 If Smart indicates newly unset package locks, report them to the
1592 server.
1593 """
1594 message_store = self.broker_service.message_store
1595 message_store.set_accepted_types(["package-locks"])
1596
1597 self.store.add_package_locks([("name1", "", "")])
1598
1599 logging_mock = self.mocker.replace("logging.info")
1600 logging_mock("Queuing message with changes in known package locks:"
1601 " 0 created, 1 deleted.")
1602 self.mocker.replay()
1603
1604 def got_result(result):
1605 self.assertMessages(message_store.get_pending_messages(),
1606 [{"type": "package-locks",
1607 "deleted": [("name1", "", "")]}])
1608 self.assertEqual(self.store.get_package_locks(), [])
1609
1610 result = self.reporter.detect_package_locks_changes()
1611 return result.addCallback(got_result)
1612
1613 def test_detect_package_locks_changes_with_locked_already_known(self):
1614 """
1615 If we didn't detect any change in the package locks, we don't send any
1616 message, and we return a deferred resulting in C{False}.
1617 """
1618 message_store = self.broker_service.message_store
1619 message_store.set_accepted_types(["package-locks"])
1620
1621 self.facade.set_package_lock("name1")
1622 self.store.add_package_locks([("name1", "", "")])
1623
1624 def got_result(result):
1625 self.assertFalse(result)
1626 self.assertMessages(message_store.get_pending_messages(), [])
1627
1628 result = self.reporter.detect_packages_changes()
1629 return result.addCallback(got_result)
1630
1631
1632class PackageReporterAptTest(LandscapeTest, PackageReporterTestMixin):
1633
1634 if not has_new_enough_apt:
1635 skip = "Can't use AptFacade on hardy"
1636
1637 helpers = [AptFacadeHelper, SimpleRepositoryHelper, BrokerServiceHelper]
1638
1639 Facade = AptFacade
1640
1641 def setUp(self):
1642
1643 def set_up(ignored):
1644 self.store = PackageStore(self.makeFile())
1645 self.config = PackageReporterConfiguration()
1646 self.reporter = PackageReporter(
1647 self.store, self.facade, self.remote, self.config)
1648 self.config.data_path = self.makeDir()
1649 os.mkdir(self.config.package_directory)
1650
1651 result = super(PackageReporterAptTest, self).setUp()
1652 return result.addCallback(set_up)
1653
1654 def _clear_repository(self):
1655 """Remove all packages from self.repository."""
1656 create_file(self.repository_dir + "/Packages", "")
1657
1658 def set_pkg1_upgradable(self):
1659 """Make it so that package "name1" is considered to be upgradable.
1660
1661 Return the hash of the package that upgrades "name1".
1662 """
1663 self._add_package_to_deb_dir(
1664 self.repository_dir, "name1", version="version2")
1665 self.facade.reload_channels()
1666 name1_upgrade = sorted(self.facade.get_packages_by_name("name1"))[1]
1667 return self.facade.get_package_hash(name1_upgrade)
1668
1669 def set_pkg1_installed(self):
1670 """Make it so that package "name1" is considered installed."""
1671 self._install_deb_file(os.path.join(self.repository_dir, PKGNAME1))
1672
1673 def _make_fake_apt_update(self, out="output", err="error", code=0):
1674 """Create a fake apt-update executable"""
1675 self.reporter.apt_update_filename = self.makeFile(
1676 "#!/bin/sh\n"
1677 "echo -n %s\n"
1678 "echo -n %s >&2\n"
1679 "exit %d" % (out, err, code))
1680 os.chmod(self.reporter.apt_update_filename, 0755)
1681
1682 def test_run_apt_update(self):1107 def test_run_apt_update(self):
1683 """1108 """
1684 The L{PackageReporter.run_apt_update} method should run apt-update.1109 The L{PackageReporter.run_apt_update} method should run apt-update.
@@ -1708,13 +1133,13 @@
1708 reactor.callWhenRunning(do_test)1133 reactor.callWhenRunning(do_test)
1709 return deferred1134 return deferred
17101135
1711 def test_run_apt_update_with_force_smart_update(self):1136 def test_run_apt_update_with_force_apt_update(self):
1712 """1137 """
1713 L{PackageReporter.run_apt_update} forces an apt-update run if the1138 L{PackageReporter.run_apt_update} forces an apt-update run if the
1714 '--force-smart-update' command line option was passed.1139 '--force-apt-update' command line option was passed.
1715 """1140 """
1716 self.makeFile("", path=self.config.update_stamp_filename)1141 self.makeFile("", path=self.config.update_stamp_filename)
1717 self.config.load(["--force-smart-update"])1142 self.config.load(["--force-apt-update"])
1718 self._make_fake_apt_update()1143 self._make_fake_apt_update()
17191144
1720 deferred = Deferred()1145 deferred = Deferred()
@@ -1730,7 +1155,7 @@
1730 reactor.callWhenRunning(do_test)1155 reactor.callWhenRunning(do_test)
1731 return deferred1156 return deferred
17321157
1733 def test_run_apt_update_with_force_smart_update_if_sources_changed(self):1158 def test_run_apt_update_with_force_apt_update_if_sources_changed(self):
1734 """1159 """
1735 L{PackageReporter.run_apt_update} forces an apt-update run if the APT1160 L{PackageReporter.run_apt_update} forces an apt-update run if the APT
1736 sources.list file has changed.1161 sources.list file has changed.
@@ -1929,7 +1354,22 @@
1929 return deferred1354 return deferred
19301355
19311356
1932class GlobalPackageReporterTestMixin(object):1357class GlobalPackageReporterAptTest(LandscapeTest):
1358
1359 helpers = [AptFacadeHelper, SimpleRepositoryHelper, BrokerServiceHelper]
1360
1361 def setUp(self):
1362
1363 def set_up(ignored):
1364 self.store = FakePackageStore(self.makeFile())
1365 self.config = PackageReporterConfiguration()
1366 self.reporter = FakeGlobalReporter(
1367 self.store, self.facade, self.remote, self.config)
1368 self.config.data_path = self.makeDir()
1369 os.mkdir(self.config.package_directory)
1370
1371 result = super(GlobalPackageReporterAptTest, self).setUp()
1372 return result.addCallback(set_up)
19331373
1934 def test_store_messages(self):1374 def test_store_messages(self):
1935 """1375 """
@@ -1937,13 +1377,13 @@
1937 """1377 """
1938 message_store = self.broker_service.message_store1378 message_store = self.broker_service.message_store
1939 message_store.set_accepted_types(["package-reporter-result"])1379 message_store.set_accepted_types(["package-reporter-result"])
1940 self.reporter.smart_update_filename = self.makeFile(1380 self.reporter.apt_update_filename = self.makeFile(
1941 "#!/bin/sh\necho -n error >&2\necho -n output\nexit 0")1381 "#!/bin/sh\necho -n error >&2\necho -n output\nexit 0")
1942 os.chmod(self.reporter.smart_update_filename, 0755)1382 os.chmod(self.reporter.apt_update_filename, 0755)
1943 deferred = Deferred()1383 deferred = Deferred()
19441384
1945 def do_test():1385 def do_test():
1946 result = self.reporter.run_smart_update()1386 result = self.reporter.run_apt_update()
19471387
1948 def callback(ignore):1388 def callback(ignore):
1949 message = {"type": "package-reporter-result",1389 message = {"type": "package-reporter-result",
@@ -1962,47 +1402,6 @@
1962 return deferred1402 return deferred
19631403
19641404
1965class GlobalPackageReporterAptTest(LandscapeTest,
1966 GlobalPackageReporterTestMixin):
1967
1968 if not has_new_enough_apt:
1969 skip = "Can't use AptFacade on hardy"
1970
1971 helpers = [AptFacadeHelper, SimpleRepositoryHelper, BrokerServiceHelper]
1972
1973 def setUp(self):
1974
1975 def set_up(ignored):
1976 self.store = FakePackageStore(self.makeFile())
1977 self.config = PackageReporterConfiguration()
1978 self.reporter = FakeGlobalReporter(
1979 self.store, self.facade, self.remote, self.config)
1980 self.config.data_path = self.makeDir()
1981 os.mkdir(self.config.package_directory)
1982
1983 result = super(GlobalPackageReporterAptTest, self).setUp()
1984 return result.addCallback(set_up)
1985
1986
1987class GlobalPackageReporterSmartTest(LandscapeTest,
1988 GlobalPackageReporterTestMixin):
1989
1990 helpers = [SmartFacadeHelper, BrokerServiceHelper]
1991
1992 def setUp(self):
1993
1994 def set_up(ignored):
1995 self.store = FakePackageStore(self.makeFile())
1996 self.config = PackageReporterConfiguration()
1997 self.reporter = FakeGlobalReporter(
1998 self.store, self.facade, self.remote, self.config)
1999 self.config.data_path = self.makeDir()
2000 os.mkdir(self.config.package_directory)
2001
2002 result = super(GlobalPackageReporterSmartTest, self).setUp()
2003 return result.addCallback(set_up)
2004
2005
2006class FakePackageReporterTest(LandscapeTest):1405class FakePackageReporterTest(LandscapeTest):
20071406
2008 helpers = [EnvironSaverHelper, BrokerServiceHelper]1407 helpers = [EnvironSaverHelper, BrokerServiceHelper]
20091408
=== modified file 'landscape/package/tests/test_skeleton.py'
--- landscape/package/tests/test_skeleton.py 2012-03-19 09:33:34 +0000
+++ landscape/package/tests/test_skeleton.py 2012-06-04 14:08:28 +0000
@@ -1,21 +1,10 @@
1try:
2 import smart
3 from smart.cache import Package
4except ImportError:
5 # Smart is optional if AptFacade is being used.
6 pass
7
8from landscape.package.interface import (
9 install_landscape_interface, uninstall_landscape_interface)
10
11from landscape.package.facade import has_new_enough_apt
12from landscape.package.skeleton import (1from landscape.package.skeleton import (
13 build_skeleton, PackageTypeError, build_skeleton_apt, DEB_PROVIDES,2 build_skeleton_apt, DEB_PROVIDES,
14 DEB_NAME_PROVIDES, DEB_REQUIRES, DEB_OR_REQUIRES, DEB_UPGRADES,3 DEB_NAME_PROVIDES, DEB_REQUIRES, DEB_OR_REQUIRES, DEB_UPGRADES,
15 DEB_CONFLICTS)4 DEB_CONFLICTS)
165
17from landscape.package.tests.helpers import (6from landscape.package.tests.helpers import (
18 AptFacadeHelper, SmartHelper, HASH1, create_simple_repository, create_deb,7 AptFacadeHelper, HASH1, create_simple_repository, create_deb,
19 PKGNAME_MINIMAL, PKGDEB_MINIMAL, HASH_MINIMAL, PKGNAME_SIMPLE_RELATIONS,8 PKGNAME_MINIMAL, PKGDEB_MINIMAL, HASH_MINIMAL, PKGNAME_SIMPLE_RELATIONS,
20 PKGDEB_SIMPLE_RELATIONS, HASH_SIMPLE_RELATIONS, PKGNAME_VERSION_RELATIONS,9 PKGDEB_SIMPLE_RELATIONS, HASH_SIMPLE_RELATIONS, PKGNAME_VERSION_RELATIONS,
21 PKGDEB_VERSION_RELATIONS, HASH_VERSION_RELATIONS,10 PKGDEB_VERSION_RELATIONS, HASH_VERSION_RELATIONS,
@@ -47,16 +36,32 @@
47 PKGDEB_OR_RELATIONS)36 PKGDEB_OR_RELATIONS)
4837
4938
50class SkeletonTestMixin(object):39class SkeletonAptTest(LandscapeTest):
51 """Tests for building a skeleton from a package.40 """C{PackageSkeleton} tests for apt packages."""
5241
53 This class should be mixed in to test different backends, like smart42 helpers = [AptFacadeHelper, SkeletonTestHelper]
54 and apt.43
5544 def setUp(self):
56 The main test case classes need to implement C{get_package(name)} to45 super(SkeletonAptTest, self).setUp()
57 get a package by name, and C{build_skeleton(package, with_info,46 self.facade.add_channel_deb_dir(self.skeleton_repository_dir)
58 with_unicode}, which builds the skeleton.47 # Don't use reload_channels(), since that causes the test setup
59 """48 # depending on build_skeleton_apt working correctly, which makes
49 # it harder to do TDD for these tests.
50 self.facade._cache.open(None)
51 self.facade._cache.update(None)
52 self.facade._cache.open(None)
53
54 def get_package(self, name):
55 """Return the package with the specified name."""
56 # Don't use get_packages(), since that causes the test setup
57 # depending on build_skeleton_apt working correctly, which makes
58 # it harder to to TDD for these tests.
59 package = self.facade._cache[name]
60 return package.candidate
61
62 def build_skeleton(self, *args, **kwargs):
63 """Build the skeleton to be tested."""
64 return build_skeleton_apt(*args, **kwargs)
6065
61 def test_build_skeleton(self):66 def test_build_skeleton(self):
62 """67 """
@@ -253,67 +258,3 @@
253 (DEB_UPGRADES, "or-relations < 1.0")]258 (DEB_UPGRADES, "or-relations < 1.0")]
254 self.assertEqual(relations, skeleton.relations)259 self.assertEqual(relations, skeleton.relations)
255 self.assertEqual(HASH_OR_RELATIONS, skeleton.get_hash())260 self.assertEqual(HASH_OR_RELATIONS, skeleton.get_hash())
256
257
258class SmartSkeletonTest(LandscapeTest, SkeletonTestMixin):
259 """C{PackageSkeleton} tests for smart packages."""
260
261 helpers = [SmartHelper, SkeletonTestHelper]
262
263 def setUp(self):
264 super(SmartSkeletonTest, self).setUp()
265 install_landscape_interface()
266 self.ctrl = smart.init(interface="landscape", datadir=self.smart_dir)
267 smart.sysconf.set(
268 "channels", {"alias": {"type": "deb-dir",
269 "path": self.skeleton_repository_dir}})
270 self.ctrl.reloadChannels()
271 self.cache = self.ctrl.getCache()
272
273 def tearDown(self):
274 uninstall_landscape_interface()
275 super(SmartSkeletonTest, self).tearDown()
276
277 def get_package(self, name):
278 """Return the package with the specified name."""
279 [package] = self.cache.getPackages(name)
280 return package
281
282 def build_skeleton(self, *args, **kwargs):
283 """Build the skeleton to be tested."""
284 return build_skeleton(*args, **kwargs)
285
286 def test_refuse_to_build_non_debian_packages(self):
287 self.assertRaises(PackageTypeError, build_skeleton,
288 Package("name", "version"))
289
290
291class SkeletonAptTest(LandscapeTest, SkeletonTestMixin):
292 """C{PackageSkeleton} tests for apt packages."""
293
294 if not has_new_enough_apt:
295 skip = "Can't use AptFacade on hardy"
296
297 helpers = [AptFacadeHelper, SkeletonTestHelper]
298
299 def setUp(self):
300 super(SkeletonAptTest, self).setUp()
301 self.facade.add_channel_deb_dir(self.skeleton_repository_dir)
302 # Don't use reload_channels(), since that causes the test setup
303 # depending on build_skeleton_apt working correctly, which makes
304 # it harder to to TDD for these tests.
305 self.facade._cache.open(None)
306 self.facade._cache.update(None)
307 self.facade._cache.open(None)
308
309 def get_package(self, name):
310 """Return the package with the specified name."""
311 # Don't use get_packages(), since that causes the test setup
312 # depending on build_skeleton_apt working correctly, which makes
313 # it harder to to TDD for these tests.
314 package = self.facade._cache[name]
315 return package.candidate
316
317 def build_skeleton(self, *args, **kwargs):
318 """Build the skeleton to be tested."""
319 return build_skeleton_apt(*args, **kwargs)
320261
=== modified file 'landscape/package/tests/test_store.py'
--- landscape/package/tests/test_store.py 2011-07-18 15:16:18 +0000
+++ landscape/package/tests/test_store.py 2012-06-04 14:08:28 +0000
@@ -1,6 +1,5 @@
1import threading1import threading
2import time2import time
3import sys
43
5import sqlite34import sqlite3
6from landscape.tests.helpers import LandscapeTest5from landscape.tests.helpers import LandscapeTest
@@ -356,11 +355,9 @@
356355
357 database = sqlite3.connect(filename)356 database = sqlite3.connect(filename)
358 cursor = database.cursor()357 cursor = database.cursor()
359 for table in ["package_locks", "locked"]:358 cursor.execute("pragma table_info(locked)")
360 query = "pragma table_info(%s)" % table359 result = cursor.fetchall()
361 cursor.execute(query)360 self.assertTrue(len(result) > 0)
362 result = cursor.fetchall()
363 self.assertTrue(len(result) > 0)
364361
365 def test_add_and_get_locked(self):362 def test_add_and_get_locked(self):
366 """363 """
@@ -401,103 +398,6 @@
401 self.store1.clear_locked()398 self.store1.clear_locked()
402 self.assertEqual(self.store2.get_locked(), [])399 self.assertEqual(self.store2.get_locked(), [])
403400
404 def test_get_package_locks_with_no_lock(self):
405 """
406 L{PackageStore.get_package_locks} returns an empty list if no package
407 locks are stored.
408 """
409 self.assertEqual(self.store1.get_package_locks(), [])
410
411 def test_add_package_locks(self):
412 """
413 L{PackageStore.add_package_locks} adds a package lock to the store.
414 """
415 self.store1.add_package_locks([("name", "", "")])
416 self.assertEqual(self.store2.get_package_locks(),
417 [("name", "", "")])
418
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches