Merge lp:~ubuntu-branches/ubuntu/natty/landscape-client/natty-updates-201411191718 into lp:ubuntu/natty-updates/landscape-client
- Natty (11.04)
- natty-updates-201411191718
- Merge into natty-updates
Status: | Needs review |
---|---|
Proposed branch: | lp:~ubuntu-branches/ubuntu/natty/landscape-client/natty-updates-201411191718 |
Merge into: | lp:ubuntu/natty-updates/landscape-client |
Diff against target: |
15117 lines (+13869/-282) (has conflicts) 69 files modified
applications/landscape-client-settings.desktop.in (+17/-0) apt-update/Makefile (+7/-0) apt-update/apt-update.c (+81/-0) dbus-1/com.canonical.LandscapeClientRegistration.conf (+16/-0) dbus-1/com.canonical.LandscapeClientRegistration.service (+4/-0) dbus-1/com.canonical.LandscapeClientSettings.conf (+16/-0) dbus-1/com.canonical.LandscapeClientSettings.service (+4/-0) debian/changelog (+79/-0) debian/control (+43/-0) debian/landscape-client-ui-install.install (+4/-0) debian/landscape-client-ui.install (+9/-0) debian/landscape-client.templates (+55/-0) debian/po/templates.pot (+133/-0) debian/rules (+13/-0) dev/dns-server (+99/-0) glib-2.0/schemas/com.canonical.landscape-client-settings.gschema.xml (+37/-0) icons/preferences-management-service.svg (+62/-0) landscape/__init__.py (+4/-0) landscape/deployment.py (+10/-0) landscape/lib/dns.py (+78/-0) landscape/lib/tests/test_dns.py (+208/-0) landscape/manager/fakepackagemanager.py (+57/-0) landscape/manager/hardwareinfo.py (+28/-0) landscape/manager/tests/test_fakepackagemanager.py (+70/-0) landscape/manager/tests/test_hardwareinfo.py (+45/-0) landscape/monitor/packagemonitor.py (+53/-0) landscape/package/changer.py (+99/-1) landscape/package/facade.py (+1013/-58) landscape/package/interface.py.OTHER (+84/-0) landscape/package/reporter.py (+207/-0) landscape/package/skeleton.py (+91/-0) landscape/package/taskhandler.py (+12/-0) landscape/package/tests/helpers.py (+251/-116) landscape/package/tests/test_changer.py (+991/-62) landscape/package/tests/test_facade.py (+2946/-45) landscape/package/tests/test_reporter.py (+1077/-0) landscape/package/tests/test_skeleton.py (+323/-0) landscape/package/tests/test_taskhandler.py (+65/-0) landscape/ui/constants.py (+3/-0) landscape/ui/controller/app.py (+91/-0) landscape/ui/controller/configuration.py (+122/-0) landscape/ui/controller/tests/test_app.py (+137/-0) landscape/ui/controller/tests/test_configuration.py (+212/-0) landscape/ui/lib/polkit.py (+102/-0) landscape/ui/model/configuration/mechanism.py (+113/-0) landscape/ui/model/configuration/proxy.py (+110/-0) landscape/ui/model/configuration/state.py (+586/-0) landscape/ui/model/configuration/tests/test_mechanism.py (+210/-0) landscape/ui/model/configuration/tests/test_proxy.py (+193/-0) landscape/ui/model/configuration/tests/test_state.py (+645/-0) landscape/ui/model/configuration/tests/test_uisettings.py (+167/-0) landscape/ui/model/configuration/uisettings.py (+57/-0) landscape/ui/model/registration/mechanism.py (+177/-0) landscape/ui/model/registration/proxy.py (+139/-0) landscape/ui/model/registration/tests/test_mechanism.py (+107/-0) landscape/ui/model/registration/tests/test_proxy.py (+148/-0) landscape/ui/tests/helpers.py (+187/-0) landscape/ui/view/configuration.py (+356/-0) landscape/ui/view/tests/test_configuration.py (+605/-0) landscape/ui/view/ui/landscape-client-settings.glade (+490/-0) po/POTFILES.in (+8/-0) po/fr.po (+168/-0) po/landscape-client.pot (+170/-0) polkit-1/com.canonical.LandscapeClientSettings.policy.in (+21/-0) scripts/landscape-client-registration-mechanism (+15/-0) scripts/landscape-client-settings-mechanism (+17/-0) scripts/landscape-client-settings-ui (+35/-0) scripts/landscape-client-ui-install (+78/-0) setup.cfg (+9/-0) Conflict adding file applications. Moved existing file to applications.moved. Conflict adding file apt-update. Moved existing file to apt-update.moved. Conflict adding file dbus-1. Moved existing file to dbus-1.moved. Text conflict in debian/changelog Text conflict in debian/control Conflict adding file debian/landscape-client-ui-install.install. Moved existing file to debian/landscape-client-ui-install.install.moved. Conflict adding file debian/landscape-client-ui.install. Moved existing file to debian/landscape-client-ui.install.moved. Text conflict in debian/landscape-client.templates Text conflict in debian/po/templates.pot Text conflict in debian/rules Conflict adding file dev/dns-server. Moved existing file to dev/dns-server.moved. Conflict adding file glib-2.0. Moved existing file to glib-2.0.moved. Conflict adding file icons. Moved existing file to icons.moved. Text conflict in landscape/__init__.py Text conflict in landscape/deployment.py Conflict adding file landscape/lib/dns.py. Moved existing file to landscape/lib/dns.py.moved. Conflict adding file landscape/lib/tests/test_dns.py. Moved existing file to landscape/lib/tests/test_dns.py.moved. Conflict adding file landscape/manager/fakepackagemanager.py. Moved existing file to landscape/manager/fakepackagemanager.py.moved. Conflict adding file landscape/manager/hardwareinfo.py. Moved existing file to landscape/manager/hardwareinfo.py.moved. Conflict adding file landscape/manager/tests/test_fakepackagemanager.py. Moved existing file to landscape/manager/tests/test_fakepackagemanager.py.moved. Conflict adding file landscape/manager/tests/test_hardwareinfo.py. Moved existing file to landscape/manager/tests/test_hardwareinfo.py.moved. Text conflict in landscape/monitor/packagemonitor.py Text conflict in landscape/package/changer.py Text conflict in landscape/package/facade.py Contents conflict in landscape/package/interface.py Text conflict in landscape/package/reporter.py Text conflict in landscape/package/skeleton.py Text conflict in landscape/package/taskhandler.py Text conflict in landscape/package/tests/helpers.py Text conflict in landscape/package/tests/test_changer.py Text conflict in landscape/package/tests/test_facade.py Text conflict in landscape/package/tests/test_reporter.py Text conflict in landscape/package/tests/test_skeleton.py Text conflict in landscape/package/tests/test_taskhandler.py Conflict adding file landscape/ui. Moved existing file to landscape/ui.moved. Conflict adding file po. Moved existing file to po.moved. Conflict adding file polkit-1. Moved existing file to polkit-1.moved. Conflict adding file scripts/landscape-client-registration-mechanism. Moved existing file to scripts/landscape-client-registration-mechanism.moved. Conflict adding file scripts/landscape-client-settings-mechanism. Moved existing file to scripts/landscape-client-settings-mechanism.moved. Conflict adding file scripts/landscape-client-settings-ui. Moved existing file to scripts/landscape-client-settings-ui.moved. Conflict adding file scripts/landscape-client-ui-install. Moved existing file to scripts/landscape-client-ui-install.moved. Conflict adding file setup.cfg. Moved existing file to setup.cfg.moved. |
To merge this branch: | bzr merge lp:~ubuntu-branches/ubuntu/natty/landscape-client/natty-updates-201411191718 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Development Team | Pending | ||
Review via email: mp+242252@code.launchpad.net |
Commit message
Description of the change
The package importer has detected a possible inconsistency between the package history in the archive and the history in bzr. As the archive is authoritative the importer has made lp:ubuntu/natty-updates/landscape-client reflect what is in the archive and the old bzr branch has been pushed to lp:~ubuntu-branches/ubuntu/natty/landscape-client/natty-updates-201411191718. This merge proposal was created so that an Ubuntu developer can review the situations and perform a merge/upload if necessary. There are three typical cases where this can happen.
1. Where someone pushes a change to bzr and someone else uploads the package without that change. This is the reason that this check is done by the importer. If this appears to be the case then a merge/upload should be done if the changes that were in bzr are still desirable.
2. The importer incorrectly detected the above situation when someone made a change in bzr and then uploaded it.
3. The importer incorrectly detected the above situation when someone just uploaded a package and didn't touch bzr.
If this case doesn't appear to be the first situation then set the status of the merge proposal to "Rejected" and help avoid the problem in future by filing a bug at https:/
(this is an automatically generated message)
Unmerged revisions
Preview Diff
1 | === added directory 'applications' |
2 | === renamed directory 'applications' => 'applications.moved' |
3 | === added file 'applications/landscape-client-settings.desktop.in' |
4 | --- applications/landscape-client-settings.desktop.in 1970-01-01 00:00:00 +0000 |
5 | +++ applications/landscape-client-settings.desktop.in 2014-11-19 18:22:39 +0000 |
6 | @@ -0,0 +1,17 @@ |
7 | +[Desktop Entry] |
8 | +_Name=Management Service |
9 | +_Comment=Management Service Preferences |
10 | +Exec=landscape-client-ui-install |
11 | +Icon=preferences-management-service |
12 | +Terminal=False |
13 | +Type=Application |
14 | +StartupNotify=true |
15 | +Categories=GNOME;GTK;Settings;X-GNOME-SystemSettings;X-GNOME-Settings-Panel; |
16 | +OnlyShowIn=GNOME;Unity; |
17 | +X-GNOME-Bugzilla-Bugzilla=GNOME |
18 | +X-GNOME-Bugzilla-Product=gnome-control-center |
19 | +X-GNOME-Bugzilla-Component=sample |
20 | +X-GNOME-Bugzilla-Version=1.0.0 |
21 | +X-GNOME-Settings-Panel=sample |
22 | +X-GNOME-Keywords=device;system;information;memory;processor;version;default;application;fallback;preferred; |
23 | +X-Ubuntu-Gettext-Domain=landscape-client |
24 | |
25 | === added directory 'apt-update' |
26 | === renamed directory 'apt-update' => 'apt-update.moved' |
27 | === added file 'apt-update/Makefile' |
28 | --- apt-update/Makefile 1970-01-01 00:00:00 +0000 |
29 | +++ apt-update/Makefile 2014-11-19 18:22:39 +0000 |
30 | @@ -0,0 +1,7 @@ |
31 | +NAME = apt-update |
32 | + |
33 | +$(NAME): $(NAME).c |
34 | + $(CC) $(CFLAGS) -Wall $< -o $@ |
35 | + |
36 | +clean: |
37 | + rm -f $(NAME) |
38 | |
39 | === added file 'apt-update/apt-update.c' |
40 | --- apt-update/apt-update.c 1970-01-01 00:00:00 +0000 |
41 | +++ apt-update/apt-update.c 2014-11-19 18:22:39 +0000 |
42 | @@ -0,0 +1,81 @@ |
43 | +/* |
44 | + |
45 | + Copyright (c) 2011 Canonical, Ltd. |
46 | + |
47 | +*/ |
48 | + |
49 | +#define _GNU_SOURCE |
50 | +#include <sys/resource.h> |
51 | +#include <sys/types.h> |
52 | +#include <sys/stat.h> |
53 | +#include <grp.h> |
54 | +#include <unistd.h> |
55 | +#include <stdlib.h> |
56 | +#include <string.h> |
57 | +#include <errno.h> |
58 | +#include <stdio.h> |
59 | +#include <pwd.h> |
60 | + |
61 | +int main(int argc, char *argv[], char *envp[]) |
62 | +{ |
63 | + char *apt_argv[] = {"/usr/bin/apt-get", "-q", "update", NULL}; |
64 | + char *apt_envp[] = {"PATH=/bin:/usr/bin", NULL, NULL}; |
65 | + |
66 | + // Set the HOME environment variable |
67 | + struct passwd *pwd = getpwuid(geteuid()); |
68 | + if (!pwd) { |
69 | + fprintf(stderr, "error: Unable to find passwd entry for uid %d (%s)\n", |
70 | + geteuid(), strerror(errno)); |
71 | + exit(1); |
72 | + } |
73 | + if (asprintf(&apt_envp[1], "HOME=%s", pwd->pw_dir) == -1) { |
74 | + perror("error: Unable to create HOME environment variable"); |
75 | + exit(1); |
76 | + } |
77 | + |
78 | + // Drop any supplementary group |
79 | + if (setgroups(0, NULL) == -1) { |
80 | + perror("error: Unable to set supplementary groups IDs"); |
81 | + exit(1); |
82 | + } |
83 | + |
84 | + // Set real/effective gid and uid |
85 | + if (setregid(pwd->pw_gid, pwd->pw_gid) == -1) { |
86 | + fprintf(stderr, "error: Unable to set real and effective gid (%s)\n", |
87 | + strerror(errno)); |
88 | + exit(1); |
89 | + } |
90 | + if (setreuid(pwd->pw_uid, pwd->pw_uid) == -1) { |
91 | + perror("error: Unable to set real and effective uid"); |
92 | + exit(1); |
93 | + } |
94 | + |
95 | + // Close all file descriptors except the standard ones |
96 | + struct rlimit rlp; |
97 | + if (getrlimit(RLIMIT_NOFILE, &rlp) == -1) { |
98 | + perror("error: Unable to determine file descriptor limits"); |
99 | + exit(1); |
100 | + } |
101 | + int file_max; |
102 | + if (rlp.rlim_max == RLIM_INFINITY || rlp.rlim_max > 4096) |
103 | + file_max = 4096; |
104 | + else |
105 | + file_max = rlp.rlim_max; |
106 | + int file; |
107 | + for (file = 3; file < file_max; file++) { |
108 | + close(file); |
109 | + } |
110 | + |
111 | + // Set umask to 022 |
112 | + umask(S_IWGRP | S_IWOTH); |
113 | + |
114 | + if (chdir("/") == -1) { |
115 | + perror("error: Unable to change working directory"); |
116 | + exit(1); |
117 | + } |
118 | + |
119 | + // Run apt-get update |
120 | + execve(apt_argv[0], apt_argv, apt_envp); |
121 | + perror("error: Unable to execute apt-get"); |
122 | + return 1; |
123 | +} |
124 | |
125 | === added directory 'dbus-1' |
126 | === renamed directory 'dbus-1' => 'dbus-1.moved' |
127 | === added file 'dbus-1/com.canonical.LandscapeClientRegistration.conf' |
128 | --- dbus-1/com.canonical.LandscapeClientRegistration.conf 1970-01-01 00:00:00 +0000 |
129 | +++ dbus-1/com.canonical.LandscapeClientRegistration.conf 2014-11-19 18:22:39 +0000 |
130 | @@ -0,0 +1,16 @@ |
131 | +<?xml version="1.0" encoding="UTF-8"?> |
132 | +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" |
133 | +"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> |
134 | +<busconfig> |
135 | + <type>system</type> |
136 | + <policy user="root"> |
137 | + <allow own="com.canonical.LandscapeClientRegistration"/> |
138 | + <allow send_destination="com.canonical.LandscapeClientRegistration"/> |
139 | + <allow send_interface="com.canonical.LandscapeClientRegistration"/> |
140 | + </policy> |
141 | + |
142 | + <policy context="default"> |
143 | + <allow send_destination="com.canonical.LandscapeClientRegistration"/> |
144 | + <allow send_interface="com.canonical.LandscapeClientRegistration"/> |
145 | + </policy> |
146 | +</busconfig> |
147 | |
148 | === added file 'dbus-1/com.canonical.LandscapeClientRegistration.service' |
149 | --- dbus-1/com.canonical.LandscapeClientRegistration.service 1970-01-01 00:00:00 +0000 |
150 | +++ dbus-1/com.canonical.LandscapeClientRegistration.service 2014-11-19 18:22:39 +0000 |
151 | @@ -0,0 +1,4 @@ |
152 | +[D-BUS Service] |
153 | +Name=com.canonical.LandscapeClientRegistration |
154 | +Exec=/usr/bin/landscape-client-registration-mechanism |
155 | +User=root |
156 | \ No newline at end of file |
157 | |
158 | === added file 'dbus-1/com.canonical.LandscapeClientSettings.conf' |
159 | --- dbus-1/com.canonical.LandscapeClientSettings.conf 1970-01-01 00:00:00 +0000 |
160 | +++ dbus-1/com.canonical.LandscapeClientSettings.conf 2014-11-19 18:22:39 +0000 |
161 | @@ -0,0 +1,16 @@ |
162 | +<?xml version="1.0" encoding="UTF-8"?> |
163 | +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" |
164 | +"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> |
165 | +<busconfig> |
166 | + <type>system</type> |
167 | + <policy user="root"> |
168 | + <allow own="com.canonical.LandscapeClientSettings"/> |
169 | + <allow send_destination="com.canonical.LandscapeClientSettings"/> |
170 | + <allow send_interface="com.canonical.LandscapeClientSettings"/> |
171 | + </policy> |
172 | + |
173 | + <policy context="default"> |
174 | + <allow send_destination="com.canonical.LandscapeClientSettings"/> |
175 | + <allow send_interface="com.canonical.LandscapeClientSettings"/> |
176 | + </policy> |
177 | +</busconfig> |
178 | |
179 | === added file 'dbus-1/com.canonical.LandscapeClientSettings.service' |
180 | --- dbus-1/com.canonical.LandscapeClientSettings.service 1970-01-01 00:00:00 +0000 |
181 | +++ dbus-1/com.canonical.LandscapeClientSettings.service 2014-11-19 18:22:39 +0000 |
182 | @@ -0,0 +1,4 @@ |
183 | +[D-BUS Service] |
184 | +Name=com.canonical.LandscapeClientSettings |
185 | +Exec=/usr/bin/landscape-client-settings-mechanism |
186 | +User=root |
187 | \ No newline at end of file |
188 | |
189 | === modified file 'debian/changelog' |
190 | --- debian/changelog 2012-06-19 15:12:07 +0000 |
191 | +++ debian/changelog 2014-11-19 18:22:39 +0000 |
192 | @@ -1,3 +1,4 @@ |
193 | +<<<<<<< TREE |
194 | landscape-client (12.05-0ubuntu0.11.04) natty-proposed; urgency=low |
195 | |
196 | * New upstream release 12.05 (r561 in trunk) (LP: #1004678). |
197 | @@ -115,6 +116,84 @@ |
198 | |
199 | -- Andreas Hasenack <andreas@canonical.com> Tue, 10 Apr 2012 14:14:16 -0300 |
200 | |
201 | +======= |
202 | +landscape-client (12.04.3-0ubuntu0.11.04) UNRELEASED; urgency=low |
203 | + |
204 | + Tracking bug: LP: #978884 |
205 | + |
206 | + [ David Britton ] |
207 | + * Warn on unicode entry into settings UI (LP: #956612). |
208 | + * Sanitise hostname field in settings UI (LP: #954507). |
209 | + * Make it clear that the Landscape service is commercial (LP: #965850) |
210 | + * Further internationalize the settings UI (LP: #962899) |
211 | + * Depend on python-aptdaemon.gtk3widgets instead of python-aptdaemon and |
212 | + replace dependency on python-gobject by python-gi (LP: #961894) |
213 | + * Add i18n to the landscape-client-ui-install script. (LP: #961891) |
214 | + |
215 | + [ Andreas Hasenack ] |
216 | + * Fix default landscape hostname in glib schema. |
217 | + * dpkg test improvements to fix intermittent failures. |
218 | + * If ssl_public_key is supplied, use it also when fetching script |
219 | + attachments. This fixes the case of using script execution with |
220 | + attachments when the Landscape server is using a custom CA, |
221 | + most common in LDS deployments. (LP: #959846) |
222 | + * Make sure we have a PATH variable set before doing package |
223 | + activities, and also set it in the initscript for good measure. If |
224 | + the client was configured and restarted by the new UI configuration |
225 | + tool, PATH wasn't set, triggering an error in dpkg. (LP: #961190) |
226 | + * Make landscape-client-ui depend on landscape-client-ui-install, so |
227 | + that we get an entry in the system settings if just |
228 | + landscape-client-ui is installed. The actual entry comes from |
229 | + landscape-client-ui-install. |
230 | + * Optimization: when adding binaries, don't reload every repo, only the one |
231 | + containing the binaries. (LP: #954822) |
232 | + * Handle the case where the user clicks twice inadvertently on the |
233 | + Landscape icon in system settings and don't start a second copy of |
234 | + itself. (LP: #960211) |
235 | + * Change package management features to use APT instead of Smart (LP: #856244, |
236 | + #861707, #859615, #861345, #863239, #863259, #865270, #865272, #865285, |
237 | + #865273, #871641, #865299, #873196, #873939, #876493, #881973, #882438, |
238 | + #866014, #881998, #884142, #884151, #884131, #887037, #886208, #887578, |
239 | + #887947, #889067, #889069, #889087, #889099, #865303, #889113, #890605, |
240 | + #890606, #890609, #897416, #891855, #898681, #898683, #897656, #898542, |
241 | + #862212, #903202, #914734, #914735, #914737, #916301, #915280, #914742, |
242 | + #918925, #918175, #919179, #921664, #921699, #922582, #922511, #921712, |
243 | + #928750, #932136, #928941, #937411, #937567, #925543, #947803, #952973, |
244 | + #948142, #953136, #953906, #956590). |
245 | + * Add a GTK interface to configure the client (LP: #911279, #911666, #912163, |
246 | + #911665, #916300, #931937, #931937, #943622, #945025, #911279, #944652, |
247 | + #948464, #948416, #949158, #911671, #950864, #949208, #949147, #953070, |
248 | + #953292, #953463, #953034, #949200, #953026, #954499, #954516, #954285, |
249 | + #953065, #954414, #954332, #954542, #955966, #955139, #956030, #956119). |
250 | + * Add the ability to auto discover the server location on local deployment |
251 | + (LP: #917422, #927620, #917422, #928585, #929087, #932325, #948564) |
252 | + * Allow the client to accept arbitrary environment variables from the |
253 | + server for script execution (LP: #954999). |
254 | + * Make landscape-config exit non-zero when registration fails and |
255 | + --ok-no-register is not passed (LP: #271759). |
256 | + * Check for the content of /sys/bus/xen/devices to report a machine as a Xen |
257 | + VM instead of just relying on the existence of /sys/bus/xen (LP: #921970). |
258 | + * Make sure cloud registration succeeds if there is no kernel specified in |
259 | + the meta-data service (LP: #920453). |
260 | + * Report private and public IP adresses from the metadata service at cloud |
261 | + registration time (LP: #918366). |
262 | + * Add support for reporting hardware information using lshw (LP: #899002, |
263 | + #943975, #955734). |
264 | + * Add support for the new attachment service in script execution |
265 | + (LP: #893040). |
266 | + * Adds a new message type, 'register-provisioned-machine', which is meant |
267 | + to register computers using an OTP (LP: #881405). |
268 | + * Add local cloning option for load testing (LP: #872830, #925924). |
269 | + * Add more variables to preseeding (LP: #863204, #867710). |
270 | + * Allow the configuration of the ping interval (LP: #397884). |
271 | + * Add fake package reporters for load testing purposes (LP: #821571, |
272 | + #821570). |
273 | + * Report a package reporter error to the server if no APT sources are |
274 | + configured, to trigger a package reporter alert (LP: #823769). |
275 | + |
276 | + -- Andreas Hasenack <andreas@canonical.com> Tue, 10 Apr 2012 14:14:16 -0300 |
277 | + |
278 | +>>>>>>> MERGE-SOURCE |
279 | landscape-client (11.07.1.1-0ubuntu0.11.04.0) natty-proposed; urgency=low |
280 | |
281 | * Try to load the old persist file if the current one doesn't exist or is |
282 | |
283 | === modified file 'debian/control' |
284 | --- debian/control 2012-06-19 15:12:07 +0000 |
285 | +++ debian/control 2014-11-19 18:22:39 +0000 |
286 | @@ -3,8 +3,13 @@ |
287 | Priority: optional |
288 | Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> |
289 | XSBC-Original-Maintainer: Landscape Team <landscape-team@canonical.com> |
290 | +<<<<<<< TREE |
291 | Build-Depends: debhelper (>= 5), po-debconf, python-dev, python-central | python-support, lsb-release, gawk, python-twisted-core, python-distutils-extra |
292 | Standards-Version: 3.8.2 |
293 | +======= |
294 | +Build-Depends: debhelper (>= 5), po-debconf, python-dev, python-central | python-support, lsb-release, gawk, python-twisted-core, python-distutils-extra |
295 | +Standards-Version: 3.8.0 |
296 | +>>>>>>> MERGE-SOURCE |
297 | XS-Python-Version: >= 2.4, << 2.8 |
298 | |
299 | Package: landscape-common |
300 | @@ -36,8 +41,13 @@ |
301 | Depends: ${python:Depends}, ${misc:Depends}, ${extra:Depends}, |
302 | ${shlibs:Depends}, |
303 | python-twisted-web, |
304 | +<<<<<<< TREE |
305 | python-twisted-names, |
306 | landscape-common (= ${binary:Version}) |
307 | +======= |
308 | + python-twisted-names, |
309 | + landscape-common (>= ${Source-Version}) |
310 | +>>>>>>> MERGE-SOURCE |
311 | Suggests: ${extra:Suggests} |
312 | Description: The Landscape administration system client |
313 | Landscape is a web-based tool for managing Ubuntu systems. This |
314 | @@ -46,6 +56,7 @@ |
315 | . |
316 | This package provides the Landscape client and requires a Landscape account. |
317 | XB-Python-Version: ${python:Versions} |
318 | +<<<<<<< TREE |
319 | |
320 | Package: landscape-client-ui |
321 | Architecture: any |
322 | @@ -76,3 +87,35 @@ |
323 | . |
324 | This package provides an automatic installer for landscape-client-ui. |
325 | XB-Python-Version: ${python:Versions} |
326 | +======= |
327 | + |
328 | +Package: landscape-client-ui |
329 | +Architecture: any |
330 | +Depends: ${python:Depends}, ${misc:Depends}, |
331 | + landscape-client (>= ${Source-Version}), |
332 | + landscape-client-ui-install (>= ${Source-Version}), |
333 | + python-gi, |
334 | + python-dbus, |
335 | + policykit-1, |
336 | + gir1.2-notify-0.7, |
337 | + gir1.2-gtk-3.0 |
338 | +Description: The Landscape administration system client - UI configuration |
339 | + Landscape is a web-based tool for managing Ubuntu systems. |
340 | + . |
341 | + This package provides the Landscape client configuration UI. |
342 | +XB-Python-Version: ${python:Versions} |
343 | + |
344 | +Package: landscape-client-ui-install |
345 | +Architecture: any |
346 | +Depends: ${python:Depends}, ${misc:Depends}, |
347 | + python-gi, |
348 | + python-dbus, |
349 | + policykit-1, |
350 | + gir1.2-gtk-3.0, |
351 | + python-aptdaemon.gtk3widgets |
352 | +Description: The Landscape administration system client - UI installer |
353 | + Landscape is a web-based tool for managing Ubuntu systems. |
354 | + . |
355 | + This package provides an automatic installer for landscape-client-ui. |
356 | +XB-Python-Version: ${python:Versions} |
357 | +>>>>>>> MERGE-SOURCE |
358 | |
359 | === added file 'debian/landscape-client-ui-install.install' |
360 | --- debian/landscape-client-ui-install.install 1970-01-01 00:00:00 +0000 |
361 | +++ debian/landscape-client-ui-install.install 2014-11-19 18:22:39 +0000 |
362 | @@ -0,0 +1,4 @@ |
363 | +usr/bin/landscape-client-ui-install |
364 | +usr/share/applications/landscape-client-settings.desktop |
365 | +usr/share/icons/hicolor/scalable/apps/preferences-management-service.svg |
366 | +usr/share/locale |
367 | |
368 | === renamed file 'debian/landscape-client-ui-install.install' => 'debian/landscape-client-ui-install.install.moved' |
369 | === added file 'debian/landscape-client-ui.install' |
370 | --- debian/landscape-client-ui.install 1970-01-01 00:00:00 +0000 |
371 | +++ debian/landscape-client-ui.install 2014-11-19 18:22:39 +0000 |
372 | @@ -0,0 +1,9 @@ |
373 | +usr/bin/landscape-client-settings-mechanism |
374 | +usr/bin/landscape-client-registration-mechanism |
375 | +usr/bin/landscape-client-settings-ui |
376 | +usr/share/dbus-1/system-services/com.canonical.LandscapeClientSettings.service |
377 | +usr/share/dbus-1/system-services/com.canonical.LandscapeClientRegistration.service |
378 | +usr/share/polkit-1/actions/com.canonical.LandscapeClientSettings.policy |
379 | +etc/dbus-1/system.d/com.canonical.LandscapeClientSettings.conf |
380 | +etc/dbus-1/system.d/com.canonical.LandscapeClientRegistration.conf |
381 | +usr/share/glib-2.0/schemas/com.canonical.landscape-client-settings.gschema.xml |
382 | |
383 | === renamed file 'debian/landscape-client-ui.install' => 'debian/landscape-client-ui.install.moved' |
384 | === modified file 'debian/landscape-client.templates' |
385 | --- debian/landscape-client.templates 2012-06-19 15:12:07 +0000 |
386 | +++ debian/landscape-client.templates 2014-11-19 18:22:39 +0000 |
387 | @@ -17,6 +17,7 @@ |
388 | needed if the given account is requesting a client registration |
389 | password. |
390 | |
391 | +<<<<<<< TREE |
392 | Template: landscape-client/url |
393 | Type: string |
394 | Default: https://landscape.canonical.com/message-system |
395 | @@ -69,6 +70,60 @@ |
396 | first registration. Once the machine is registered, these tags can only be |
397 | changed using the Landscape server. |
398 | |
399 | +======= |
400 | +Template: landscape-client/url |
401 | +Type: string |
402 | +Default: https://landscape.canonical.com/message-system |
403 | +_Description: Landscape Server URL: |
404 | + The server URL to connect to. |
405 | + |
406 | +Template: landscape-client/exchange_interval |
407 | +Type: string |
408 | +Default: 900 |
409 | +_Description: Message Exchange Interval: |
410 | + Interval, in seconds, between normal message exchanges with the Landscape |
411 | + server. |
412 | + |
413 | +Template: landscape-client/urgent_exchange_interval |
414 | +Type: string |
415 | +Default: 60 |
416 | +_Description: Urgent Message Exchange Interval: |
417 | + Interval, in seconds, between urgent message exchanges with the Landscape |
418 | + server. |
419 | + |
420 | +Template: landscape-client/ping_url |
421 | +Type: string |
422 | +Default: http://landscape.canonical.com/ping |
423 | +_Description: Landscape PingServer URL: |
424 | + The URL to perform lightweight exchange initiation with. |
425 | + |
426 | +Template: landscape-client/ping_interval |
427 | +Type: string |
428 | +Default: 30 |
429 | +_Description: Ping Interval: |
430 | + Interval, in seconds, between client ping exchanges with the Landscape server. |
431 | + |
432 | +Template: landscape-client/http_proxy |
433 | +Type: string |
434 | +Default: |
435 | +_Description: HTTP proxy (blank for none): |
436 | + The URL of the HTTP proxy, if one is needed. |
437 | + |
438 | +Template: landscape-client/https_proxy |
439 | +Type: string |
440 | +Default: |
441 | +_Description: HTTPS proxy (blank for none): |
442 | + The URL of the HTTPS proxy, if one is needed. |
443 | + |
444 | +Template: landscape-client/tags |
445 | +Type: string |
446 | +Default: |
447 | +_Description: Initial tags for first registration |
448 | + Comma separated list of tags which will be assigned to this computer on its |
449 | + first registration. Once the machine is registered, these tags can only be |
450 | + changed using the Landscape server. |
451 | + |
452 | +>>>>>>> MERGE-SOURCE |
453 | Template: landscape-client/register_system |
454 | Type: boolean |
455 | Default: false |
456 | |
457 | === modified file 'debian/po/templates.pot' |
458 | --- debian/po/templates.pot 2012-06-19 15:12:07 +0000 |
459 | +++ debian/po/templates.pot 2014-11-19 18:22:39 +0000 |
460 | @@ -8,7 +8,11 @@ |
461 | msgstr "" |
462 | "Project-Id-Version: landscape-client\n" |
463 | "Report-Msgid-Bugs-To: landscape-client@packages.debian.org\n" |
464 | +<<<<<<< TREE |
465 | "POT-Creation-Date: 2012-06-01 18:35-0300\n" |
466 | +======= |
467 | +"POT-Creation-Date: 2012-03-07 17:07+0100\n" |
468 | +>>>>>>> MERGE-SOURCE |
469 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" |
470 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" |
471 | "Language-Team: LANGUAGE <LL@li.org>\n" |
472 | @@ -59,6 +63,7 @@ |
473 | "if the given account is requesting a client registration password." |
474 | msgstr "" |
475 | |
476 | +<<<<<<< TREE |
477 | #. Type: string |
478 | #. Description |
479 | #: ../landscape-client.templates:4001 |
480 | @@ -164,6 +169,113 @@ |
481 | "changed using the Landscape server." |
482 | msgstr "" |
483 | |
484 | +======= |
485 | +#. Type: string |
486 | +#. Description |
487 | +#: ../landscape-client.templates:4001 |
488 | +msgid "Landscape Server URL:" |
489 | +msgstr "" |
490 | + |
491 | +#. Type: string |
492 | +#. Description |
493 | +#: ../landscape-client.templates:4001 |
494 | +msgid "The server URL to connect to." |
495 | +msgstr "" |
496 | + |
497 | +#. Type: string |
498 | +#. Description |
499 | +#: ../landscape-client.templates:5001 |
500 | +msgid "Message Exchange Interval:" |
501 | +msgstr "" |
502 | + |
503 | +#. Type: string |
504 | +#. Description |
505 | +#: ../landscape-client.templates:5001 |
506 | +msgid "" |
507 | +"Interval, in seconds, between normal message exchanges with the Landscape " |
508 | +"server." |
509 | +msgstr "" |
510 | + |
511 | +#. Type: string |
512 | +#. Description |
513 | +#: ../landscape-client.templates:6001 |
514 | +msgid "Urgent Message Exchange Interval:" |
515 | +msgstr "" |
516 | + |
517 | +#. Type: string |
518 | +#. Description |
519 | +#: ../landscape-client.templates:6001 |
520 | +msgid "" |
521 | +"Interval, in seconds, between urgent message exchanges with the Landscape " |
522 | +"server." |
523 | +msgstr "" |
524 | + |
525 | +#. Type: string |
526 | +#. Description |
527 | +#: ../landscape-client.templates:7001 |
528 | +msgid "Landscape PingServer URL:" |
529 | +msgstr "" |
530 | + |
531 | +#. Type: string |
532 | +#. Description |
533 | +#: ../landscape-client.templates:7001 |
534 | +msgid "The URL to perform lightweight exchange initiation with." |
535 | +msgstr "" |
536 | + |
537 | +#. Type: string |
538 | +#. Description |
539 | +#: ../landscape-client.templates:8001 |
540 | +msgid "Ping Interval:" |
541 | +msgstr "" |
542 | + |
543 | +#. Type: string |
544 | +#. Description |
545 | +#: ../landscape-client.templates:8001 |
546 | +msgid "" |
547 | +"Interval, in seconds, between client ping exchanges with the Landscape " |
548 | +"server." |
549 | +msgstr "" |
550 | + |
551 | +#. Type: string |
552 | +#. Description |
553 | +#: ../landscape-client.templates:9001 |
554 | +msgid "HTTP proxy (blank for none):" |
555 | +msgstr "" |
556 | + |
557 | +#. Type: string |
558 | +#. Description |
559 | +#: ../landscape-client.templates:9001 |
560 | +msgid "The URL of the HTTP proxy, if one is needed." |
561 | +msgstr "" |
562 | + |
563 | +#. Type: string |
564 | +#. Description |
565 | +#: ../landscape-client.templates:10001 |
566 | +msgid "HTTPS proxy (blank for none):" |
567 | +msgstr "" |
568 | + |
569 | +#. Type: string |
570 | +#. Description |
571 | +#: ../landscape-client.templates:10001 |
572 | +msgid "The URL of the HTTPS proxy, if one is needed." |
573 | +msgstr "" |
574 | + |
575 | +#. Type: string |
576 | +#. Description |
577 | +#: ../landscape-client.templates:11001 |
578 | +msgid "Initial tags for first registration" |
579 | +msgstr "" |
580 | + |
581 | +#. Type: string |
582 | +#. Description |
583 | +#: ../landscape-client.templates:11001 |
584 | +msgid "" |
585 | +"Comma separated list of tags which will be assigned to this computer on its " |
586 | +"first registration. Once the machine is registered, these tags can only be " |
587 | +"changed using the Landscape server." |
588 | +msgstr "" |
589 | + |
590 | +>>>>>>> MERGE-SOURCE |
591 | #. Type: boolean |
592 | #. Description |
593 | #: ../landscape-client.templates:12001 |
594 | @@ -172,6 +284,7 @@ |
595 | |
596 | #. Type: boolean |
597 | #. Description |
598 | +<<<<<<< TREE |
599 | #: ../landscape-client.templates:12001 |
600 | msgid "" |
601 | "Register this system with a preexisting Landscape account. Please go to " |
602 | @@ -212,6 +325,26 @@ |
603 | #. this will break the choices shown to users |
604 | #: ../landscape-common.templates:1001 |
605 | msgid "Run sysinfo on every login" |
606 | +======= |
607 | +#: ../landscape-client.templates:12001 |
608 | +msgid "" |
609 | +"Register this system with a preexisting Landscape account. Please go to " |
610 | +"http://landscape.canonical.com if you need a Landscape account." |
611 | +msgstr "" |
612 | + |
613 | +#. Type: select |
614 | +#. Choices |
615 | +#. Translators beware! the following three strings form a single |
616 | +#. Choices menu. - Every one of these strings has to fit in a standard |
617 | +#. 80 characters console, as the fancy screen setup takes up some space |
618 | +#. try to keep below ~71 characters. |
619 | +#. DO NOT USE commas (,) in Choices translations otherwise |
620 | +#. this will break the choices shown to users |
621 | +#: ../landscape-common.templates:1001 |
622 | +msgid "" |
623 | +"Do not display sysinfo on login, Cache sysinfo in /etc/motd, Run sysinfo on " |
624 | +"every login" |
625 | +>>>>>>> MERGE-SOURCE |
626 | msgstr "" |
627 | |
628 | #. Type: select |
629 | |
630 | === modified file 'debian/rules' |
631 | --- debian/rules 2012-06-19 15:12:07 +0000 |
632 | +++ debian/rules 2014-11-19 18:22:39 +0000 |
633 | @@ -2,6 +2,7 @@ |
634 | |
635 | dist_release := $(shell lsb_release -cs) |
636 | ifneq ($(dist_release),dapper) |
637 | +<<<<<<< TREE |
638 | use_pycentral = yes |
639 | endif |
640 | ifeq (,$(filter $(dist_release), hardy lucid)) |
641 | @@ -12,6 +13,18 @@ |
642 | ifeq (,$(filter $(dist_release),hardy lucid natty oneiric)) |
643 | # We want landscape-client-ui only from precise onward |
644 | dh_extra_flags += -plandscape-client-ui -plandscape-client-ui-install |
645 | +======= |
646 | + use_pycentral = yes |
647 | +endif |
648 | +ifeq (,$(filter $(dist_release), hardy lucid)) |
649 | + use_dhpython2 = yes |
650 | +endif |
651 | + |
652 | +dh_extra_flags = -plandscape-common -plandscape-client |
653 | +ifneq (,$(filter $(dist_release),precise)) |
654 | + # We want landscape-client-ui only from precise onward |
655 | + dh_extra_flags += -plandscape-client-ui -plandscape-client-ui-install |
656 | +>>>>>>> MERGE-SOURCE |
657 | endif |
658 | |
659 | -include /usr/share/python/python.mk |
660 | |
661 | === added file 'dev/dns-server' |
662 | --- dev/dns-server 1970-01-01 00:00:00 +0000 |
663 | +++ dev/dns-server 2014-11-19 18:22:39 +0000 |
664 | @@ -0,0 +1,99 @@ |
665 | +#!/usr/bin/env python |
666 | +""" |
667 | +This script creates a DNS server that has enough functionality to test |
668 | +the server autodiscovery feature of landscape client. |
669 | + |
670 | +Landscape client uses /etc/resolv.conf to find the location of name servers. |
671 | +To have landscape client use this server, make sure that: |
672 | + |
673 | +nameserver 127.0.0.1 |
674 | + |
675 | +is the first nameserver in /etc/resolv.conf. Linux name lookups only support |
676 | +port 53, so this program must run on port 53 in order to work. Be aware that |
677 | +NetworkManager overwrites this file any time your network configuration |
678 | +changes. |
679 | + |
680 | +This program does not return enough information for a tool like dig to complete |
681 | +successfully. If this is needed, use Bind 9, detailed at |
682 | +https://wiki.canonical.com/Landscape/SpecRegistry/0082 |
683 | +""" |
684 | + |
685 | +import argparse |
686 | +import sys |
687 | +from twisted.internet import reactor, defer |
688 | +from twisted.names import dns, common |
689 | +from twisted.names.server import DNSServerFactory |
690 | + |
691 | + |
692 | +PORT = 5553 |
693 | +SRV_RESPONSE = 'lds1.mylandscapehost.com' |
694 | +A_RESPONSE = '127.0.0.1' |
695 | + |
696 | + |
697 | +class SimpleResolver(common.ResolverBase): |
698 | + def _lookup(self, name, cls, typ, timeout): |
699 | + """ |
700 | + Respond to DNS requests. See documentation for |
701 | + L{twisted.names.common.ResolverBase}. |
702 | + """ |
703 | + # This nameserver returns the same result all the time, regardless |
704 | + # of what name the client asks for. |
705 | + results = [] |
706 | + ttl = 60 |
707 | + if typ == dns.SRV: |
708 | + record = dns.Record_SRV(0, 1, 80, SRV_RESPONSE, ttl) |
709 | + owner = '_landscape._tcp.mylandscapehost.com' |
710 | + results.append(dns.RRHeader(owner, record.TYPE, dns.IN, ttl, |
711 | + record, auth=True)) |
712 | + elif typ == dns.A: |
713 | + record = dns.Record_A(A_RESPONSE) |
714 | + owner = 'landscape.localdomain' |
715 | + results.append(dns.RRHeader(owner, record.TYPE, dns.IN, ttl, |
716 | + record, auth=True)) |
717 | + |
718 | + authority = [] |
719 | + return defer.succeed((results, authority, [])) |
720 | + |
721 | + |
722 | +def parse_command_line(args): |
723 | + global SRV_RESPONSE, A_RESPONSE, PORT |
724 | + description = """ |
725 | + This test tool responds to DNS queries for SRV and A records. It always |
726 | + responds with the same result regardless of the query string sent by the |
727 | + client. |
728 | + |
729 | + To test this tool, try the following commands: |
730 | + dig -p 5553 @127.0.0.1 SRV _landscape._tcp.mylandscapehost.com |
731 | + |
732 | + dig -p 5553 @127.0.0.1 localhost.localdomain |
733 | + """ |
734 | + parser = argparse.ArgumentParser(description=description) |
735 | + parser.add_argument("--srv-response", type=str, default=SRV_RESPONSE, |
736 | + help="Give this reply to SRV queries (eg: localhost)") |
737 | + parser.add_argument("--a-response", type=str, default=A_RESPONSE, |
738 | + help="Give this reply to A queries (eg: 127.0.0.1)") |
739 | + parser.add_argument("--port", type=int, default=PORT, |
740 | + help="Listen on this port (default 5553). DNS " |
741 | + "normally runs on port 53") |
742 | + |
743 | + args = vars(parser.parse_args()) |
744 | + SRV_RESPONSE = args["srv_response"] |
745 | + A_RESPONSE = args["a_response"] |
746 | + PORT = args["port"] |
747 | + |
748 | + |
749 | +def main(): |
750 | + parse_command_line(sys.argv) |
751 | + |
752 | + simple_resolver = SimpleResolver() |
753 | + factory = DNSServerFactory(authorities=[simple_resolver], verbose=1) |
754 | + protocol = dns.DNSDatagramProtocol(factory) |
755 | + print "starting reactor on port %s.." % PORT |
756 | + reactor.listenTCP(PORT, factory) |
757 | + reactor.listenUDP(PORT, protocol) |
758 | + reactor.run() |
759 | + print "reactor stopped..." |
760 | + |
761 | + |
762 | +if __name__ == "__main__": |
763 | + main() |
764 | |
765 | === renamed file 'dev/dns-server' => 'dev/dns-server.moved' |
766 | === added directory 'glib-2.0' |
767 | === renamed directory 'glib-2.0' => 'glib-2.0.moved' |
768 | === added directory 'glib-2.0/schemas' |
769 | === added file 'glib-2.0/schemas/com.canonical.landscape-client-settings.gschema.xml' |
770 | --- glib-2.0/schemas/com.canonical.landscape-client-settings.gschema.xml 1970-01-01 00:00:00 +0000 |
771 | +++ glib-2.0/schemas/com.canonical.landscape-client-settings.gschema.xml 2014-11-19 18:22:39 +0000 |
772 | @@ -0,0 +1,37 @@ |
773 | +<?xml version="1.0" encoding="utf-8"?> |
774 | +<schemalist> |
775 | + <schema id="com.canonical.landscape-client-settings" path="/com/canonical/landscape-client-settings/"> |
776 | + <key type="s" name="management-type"> |
777 | + <default>"not"</default> |
778 | + <summary>Whether the client settings UI currently set to a hosted client configuration, an LDS instance or no management service.</summary> |
779 | + </key> |
780 | + <key type="s" name="computer-title"> |
781 | + <default>""</default> |
782 | + <summary>The title to register the machine with.</summary> |
783 | + </key> |
784 | + <key type="s" name="hosted-landscape-host"> |
785 | + <default>"landscape.canonical.com"</default> |
786 | + <summary>The hostname of the Canonical hosted landscape system.</summary> |
787 | + </key> |
788 | + <key type="s" name="hosted-account-name"> |
789 | + <default>""</default> |
790 | + <summary>An account name for use with the Canonical hosted landscape system.</summary> |
791 | + </key> |
792 | + <key type="s" name="hosted-password"> |
793 | + <default>""</default> |
794 | + <summary>A password for use with the Canonical hosted landscape system</summary> |
795 | + </key> |
796 | + <key type="s" name="local-landscape-host"> |
797 | + <default>""</default> |
798 | + <summary>The hostname of the local landscape system.</summary> |
799 | + </key> |
800 | + <key type="s" name="local-account-name"> |
801 | + <default>""</default> |
802 | + <summary>An account name for use with the local landscape system.</summary> |
803 | + </key> |
804 | + <key type="s" name="local-password"> |
805 | + <default>""</default> |
806 | + <summary>A password for use with the local landscape system</summary> |
807 | + </key> |
808 | + </schema> |
809 | +</schemalist> |
810 | |
811 | === added directory 'icons' |
812 | === renamed directory 'icons' => 'icons.moved' |
813 | === added file 'icons/preferences-management-service.svg' |
814 | --- icons/preferences-management-service.svg 1970-01-01 00:00:00 +0000 |
815 | +++ icons/preferences-management-service.svg 2014-11-19 18:22:39 +0000 |
816 | @@ -0,0 +1,62 @@ |
817 | +<?xml version="1.0" encoding="UTF-8" standalone="no"?> |
818 | +<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In --> |
819 | + |
820 | +<svg |
821 | + xmlns:dc="http://purl.org/dc/elements/1.1/" |
822 | + xmlns:cc="http://creativecommons.org/ns#" |
823 | + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" |
824 | + xmlns:svg="http://www.w3.org/2000/svg" |
825 | + xmlns="http://www.w3.org/2000/svg" |
826 | + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" |
827 | + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" |
828 | + version="1.1" |
829 | + x="0px" |
830 | + y="0px" |
831 | + width="32" |
832 | + height="32" |
833 | + viewBox="-0.787 -0.867 32 32" |
834 | + enable-background="new -0.787 -0.867 285 285" |
835 | + xml:space="preserve" |
836 | + id="svg2" |
837 | + inkscape:version="0.48.2 r9819" |
838 | + sodipodi:docname="canonical_aubergine_hex.svg"><metadata |
839 | + id="metadata12"><rdf:RDF><cc:Work |
840 | + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type |
841 | + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><sodipodi:namedview |
842 | + pagecolor="#ffffff" |
843 | + bordercolor="#666666" |
844 | + borderopacity="1" |
845 | + objecttolerance="10" |
846 | + gridtolerance="10" |
847 | + guidetolerance="10" |
848 | + inkscape:pageopacity="0" |
849 | + inkscape:pageshadow="2" |
850 | + inkscape:window-width="697" |
851 | + inkscape:window-height="480" |
852 | + id="namedview10" |
853 | + showgrid="false" |
854 | + fit-margin-top="0" |
855 | + fit-margin-left="0" |
856 | + fit-margin-right="0" |
857 | + fit-margin-bottom="0" |
858 | + inkscape:zoom="7.38" |
859 | + inkscape:cx="15.9035" |
860 | + inkscape:cy="16.176" |
861 | + inkscape:window-x="449" |
862 | + inkscape:window-y="100" |
863 | + inkscape:window-maximized="0" |
864 | + inkscape:current-layer="svg2" /> |
865 | +<defs |
866 | + id="defs4"> |
867 | +</defs> |
868 | +<path |
869 | + d="m 15.21339,25.32101 c -5.62692,0 -10.18809,-4.55982 -10.18809,-10.18812 0,-5.62706 4.56117,-10.18768 10.18809,-10.18768 5.62705,0 10.18798,4.56062 10.18798,10.18768 0,5.6283 -4.56082,10.18812 -10.18798,10.18812 z" |
870 | + id="path6" |
871 | + inkscape:connector-curvature="0" |
872 | + style="fill:#772953" /> |
873 | +<path |
874 | + d="M 31.213,15.13334 C 31.213,23.96956 24.04959,31.133 15.21294,31.133 6.37607,31.133 -0.787,23.96945 -0.787,15.13334 -0.787,6.29621 6.37607,-0.867 15.21306,-0.867 24.04959,-0.867 31.213,6.29621 31.213,15.13334 z M 15.21306,3.025 c -6.68751,0 -12.10774,5.42115 -12.10774,12.10823 0,6.68809 5.42023,12.10755 12.10774,12.10755 6.68773,0 12.10796,-5.41946 12.10796,-12.10755 C 27.32102,8.44615 21.90079,3.025 15.21306,3.025 z" |
875 | + id="path8" |
876 | + inkscape:connector-curvature="0" |
877 | + style="fill:#772953" /> |
878 | +</svg> |
879 | \ No newline at end of file |
880 | |
881 | === modified file 'landscape/__init__.py' |
882 | --- landscape/__init__.py 2012-06-19 15:12:07 +0000 |
883 | +++ landscape/__init__.py 2014-11-19 18:22:39 +0000 |
884 | @@ -1,7 +1,11 @@ |
885 | import sys |
886 | |
887 | DEBIAN_REVISION = "" |
888 | +<<<<<<< TREE |
889 | UPSTREAM_VERSION = "12.05" |
890 | +======= |
891 | +UPSTREAM_VERSION = "12.04.3" |
892 | +>>>>>>> MERGE-SOURCE |
893 | VERSION = "%s%s" % (UPSTREAM_VERSION, DEBIAN_REVISION) |
894 | |
895 | # The "server-api" field of outgoing messages will be set to this value, and |
896 | |
897 | === modified file 'landscape/constants.py' |
898 | === modified file 'landscape/deployment.py' |
899 | --- landscape/deployment.py 2012-06-19 15:12:07 +0000 |
900 | +++ landscape/deployment.py 2014-11-19 18:22:39 +0000 |
901 | @@ -282,6 +282,7 @@ |
902 | - C{quiet} (C{False}) |
903 | - C{log_dir} (C{"/var/log/landscape"}) |
904 | - C{log_level} (C{"info"}) |
905 | +<<<<<<< TREE |
906 | - C{url} (C{"http://landscape.canonical.com/message-system"}) |
907 | - C{ping_url} (C{"http://landscape.canonical.com/ping"}) |
908 | - C{ssl_public_key} |
909 | @@ -289,6 +290,15 @@ |
910 | - C{autodiscover_srv_query_string} |
911 | (C{"_tcp._landscape.localdomain"}) |
912 | - C{autodiscover_a_query_string} (C{"landscape.localdomain"}) |
913 | +======= |
914 | + - C{url} (C{"http://landcape.canonical.com/message-system"}) |
915 | + - C{ping_url} (C{"http://landscape.canonical.com/ping"}) |
916 | + - C{ssl_public_key} |
917 | + - C{server_autodiscover} (C{"false"}) |
918 | + - C{autodiscover_srv_query_string} |
919 | + (C{"_tcp._landscape.localdomain"}) |
920 | + - C{autodiscover_a_query_string} (C{"landscape.localdomain"}) |
921 | +>>>>>>> MERGE-SOURCE |
922 | - C{ignore_sigint} (C{False}) |
923 | """ |
924 | parser = super(Configuration, self).make_parser() |
925 | |
926 | === added file 'landscape/lib/dns.py' |
927 | --- landscape/lib/dns.py 1970-01-01 00:00:00 +0000 |
928 | +++ landscape/lib/dns.py 2014-11-19 18:22:39 +0000 |
929 | @@ -0,0 +1,78 @@ |
930 | +"""DNS lookups for server autodiscovery.""" |
931 | + |
932 | +import logging |
933 | +from twisted.names import dns |
934 | +from twisted.names.client import Resolver |
935 | + |
936 | + |
937 | +def discover_server(autodiscover_srv_query_string="", |
938 | + autodiscover_a_query_string="", resolver=None): |
939 | + """ |
940 | + Look up the dns location of the landscape server. |
941 | + |
942 | + @param autodiscover_srv_query_string: The query string to send to the DNS |
943 | + server when making a SRV query. |
944 | + @param autodiscover_a_query_string: The query string to send to the DNS |
945 | + server when making a A query. |
946 | + @type resolver: The resolver to use. If none is specified a resolver that |
947 | + uses settings from /etc/resolv.conf will be created. (Testing only) |
948 | + """ |
949 | + if not resolver: |
950 | + resolver = Resolver("/etc/resolv.conf") |
951 | + d = _lookup_server_record(resolver, autodiscover_srv_query_string) |
952 | + d.addErrback(_lookup_hostname, resolver, autodiscover_a_query_string) |
953 | + return d |
954 | + |
955 | + |
956 | +def _lookup_server_record(resolver, service_name): |
957 | + """ |
958 | + Do a DNS SRV record lookup for the location of the landscape server. |
959 | + |
960 | + @type resolver: A resolver to use for DNS lookups |
961 | + L{twisted.names.client.Resolver}. |
962 | + @param service_name: The query string to send to the DNS server when |
963 | + making a SRV query. |
964 | + @return: A deferred containing either the hostname of the landscape server |
965 | + if found or an empty string if not found. |
966 | + """ |
967 | + def lookup_done(result): |
968 | + name = "" |
969 | + for item in result: |
970 | + for row in item: |
971 | + if row.type == dns.SRV: |
972 | + name = row.payload.target.name |
973 | + break |
974 | + return name |
975 | + |
976 | + def lookup_failed(result): |
977 | + logging.info("SRV lookup of %s failed." % service_name) |
978 | + return result |
979 | + |
980 | + d = resolver.lookupService(service_name) |
981 | + d.addCallback(lookup_done) |
982 | + d.addErrback(lookup_failed) |
983 | + return d |
984 | + |
985 | + |
986 | +def _lookup_hostname(result, resolver, hostname): |
987 | + """ |
988 | + Do a DNS name lookup for the location of the landscape server. |
989 | + |
990 | + @param result: The result from a call to lookup_server_record. |
991 | + @param resolver: The resolver to use for DNS lookups. |
992 | + @param hostname: The query string to send to the DNS server when making |
993 | + a A query. |
994 | + @param return: A deferred containing the ip address of the landscape |
995 | + server if found or None if not found. |
996 | + """ |
997 | + def lookup_done(result): |
998 | + return result |
999 | + |
1000 | + def lookup_failed(result): |
1001 | + logging.info("Name lookup of %s failed." % hostname) |
1002 | + return result |
1003 | + |
1004 | + d = resolver.getHostByName(hostname) |
1005 | + d.addCallback(lookup_done) |
1006 | + d.addErrback(lookup_failed) |
1007 | + return d |
1008 | |
1009 | === renamed file 'landscape/lib/dns.py' => 'landscape/lib/dns.py.moved' |
1010 | === added file 'landscape/lib/tests/test_dns.py' |
1011 | --- landscape/lib/tests/test_dns.py 1970-01-01 00:00:00 +0000 |
1012 | +++ landscape/lib/tests/test_dns.py 2014-11-19 18:22:39 +0000 |
1013 | @@ -0,0 +1,208 @@ |
1014 | +from landscape.tests.helpers import LandscapeTest |
1015 | +from landscape.lib.dns import ( |
1016 | + _lookup_server_record, _lookup_hostname, discover_server) |
1017 | + |
1018 | +from twisted.internet import defer |
1019 | +from twisted.names import dns |
1020 | +from twisted.names.error import ResolverError |
1021 | + |
1022 | + |
1023 | +class FakeResolverResult(object): |
1024 | + """ |
1025 | + A fake resolver result returned by L{FakeResolver}. |
1026 | + |
1027 | + @param type: The result type L{twisted.names.dns.SRV} |
1028 | + @param payload: The result contents |
1029 | + """ |
1030 | + def __init__(self): |
1031 | + self.type = None |
1032 | + |
1033 | + class Payload(object): |
1034 | + """ |
1035 | + A payload result returned by fake resolver. |
1036 | + |
1037 | + @param target: The result of the lookup |
1038 | + """ |
1039 | + def __init__(self): |
1040 | + self.target = "" |
1041 | + |
1042 | + class Target(object): |
1043 | + """ |
1044 | + A payload target returned by fake resolver. |
1045 | + |
1046 | + @param name: The name contained by the target. |
1047 | + """ |
1048 | + def __init__(self): |
1049 | + self.name = "" |
1050 | + |
1051 | + self.payload = Payload() |
1052 | + self.payload.target = Target() |
1053 | + |
1054 | + |
1055 | +class FakeResolver(object): |
1056 | + """ |
1057 | + A fake resolver that mimics L{twisted.names.client.Resolver} |
1058 | + """ |
1059 | + def __init__(self): |
1060 | + self.results = None |
1061 | + self.name = None |
1062 | + self.queried = None |
1063 | + |
1064 | + def lookupService(self, arg1): |
1065 | + self.queried = arg1 |
1066 | + deferred = defer.Deferred() |
1067 | + deferred.callback(self.results) |
1068 | + return deferred |
1069 | + |
1070 | + def getHostByName(self, arg1): |
1071 | + self.queried = arg1 |
1072 | + deferred = defer.Deferred() |
1073 | + deferred.callback(self.name) |
1074 | + return deferred |
1075 | + |
1076 | + |
1077 | +class BadResolver(object): |
1078 | + """ |
1079 | + A resolver that mimics L{twisted.names.client.Resolver} and always returns |
1080 | + an error. |
1081 | + """ |
1082 | + def lookupService(self, arg1): |
1083 | + deferred = defer.Deferred() |
1084 | + deferred.errback(ResolverError("Couldn't connect")) |
1085 | + return deferred |
1086 | + |
1087 | + def getHostByName(self, arg1): |
1088 | + deferred = defer.Deferred() |
1089 | + deferred.errback(ResolverError("Couldn't connect")) |
1090 | + return deferred |
1091 | + |
1092 | + |
1093 | +class DnsSrvLookupTest(LandscapeTest): |
1094 | + def test_with_server_found(self): |
1095 | + """ |
1096 | + Looking up a DNS SRV record should return the result of the lookup. |
1097 | + """ |
1098 | + fake_result = FakeResolverResult() |
1099 | + fake_result.type = dns.SRV |
1100 | + fake_result.payload.target.name = "a.b.com" |
1101 | + fake_resolver = FakeResolver() |
1102 | + fake_resolver.results = [[fake_result]] |
1103 | + query_string = "_landscape._tcp.mylandscapehost.com" |
1104 | + |
1105 | + def check(result): |
1106 | + self.assertEqual(fake_resolver.queried, query_string) |
1107 | + self.assertEqual("a.b.com", result) |
1108 | + |
1109 | + d = _lookup_server_record(fake_resolver, query_string) |
1110 | + d.addCallback(check) |
1111 | + return d |
1112 | + |
1113 | + def test_with_server_not_found(self): |
1114 | + """ |
1115 | + Looking up a DNS SRV record and finding nothing exists should return |
1116 | + an empty string. |
1117 | + """ |
1118 | + fake_resolver = FakeResolver() |
1119 | + fake_resolver.results = [[]] |
1120 | + |
1121 | + def check(result): |
1122 | + self.assertEqual("", result) |
1123 | + |
1124 | + d = _lookup_server_record(fake_resolver, |
1125 | + "_landscape._tcp.mylandscapehost.com") |
1126 | + d.addCallback(check) |
1127 | + return d |
1128 | + |
1129 | + def test_with_resolver_error(self): |
1130 | + """A resolver error triggers error handling code.""" |
1131 | + # The failure should be properly logged |
1132 | + logging_mock = self.mocker.replace("logging.info") |
1133 | + logging_mock("SRV lookup of _landscape._tcp.mylandscapehost.com " |
1134 | + "failed.") |
1135 | + self.mocker.replay() |
1136 | + |
1137 | + d = _lookup_server_record(BadResolver(), |
1138 | + "_landscape._tcp.mylandscapehost.com") |
1139 | + self.assertFailure(d, ResolverError) |
1140 | + return d |
1141 | + |
1142 | + |
1143 | +class DnsNameLookupTest(LandscapeTest): |
1144 | + def test_with_name_found(self): |
1145 | + """ |
1146 | + Looking up a DNS name record should return the result of the lookup. |
1147 | + """ |
1148 | + fake_resolver = FakeResolver() |
1149 | + fake_resolver.name = "a.b.com" |
1150 | + query_string = "landscape.localdomain" |
1151 | + |
1152 | + def check(result): |
1153 | + self.assertEqual(fake_resolver.queried, query_string) |
1154 | + self.assertEqual("a.b.com", result) |
1155 | + |
1156 | + d = _lookup_hostname(None, fake_resolver, query_string) |
1157 | + d.addCallback(check) |
1158 | + return d |
1159 | + |
1160 | + def test_with_name_not_found(self): |
1161 | + """ |
1162 | + Looking up a DNS NAME record and not finding a result should return |
1163 | + None. |
1164 | + """ |
1165 | + fake_resolver = FakeResolver() |
1166 | + fake_resolver.name = None |
1167 | + |
1168 | + def check(result): |
1169 | + self.assertEqual(None, result) |
1170 | + |
1171 | + d = _lookup_hostname(None, fake_resolver, "landscape.localdomain") |
1172 | + d.addCallback(check) |
1173 | + return d |
1174 | + |
1175 | + def test_with_resolver_error(self): |
1176 | + """A resolver error triggers error handling code.""" |
1177 | + # The failure should be properly logged |
1178 | + logging_mock = self.mocker.replace("logging.info") |
1179 | + logging_mock("Name lookup of landscape.localdomain failed.") |
1180 | + self.mocker.replay() |
1181 | + |
1182 | + d = _lookup_hostname(None, BadResolver(), "landscape.localdomain") |
1183 | + self.assertFailure(d, ResolverError) |
1184 | + return d |
1185 | + |
1186 | + |
1187 | +class DiscoverServerTest(LandscapeTest): |
1188 | + def test_srv_lookup(self): |
1189 | + """The DNS name of the server is found using a SRV lookup.""" |
1190 | + fake_result = FakeResolverResult() |
1191 | + fake_result.type = dns.SRV |
1192 | + fake_result.payload.target.name = "a.b.com" |
1193 | + fake_resolver = FakeResolver() |
1194 | + fake_resolver.results = [[fake_result]] |
1195 | + |
1196 | + d = discover_server(resolver=fake_resolver) |
1197 | + |
1198 | + def check(result): |
1199 | + self.assertEqual("a.b.com", result) |
1200 | + |
1201 | + d.addCallback(check) |
1202 | + return d |
1203 | + |
1204 | + def test_a_name_lookup(self): |
1205 | + """The DNS name of the server is found using an A name lookup.""" |
1206 | + fake_resolver = FakeResolver() |
1207 | + fake_resolver.name = "x.y.com" |
1208 | + |
1209 | + d = discover_server(resolver=fake_resolver) |
1210 | + |
1211 | + def check(result): |
1212 | + self.assertEqual("x.y.com", result) |
1213 | + |
1214 | + d.addCallback(check) |
1215 | + return d |
1216 | + |
1217 | + def test_failed_lookup(self): |
1218 | + """A resolver error is returned when server autodiscovery fails.""" |
1219 | + d = _lookup_server_record(BadResolver(), "landscape.localdomain") |
1220 | + self.assertFailure(d, ResolverError) |
1221 | + return d |
1222 | |
1223 | === renamed file 'landscape/lib/tests/test_dns.py' => 'landscape/lib/tests/test_dns.py.moved' |
1224 | === added file 'landscape/manager/fakepackagemanager.py' |
1225 | --- landscape/manager/fakepackagemanager.py 1970-01-01 00:00:00 +0000 |
1226 | +++ landscape/manager/fakepackagemanager.py 2014-11-19 18:22:39 +0000 |
1227 | @@ -0,0 +1,57 @@ |
1228 | +import logging |
1229 | +import os |
1230 | +import random |
1231 | + |
1232 | +from twisted.internet.utils import getProcessOutput |
1233 | +from twisted.internet.defer import succeed |
1234 | + |
1235 | +from landscape.package.store import PackageStore |
1236 | +from landscape.package.changer import PackageChanger |
1237 | +from landscape.package.releaseupgrader import ReleaseUpgrader |
1238 | +from landscape.manager.plugin import ManagerPlugin |
1239 | +from landscape.manager.manager import SUCCEEDED |
1240 | + |
1241 | + |
1242 | +class FakePackageManager(ManagerPlugin): |
1243 | + |
1244 | + run_interval = 1800 |
1245 | + randint = random.randint |
1246 | + |
1247 | + def register(self, registry): |
1248 | + super(FakePackageManager, self).register(registry) |
1249 | + self.config = registry.config |
1250 | + |
1251 | + registry.register_message("change-packages", |
1252 | + self.handle_change_packages) |
1253 | + registry.register_message("change-package-locks", |
1254 | + self.handle_change_package_locks) |
1255 | + registry.register_message("release-upgrade", |
1256 | + self.handle_release_upgrade) |
1257 | + |
1258 | + def _handle(self, response): |
1259 | + delay = self.randint(30, 300) |
1260 | + self.registry.reactor.call_later( |
1261 | + delay, self.manager.broker.send_message, response, urgent=True) |
1262 | + |
1263 | + def handle_change_packages(self, message): |
1264 | + response = {"type": "change-packages-result", |
1265 | + "operation-id": message.get("operation-id"), |
1266 | + "result-code": 1, |
1267 | + "result-text": "OK done."} |
1268 | + return self._handle(response) |
1269 | + |
1270 | + def handle_change_package_locks(self, message): |
1271 | + response = {"type": "operation-result", |
1272 | + "operation-id": message.get("operation-id"), |
1273 | + "status": SUCCEEDED, |
1274 | + "result-text": "Package locks successfully changed.", |
1275 | + "result-code": 0} |
1276 | + return self._handle(response) |
1277 | + |
1278 | + def handle_release_upgrade(self, message): |
1279 | + response = {"type": "operation-result", |
1280 | + "operation-id": message.get("operation-id"), |
1281 | + "status": SUCCEEDED, |
1282 | + "result-text": "Successful release upgrade.", |
1283 | + "result-code": 0} |
1284 | + return self._handle(response) |
1285 | |
1286 | === renamed file 'landscape/manager/fakepackagemanager.py' => 'landscape/manager/fakepackagemanager.py.moved' |
1287 | === added file 'landscape/manager/hardwareinfo.py' |
1288 | --- landscape/manager/hardwareinfo.py 1970-01-01 00:00:00 +0000 |
1289 | +++ landscape/manager/hardwareinfo.py 2014-11-19 18:22:39 +0000 |
1290 | @@ -0,0 +1,28 @@ |
1291 | +import os |
1292 | + |
1293 | +from twisted.internet.utils import getProcessOutput |
1294 | + |
1295 | +from landscape.manager.plugin import ManagerPlugin |
1296 | + |
1297 | + |
1298 | +class HardwareInfo(ManagerPlugin): |
1299 | + """A plugin to retrieve hardware information.""" |
1300 | + |
1301 | + message_type = "hardware-info" |
1302 | + run_interval = 60 * 60 * 24 |
1303 | + run_immediately = True |
1304 | + command = "/usr/bin/lshw" |
1305 | + |
1306 | + def run(self): |
1307 | + self.call_on_accepted(self.message_type, self.send_message) |
1308 | + return self.registry.broker.call_if_accepted( |
1309 | + self.message_type, self.send_message) |
1310 | + |
1311 | + def send_message(self): |
1312 | + result = getProcessOutput( |
1313 | + self.command, args=["-xml", "-quiet"], env=os.environ, path=None) |
1314 | + return result.addCallback(self._got_output) |
1315 | + |
1316 | + def _got_output(self, output): |
1317 | + message = {"type": self.message_type, "data": output} |
1318 | + return self.registry.broker.send_message(message) |
1319 | |
1320 | === renamed file 'landscape/manager/hardwareinfo.py' => 'landscape/manager/hardwareinfo.py.moved' |
1321 | === modified file 'landscape/manager/tests/test_aptsources.py' |
1322 | === added file 'landscape/manager/tests/test_fakepackagemanager.py' |
1323 | --- landscape/manager/tests/test_fakepackagemanager.py 1970-01-01 00:00:00 +0000 |
1324 | +++ landscape/manager/tests/test_fakepackagemanager.py 2014-11-19 18:22:39 +0000 |
1325 | @@ -0,0 +1,70 @@ |
1326 | +from landscape.manager.plugin import SUCCEEDED |
1327 | + |
1328 | +from landscape.manager.fakepackagemanager import FakePackageManager |
1329 | +from landscape.tests.helpers import LandscapeTest, ManagerHelper |
1330 | + |
1331 | + |
1332 | +class FakePackageManagerTest(LandscapeTest): |
1333 | + """Tests for the fake package manager plugin.""" |
1334 | + |
1335 | + helpers = [ManagerHelper] |
1336 | + |
1337 | + def setUp(self): |
1338 | + super(FakePackageManagerTest, self).setUp() |
1339 | + self.package_manager = FakePackageManager() |
1340 | + self.package_manager.randint = lambda x, y: 0 |
1341 | + |
1342 | + def test_handle_change_packages(self): |
1343 | + """ |
1344 | + L{FakePackageManager} is able to handle a C{change-packages} message, |
1345 | + creating a C{change-packages-result} in response. |
1346 | + """ |
1347 | + self.manager.add(self.package_manager) |
1348 | + service = self.broker_service |
1349 | + service.message_store.set_accepted_types(["change-packages-result"]) |
1350 | + message = {"type": "change-packages", "operation-id": 1} |
1351 | + self.manager.dispatch_message(message) |
1352 | + self.manager.reactor.advance(1) |
1353 | + |
1354 | + self.assertMessages(service.message_store.get_pending_messages(), |
1355 | + [{"type": "change-packages-result", |
1356 | + "result-text": "OK done.", |
1357 | + "result-code": 1, "operation-id": 1}]) |
1358 | + |
1359 | + def test_handle_change_package_locks(self): |
1360 | + """ |
1361 | + L{FakePackageManager} is able to handle a C{change-package-locks} |
1362 | + message, creating a C{operation-result} in response. |
1363 | + """ |
1364 | + self.manager.add(self.package_manager) |
1365 | + service = self.broker_service |
1366 | + service.message_store.set_accepted_types(["operation-result"]) |
1367 | + message = {"type": "change-package-locks", "operation-id": 1} |
1368 | + self.manager.dispatch_message(message) |
1369 | + self.manager.reactor.advance(1) |
1370 | + |
1371 | + self.assertMessages(service.message_store.get_pending_messages(), |
1372 | + [{"type": "operation-result", |
1373 | + "result-text": |
1374 | + "Package locks successfully changed.", |
1375 | + "result-code": 0, "status": SUCCEEDED, |
1376 | + "operation-id": 1}]) |
1377 | + |
1378 | + def test_handle_release_upgrade(self): |
1379 | + """ |
1380 | + L{FakePackageManager} is able to handle a C{release-upgrade} message, |
1381 | + creating a C{operation-result} in response. |
1382 | + """ |
1383 | + self.manager.add(self.package_manager) |
1384 | + service = self.broker_service |
1385 | + service.message_store.set_accepted_types(["operation-result"]) |
1386 | + message = {"type": "release-upgrade", "operation-id": 1} |
1387 | + self.manager.dispatch_message(message) |
1388 | + self.manager.reactor.advance(1) |
1389 | + |
1390 | + self.assertMessages(service.message_store.get_pending_messages(), |
1391 | + [{"type": "operation-result", |
1392 | + "result-text": |
1393 | + "Successful release upgrade.", |
1394 | + "result-code": 0, "status": SUCCEEDED, |
1395 | + "operation-id": 1}]) |
1396 | |
1397 | === renamed file 'landscape/manager/tests/test_fakepackagemanager.py' => 'landscape/manager/tests/test_fakepackagemanager.py.moved' |
1398 | === added file 'landscape/manager/tests/test_hardwareinfo.py' |
1399 | --- landscape/manager/tests/test_hardwareinfo.py 1970-01-01 00:00:00 +0000 |
1400 | +++ landscape/manager/tests/test_hardwareinfo.py 2014-11-19 18:22:39 +0000 |
1401 | @@ -0,0 +1,45 @@ |
1402 | +from landscape.tests.helpers import LandscapeTest, ManagerHelper |
1403 | + |
1404 | +from landscape.manager.hardwareinfo import HardwareInfo |
1405 | + |
1406 | + |
1407 | +class HardwareInfoTests(LandscapeTest): |
1408 | + helpers = [ManagerHelper] |
1409 | + |
1410 | + def setUp(self): |
1411 | + super(HardwareInfoTests, self).setUp() |
1412 | + self.info = HardwareInfo() |
1413 | + self.info.command = "/bin/echo" |
1414 | + self.manager.add(self.info) |
1415 | + |
1416 | + service = self.broker_service |
1417 | + service.message_store.set_accepted_types(["hardware-info"]) |
1418 | + |
1419 | + def test_message(self): |
1420 | + """ |
1421 | + L{HardwareInfo} sends the output of its command when running. |
1422 | + """ |
1423 | + deferred = self.info.send_message() |
1424 | + |
1425 | + def check(ignored): |
1426 | + self.assertMessages( |
1427 | + self.broker_service.message_store.get_pending_messages(), |
1428 | + [{"data": u"-xml -quiet\n", "type": "hardware-info"}]) |
1429 | + |
1430 | + return deferred.addCallback(check) |
1431 | + |
1432 | + def test_run_upgraded_system(self): |
1433 | + """ |
1434 | + L{HardwareInfo} sends the output of its command when running on |
1435 | + a system that has been upgraded to include this plugin, i.e. |
1436 | + where the client already knows that it can send the |
1437 | + hardware-info message. |
1438 | + """ |
1439 | + deferred = self.info.run() |
1440 | + |
1441 | + def check(ignored): |
1442 | + self.assertMessages( |
1443 | + self.broker_service.message_store.get_pending_messages(), |
1444 | + [{"data": u"-xml -quiet\n", "type": "hardware-info"}]) |
1445 | + |
1446 | + return deferred.addCallback(check) |
1447 | |
1448 | === renamed file 'landscape/manager/tests/test_hardwareinfo.py' => 'landscape/manager/tests/test_hardwareinfo.py.moved' |
1449 | === modified file 'landscape/monitor/packagemonitor.py' |
1450 | --- landscape/monitor/packagemonitor.py 2012-06-19 15:12:07 +0000 |
1451 | +++ landscape/monitor/packagemonitor.py 2014-11-19 18:22:39 +0000 |
1452 | @@ -53,6 +53,7 @@ |
1453 | if "packages" in message_types: |
1454 | self.spawn_reporter() |
1455 | |
1456 | +<<<<<<< TREE |
1457 | def _run_fake_reporter(self, args): |
1458 | """Run a fake-reporter in-process.""" |
1459 | |
1460 | @@ -103,6 +104,58 @@ |
1461 | |
1462 | return result.addBoth(done) |
1463 | |
1464 | +======= |
1465 | + def _run_fake_reporter(self, args): |
1466 | + """Run a fake-reporter in-process.""" |
1467 | + |
1468 | + class FakeFacade(object): |
1469 | + """ |
1470 | + A fake facade to workaround the issue that the SmartFacade |
1471 | + essentially allows only once instance per process. |
1472 | + """ |
1473 | + |
1474 | + def get_arch(self): |
1475 | + arch = os.uname()[-1] |
1476 | + result = {"pentium": "i386", |
1477 | + "i86pc": "i386", |
1478 | + "x86_64": "amd64"}.get(arch) |
1479 | + if result: |
1480 | + arch = result |
1481 | + elif (arch[0] == "i" and arch.endswith("86")): |
1482 | + arch = "i386" |
1483 | + return arch |
1484 | + |
1485 | + if getattr(self, "_fake_reporter", None) is None: |
1486 | + |
1487 | + from landscape.package.reporter import ( |
1488 | + FakeReporter, PackageReporterConfiguration) |
1489 | + from landscape.package.store import FakePackageStore |
1490 | + package_facade = FakeFacade() |
1491 | + package_config = PackageReporterConfiguration() |
1492 | + package_config.load(args + ["-d", self.config.data_path, |
1493 | + "-l", self.config.log_dir]) |
1494 | + package_store = FakePackageStore(package_config.store_filename) |
1495 | + self._fake_reporter = FakeReporter(package_store, package_facade, |
1496 | + self.registry.broker, |
1497 | + package_config) |
1498 | + self._fake_reporter.global_store_filename = os.path.join( |
1499 | + self.config.master_data_path, "package", "database") |
1500 | + self._fake_reporter_running = False |
1501 | + |
1502 | + if self._fake_reporter_running: |
1503 | + from twisted.internet.defer import succeed |
1504 | + return succeed(None) |
1505 | + |
1506 | + self._fake_reporter_running = True |
1507 | + result = self._fake_reporter.run() |
1508 | + |
1509 | + def done(passthrough): |
1510 | + self._fake_reporter_running = False |
1511 | + return passthrough |
1512 | + |
1513 | + return result.addBoth(done) |
1514 | + |
1515 | +>>>>>>> MERGE-SOURCE |
1516 | def spawn_reporter(self): |
1517 | args = ["--quiet"] |
1518 | if self.config.config: |
1519 | |
1520 | === modified file 'landscape/package/changer.py' |
1521 | --- landscape/package/changer.py 2012-06-19 15:12:07 +0000 |
1522 | +++ landscape/package/changer.py 2014-11-19 18:22:39 +0000 |
1523 | @@ -18,7 +18,11 @@ |
1524 | from landscape.package.taskhandler import ( |
1525 | PackageTaskHandler, PackageTaskHandlerConfiguration, PackageTaskError, |
1526 | run_task_handler) |
1527 | +<<<<<<< TREE |
1528 | from landscape.manager.manager import FAILED |
1529 | +======= |
1530 | +from landscape.manager.manager import FAILED, SUCCEEDED |
1531 | +>>>>>>> MERGE-SOURCE |
1532 | |
1533 | |
1534 | class UnknownPackageData(Exception): |
1535 | @@ -99,6 +103,8 @@ |
1536 | return result.addErrback(self.unknown_package_data_error, task) |
1537 | if message["type"] == "change-package-locks": |
1538 | return self.handle_change_package_locks(message) |
1539 | + if message["type"] == "change-package-holds": |
1540 | + return self.handle_change_package_holds(message) |
1541 | |
1542 | def unknown_package_data_error(self, failure, task): |
1543 | """Handle L{UnknownPackageData} data errors. |
1544 | @@ -293,13 +299,105 @@ |
1545 | Package locks aren't supported anymore. |
1546 | """ |
1547 | |
1548 | +<<<<<<< TREE |
1549 | response = { |
1550 | "type": "operation-result", |
1551 | "operation-id": message.get("operation-id"), |
1552 | "status": FAILED, |
1553 | "result-text": "This client doesn't support package locks.", |
1554 | "result-code": 1} |
1555 | - return self._broker.send_message(response, True) |
1556 | +======= |
1557 | + if not self._facade.supports_package_locks: |
1558 | + response = { |
1559 | + "type": "operation-result", |
1560 | + "operation-id": message.get("operation-id"), |
1561 | + "status": FAILED, |
1562 | + "result-text": "This client doesn't support package locks.", |
1563 | + "result-code": 1} |
1564 | + return self._broker.send_message(response, True) |
1565 | + |
1566 | + for lock in message.get("create", ()): |
1567 | + self._facade.set_package_lock(*lock) |
1568 | + for lock in message.get("delete", ()): |
1569 | + self._facade.remove_package_lock(*lock) |
1570 | + self._facade.save_config() |
1571 | + |
1572 | + response = {"type": "operation-result", |
1573 | + "operation-id": message.get("operation-id"), |
1574 | + "status": SUCCEEDED, |
1575 | + "result-text": "Package locks successfully changed.", |
1576 | + "result-code": 0} |
1577 | + |
1578 | + logging.info("Queuing message with change package locks results to " |
1579 | + "exchange urgently.") |
1580 | +>>>>>>> MERGE-SOURCE |
1581 | + return self._broker.send_message(response, True) |
1582 | + |
1583 | + def _send_change_package_holds_response(self, response): |
1584 | + """Log that a package holds result is sent and send the response.""" |
1585 | + logging.info("Queuing message with change package holds results to " |
1586 | + "exchange urgently.") |
1587 | + return self._broker.send_message(response, True) |
1588 | + |
1589 | + def handle_change_package_holds(self, message): |
1590 | + """Handle a C{change-package-holds} message. |
1591 | + |
1592 | + Create and delete package holds as requested by the given C{message}. |
1593 | + """ |
1594 | + if not self._facade.supports_package_holds: |
1595 | + response = { |
1596 | + "type": "operation-result", |
1597 | + "operation-id": message.get("operation-id"), |
1598 | + "status": FAILED, |
1599 | + "result-text": "This client doesn't support package holds.", |
1600 | + "result-code": 1} |
1601 | + return self._send_change_package_holds_response(response) |
1602 | + |
1603 | + not_installed = set() |
1604 | + holds_to_create = message.get("create", []) |
1605 | + versions_to_create = set() |
1606 | + for id in holds_to_create: |
1607 | + hash = self._store.get_id_hash(id) |
1608 | + hold_version = self._facade.get_package_by_hash(hash) |
1609 | + if (hold_version |
1610 | + and self._facade.is_package_installed(hold_version)): |
1611 | + versions_to_create.add((hold_version.package, hold_version)) |
1612 | + else: |
1613 | + not_installed.add(str(id)) |
1614 | + holds_to_remove = message.get("delete", []) |
1615 | + versions_to_remove = set() |
1616 | + for id in holds_to_remove: |
1617 | + hash = self._store.get_id_hash(id) |
1618 | + hold_version = self._facade.get_package_by_hash(hash) |
1619 | + if (hold_version |
1620 | + and self._facade.is_package_installed(hold_version)): |
1621 | + versions_to_remove.add((hold_version.package, hold_version)) |
1622 | + |
1623 | + if not_installed: |
1624 | + response = { |
1625 | + "type": "operation-result", |
1626 | + "operation-id": message.get("operation-id"), |
1627 | + "status": FAILED, |
1628 | + "result-text": "Package holds not changed, since the" + |
1629 | + " following packages are not installed: %s" % ( |
1630 | + ", ".join(sorted(not_installed))), |
1631 | + "result-code": 1} |
1632 | + return self._send_change_package_holds_response(response) |
1633 | + |
1634 | + for package, hold_version in versions_to_create: |
1635 | + self._facade.set_package_hold(hold_version) |
1636 | + for package, hold_version in versions_to_remove: |
1637 | + self._facade.remove_package_hold(hold_version) |
1638 | + |
1639 | + self._facade.reload_channels() |
1640 | + |
1641 | + response = {"type": "operation-result", |
1642 | + "operation-id": message.get("operation-id"), |
1643 | + "status": SUCCEEDED, |
1644 | + "result-text": "Package holds successfully changed.", |
1645 | + "result-code": 0} |
1646 | + |
1647 | + return self._send_change_package_holds_response(response) |
1648 | |
1649 | @staticmethod |
1650 | def find_command(): |
1651 | |
1652 | === modified file 'landscape/package/facade.py' |
1653 | --- landscape/package/facade.py 2012-06-19 15:12:07 +0000 |
1654 | +++ landscape/package/facade.py 2014-11-19 18:22:39 +0000 |
1655 | @@ -1,3 +1,4 @@ |
1656 | +<<<<<<< TREE |
1657 | import hashlib |
1658 | import os |
1659 | import subprocess |
1660 | @@ -22,6 +23,47 @@ |
1661 | from landscape.lib.fs import append_file, create_file, read_file |
1662 | from landscape.constants import UBUNTU_PATH |
1663 | from landscape.package.skeleton import build_skeleton_apt |
1664 | +======= |
1665 | +import hashlib |
1666 | +import os |
1667 | +import subprocess |
1668 | +import tempfile |
1669 | +from cStringIO import StringIO |
1670 | +from operator import attrgetter |
1671 | + |
1672 | +has_smart = True |
1673 | +try: |
1674 | + import smart |
1675 | + from smart.transaction import ( |
1676 | + Transaction, PolicyInstall, PolicyUpgrade, PolicyRemove, Failed) |
1677 | + from smart.const import INSTALL, REMOVE, UPGRADE, ALWAYS, NEVER |
1678 | +except ImportError: |
1679 | + has_smart = False |
1680 | + |
1681 | +# Importing apt throws a FutureWarning on hardy, that we don't want to |
1682 | +# see. |
1683 | +import warnings |
1684 | +warnings.filterwarnings("ignore", module="apt", category=FutureWarning) |
1685 | +del warnings |
1686 | + |
1687 | +import apt |
1688 | +import apt_inst |
1689 | +import apt_pkg |
1690 | + |
1691 | +has_new_enough_apt = True |
1692 | +from aptsources.sourceslist import SourcesList |
1693 | +try: |
1694 | + from apt.progress.text import AcquireProgress |
1695 | + from apt.progress.base import InstallProgress |
1696 | +except ImportError: |
1697 | + AcquireProgress = object |
1698 | + InstallProgress = object |
1699 | + has_new_enough_apt = False |
1700 | + |
1701 | +from landscape.lib.fs import append_file, create_file, read_file |
1702 | +from landscape.constants import UBUNTU_PATH |
1703 | +from landscape.package.skeleton import build_skeleton, build_skeleton_apt |
1704 | +>>>>>>> MERGE-SOURCE |
1705 | |
1706 | |
1707 | class TransactionError(Exception): |
1708 | @@ -43,64 +85,708 @@ |
1709 | """Raised when channels fail to load.""" |
1710 | |
1711 | |
1712 | -class LandscapeAcquireProgress(AcquireProgress): |
1713 | - |
1714 | - def _winch(self, *dummy): |
1715 | - """Override trying to get the column count of the buffer. |
1716 | - |
1717 | - We always send the output to a file, not to a terminal, so the |
1718 | - default width (80 columns) is fine for us. |
1719 | - |
1720 | - Overriding this method means that we don't have to care about |
1721 | - fcntl.ioctl API differences for different Python versions. |
1722 | - """ |
1723 | - |
1724 | - |
1725 | -class LandscapeInstallProgress(InstallProgress): |
1726 | - |
1727 | - dpkg_exited = None |
1728 | - |
1729 | - def wait_child(self): |
1730 | - """Override to find out whether dpkg exited or not. |
1731 | - |
1732 | - The C{run()} method returns os.WEXITSTATUS(res) without checking |
1733 | - os.WIFEXITED(res) first, so it can signal that everything is ok, |
1734 | - even though something causes dpkg not to exit cleanly. |
1735 | - |
1736 | - Save whether dpkg exited cleanly into the C{dpkg_exited} |
1737 | - attribute. If dpkg exited cleanly the exit code can be used to |
1738 | - determine whether there were any errors. If dpkg didn't exit |
1739 | - cleanly it should mean that something went wrong. |
1740 | - """ |
1741 | - res = super(LandscapeInstallProgress, self).wait_child() |
1742 | - self.dpkg_exited = os.WIFEXITED(res) |
1743 | - return res |
1744 | - |
1745 | - |
1746 | -class AptFacade(object): |
1747 | - """Wrapper for tasks using Apt. |
1748 | - |
1749 | - This object wraps Apt features, in a way that makes using and testing |
1750 | - these features slightly more comfortable. |
1751 | - |
1752 | - @param root: The root dir of the Apt configuration files. |
1753 | - @ivar refetch_package_index: Whether to refetch the package indexes |
1754 | - when reloading the channels, or reuse the existing local |
1755 | - database. |
1756 | - """ |
1757 | - |
1758 | - _dpkg_status = "/var/lib/dpkg/status" |
1759 | - |
1760 | - def __init__(self, root=None): |
1761 | - self._root = root |
1762 | - self._dpkg_args = [] |
1763 | - if self._root is not None: |
1764 | - self._ensure_dir_structure() |
1765 | - self._dpkg_args.extend(["--root", self._root]) |
1766 | - # don't use memonly=True here because of a python-apt bug on Natty when |
1767 | - # sources.list contains invalid lines (LP: #886208) |
1768 | - self._cache = apt.cache.Cache(rootdir=root) |
1769 | - self._channels_loaded = False |
1770 | +<<<<<<< TREE |
1771 | +class LandscapeAcquireProgress(AcquireProgress): |
1772 | + |
1773 | + def _winch(self, *dummy): |
1774 | + """Override trying to get the column count of the buffer. |
1775 | + |
1776 | + We always send the output to a file, not to a terminal, so the |
1777 | + default width (80 columns) is fine for us. |
1778 | + |
1779 | + Overriding this method means that we don't have to care about |
1780 | + fcntl.ioctl API differences for different Python versions. |
1781 | + """ |
1782 | + |
1783 | + |
1784 | +class LandscapeInstallProgress(InstallProgress): |
1785 | + |
1786 | + dpkg_exited = None |
1787 | + |
1788 | + def wait_child(self): |
1789 | + """Override to find out whether dpkg exited or not. |
1790 | + |
1791 | + The C{run()} method returns os.WEXITSTATUS(res) without checking |
1792 | + os.WIFEXITED(res) first, so it can signal that everything is ok, |
1793 | + even though something causes dpkg not to exit cleanly. |
1794 | + |
1795 | + Save whether dpkg exited cleanly into the C{dpkg_exited} |
1796 | + attribute. If dpkg exited cleanly the exit code can be used to |
1797 | + determine whether there were any errors. If dpkg didn't exit |
1798 | + cleanly it should mean that something went wrong. |
1799 | + """ |
1800 | + res = super(LandscapeInstallProgress, self).wait_child() |
1801 | + self.dpkg_exited = os.WIFEXITED(res) |
1802 | + return res |
1803 | + |
1804 | + |
1805 | +class AptFacade(object): |
1806 | + """Wrapper for tasks using Apt. |
1807 | + |
1808 | + This object wraps Apt features, in a way that makes using and testing |
1809 | +======= |
1810 | +class LandscapeAcquireProgress(AcquireProgress): |
1811 | + |
1812 | + def _winch(self, *dummy): |
1813 | + """Override trying to get the column count of the buffer. |
1814 | + |
1815 | + We always send the output to a file, not to a terminal, so the |
1816 | + default width (80 columns) is fine for us. |
1817 | + |
1818 | + Overriding this method means that we don't have to care about |
1819 | + fcntl.ioctl API differences for different Python versions. |
1820 | + """ |
1821 | + |
1822 | + |
1823 | +class LandscapeInstallProgress(InstallProgress): |
1824 | + |
1825 | + dpkg_exited = None |
1826 | + |
1827 | + def wait_child(self): |
1828 | + """Override to find out whether dpkg exited or not. |
1829 | + |
1830 | + The C{run()} method returns os.WEXITSTATUS(res) without checking |
1831 | + os.WIFEXITED(res) first, so it can signal that everything is ok, |
1832 | + even though something causes dpkg not to exit cleanly. |
1833 | + |
1834 | + Save whether dpkg exited cleanly into the C{dpkg_exited} |
1835 | + attribute. If dpkg exited cleanly the exit code can be used to |
1836 | + determine whether there were any errors. If dpkg didn't exit |
1837 | + cleanly it should mean that something went wrong. |
1838 | + """ |
1839 | + res = super(LandscapeInstallProgress, self).wait_child() |
1840 | + self.dpkg_exited = os.WIFEXITED(res) |
1841 | + return res |
1842 | + |
1843 | + |
1844 | +class AptFacade(object): |
1845 | + """Wrapper for tasks using Apt. |
1846 | + |
1847 | + This object wraps Apt features, in a way that makes using and testing |
1848 | + these features slightly more comfortable. |
1849 | + |
1850 | + @param root: The root dir of the Apt configuration files. |
1851 | + @ivar refetch_package_index: Whether to refetch the package indexes |
1852 | + when reloading the channels, or reuse the existing local |
1853 | + database. |
1854 | + """ |
1855 | + |
1856 | + supports_package_holds = True |
1857 | + supports_package_locks = False |
1858 | + _dpkg_status = "/var/lib/dpkg/status" |
1859 | + |
1860 | + def __init__(self, root=None): |
1861 | + self._root = root |
1862 | + self._dpkg_args = [] |
1863 | + if self._root is not None: |
1864 | + self._ensure_dir_structure() |
1865 | + self._dpkg_args.extend(["--root", self._root]) |
1866 | + # don't use memonly=True here because of a python-apt bug on Natty when |
1867 | + # sources.list contains invalid lines (LP: #886208) |
1868 | + self._cache = apt.cache.Cache(rootdir=root) |
1869 | + self._channels_loaded = False |
1870 | + self._pkg2hash = {} |
1871 | + self._hash2pkg = {} |
1872 | + self._version_installs = [] |
1873 | + self._global_upgrade = False |
1874 | + self._version_removals = [] |
1875 | + self.refetch_package_index = False |
1876 | + |
1877 | + def _ensure_dir_structure(self): |
1878 | + self._ensure_sub_dir("etc/apt") |
1879 | + self._ensure_sub_dir("etc/apt/sources.list.d") |
1880 | + self._ensure_sub_dir("var/cache/apt/archives/partial") |
1881 | + self._ensure_sub_dir("var/lib/apt/lists/partial") |
1882 | + dpkg_dir = self._ensure_sub_dir("var/lib/dpkg") |
1883 | + self._ensure_sub_dir("var/lib/dpkg/info") |
1884 | + self._ensure_sub_dir("var/lib/dpkg/updates") |
1885 | + self._ensure_sub_dir("var/lib/dpkg/triggers") |
1886 | + create_file(os.path.join(dpkg_dir, "available"), "") |
1887 | + self._dpkg_status = os.path.join(dpkg_dir, "status") |
1888 | + if not os.path.exists(self._dpkg_status): |
1889 | + create_file(self._dpkg_status, "") |
1890 | + |
1891 | + def _ensure_sub_dir(self, sub_dir): |
1892 | + """Ensure that a dir in the Apt root exists.""" |
1893 | + full_path = os.path.join(self._root, sub_dir) |
1894 | + if not os.path.exists(full_path): |
1895 | + os.makedirs(full_path) |
1896 | + return full_path |
1897 | + |
1898 | + def deinit(self): |
1899 | + """This method exists solely to be compatible with C{SmartFacade}.""" |
1900 | + |
1901 | + def get_packages(self): |
1902 | + """Get all the packages available in the channels.""" |
1903 | + return self._hash2pkg.itervalues() |
1904 | + |
1905 | + def get_locked_packages(self): |
1906 | + """Get all packages in the channels that are locked. |
1907 | + |
1908 | + For Apt, it means all packages that are held. |
1909 | + """ |
1910 | + return [ |
1911 | + version for version in self.get_packages() |
1912 | + if (self.is_package_installed(version) |
1913 | + and self._is_package_held(version.package))] |
1914 | + |
1915 | + def get_package_locks(self): |
1916 | + """Return all set package locks. |
1917 | + |
1918 | + @return: A C{list} of ternary tuples, contaning the name, relation |
1919 | + and version details for each lock currently set on the system. |
1920 | + |
1921 | + XXX: This method isn't implemented yet. It's here to make the |
1922 | + transition to Apt in the package reporter easier. |
1923 | + """ |
1924 | + return [] |
1925 | + |
1926 | + def get_package_holds(self): |
1927 | + """Return the name of all the packages that are on hold.""" |
1928 | + return [version.package.name for version in self.get_locked_packages()] |
1929 | + |
1930 | + def _set_dpkg_selections(self, selection): |
1931 | + """Set the dpkg selection. |
1932 | + |
1933 | + It basically does "echo $selection | dpkg --set-selections". |
1934 | + """ |
1935 | + process = subprocess.Popen( |
1936 | + ["dpkg", "--set-selections"] + self._dpkg_args, |
1937 | + stdin=subprocess.PIPE) |
1938 | + process.communicate(selection) |
1939 | + |
1940 | + def set_package_hold(self, version): |
1941 | + """Add a dpkg hold for a package. |
1942 | + |
1943 | + @param version: The version of the package to hold. |
1944 | + """ |
1945 | + self._set_dpkg_selections(version.package.name + " hold") |
1946 | + |
1947 | + def remove_package_hold(self, version): |
1948 | + """Removes a dpkg hold for a package. |
1949 | + |
1950 | + @param version: The version of the package to unhold. |
1951 | + """ |
1952 | + if not self._is_package_held(version.package): |
1953 | + return |
1954 | + self._set_dpkg_selections(version.package.name + " install") |
1955 | + |
1956 | + def reload_channels(self, force_reload_binaries=False): |
1957 | + """Reload the channels and update the cache. |
1958 | + |
1959 | + @param force_reload_binaries: Whether to always reload |
1960 | + information about the binaries packages that are in the facade's |
1961 | + internal repo. |
1962 | + """ |
1963 | + self._cache.open(None) |
1964 | + internal_sources_list = self._get_internal_sources_list() |
1965 | + if (self.refetch_package_index or |
1966 | + (force_reload_binaries and os.path.exists(internal_sources_list))): |
1967 | + # Try to update only the internal repos, if the python-apt |
1968 | + # version is new enough to accept a sources_list parameter. |
1969 | + new_apt_args = {} |
1970 | + if force_reload_binaries and not self.refetch_package_index: |
1971 | + new_apt_args["sources_list"] = internal_sources_list |
1972 | + try: |
1973 | + try: |
1974 | + self._cache.update(**new_apt_args) |
1975 | + except TypeError: |
1976 | + self._cache.update() |
1977 | + except apt.cache.FetchFailedException: |
1978 | + raise ChannelError( |
1979 | + "Apt failed to reload channels (%r)" % ( |
1980 | + self.get_channels())) |
1981 | + self._cache.open(None) |
1982 | + |
1983 | + self._pkg2hash.clear() |
1984 | + self._hash2pkg.clear() |
1985 | + for package in self._cache: |
1986 | + if not self._is_main_architecture(package): |
1987 | + continue |
1988 | + for version in package.versions: |
1989 | + hash = self.get_package_skeleton( |
1990 | + version, with_info=False).get_hash() |
1991 | + # Use a tuple including the package, since the Version |
1992 | + # objects of two different packages can have the same |
1993 | + # hash. |
1994 | + self._pkg2hash[(package, version)] = hash |
1995 | + self._hash2pkg[hash] = version |
1996 | + self._channels_loaded = True |
1997 | + |
1998 | + def ensure_channels_reloaded(self): |
1999 | + """Reload the channels if they haven't been reloaded yet.""" |
2000 | + if self._channels_loaded: |
2001 | + return |
2002 | + self.reload_channels() |
2003 | + |
2004 | + def _get_internal_sources_list(self): |
2005 | + """Return the path to the source.list file for the facade channels.""" |
2006 | + sources_dir = apt_pkg.config.find_dir("Dir::Etc::sourceparts") |
2007 | + return os.path.join(sources_dir, "_landscape-internal-facade.list") |
2008 | + |
2009 | + def add_channel_apt_deb(self, url, codename, components=None): |
2010 | + """Add a deb URL which points to a repository. |
2011 | + |
2012 | + @param url: The base URL of the repository. |
2013 | + @param codename: The dist in the repository. |
2014 | + @param components: The components to be included. |
2015 | + """ |
2016 | + sources_file_path = self._get_internal_sources_list() |
2017 | + sources_line = "deb %s %s" % (url, codename) |
2018 | + if components: |
2019 | + sources_line += " %s" % " ".join(components) |
2020 | + if os.path.exists(sources_file_path): |
2021 | + current_content = read_file(sources_file_path).split("\n") |
2022 | + if sources_line in current_content: |
2023 | + return |
2024 | + sources_line += "\n" |
2025 | + append_file(sources_file_path, sources_line) |
2026 | + |
2027 | + def add_channel_deb_dir(self, path): |
2028 | + """Add a directory with packages as a channel. |
2029 | + |
2030 | + @param path: The path to the directory containing the packages. |
2031 | + |
2032 | + A Packages file is created in the directory with information |
2033 | + about the deb files. |
2034 | + """ |
2035 | + self._create_packages_file(path) |
2036 | + self.add_channel_apt_deb("file://%s" % path, "./", None) |
2037 | + |
2038 | + def clear_channels(self): |
2039 | + """Clear the channels that have been added through the facade. |
2040 | + |
2041 | + Channels that weren't added through the facade (i.e. |
2042 | + /etc/apt/sources.list and /etc/apt/sources.list.d) won't be |
2043 | + removed. |
2044 | + """ |
2045 | + sources_file_path = self._get_internal_sources_list() |
2046 | + if os.path.exists(sources_file_path): |
2047 | + os.remove(sources_file_path) |
2048 | + |
2049 | + def _create_packages_file(self, deb_dir): |
2050 | + """Create a Packages file in a directory with debs.""" |
2051 | + packages_contents = "\n".join( |
2052 | + self.get_package_stanza(os.path.join(deb_dir, filename)) |
2053 | + for filename in sorted(os.listdir(deb_dir))) |
2054 | + create_file(os.path.join(deb_dir, "Packages"), packages_contents) |
2055 | + |
2056 | + def get_channels(self): |
2057 | + """Return a list of channels configured. |
2058 | + |
2059 | + A channel is a deb line in sources.list or sources.list.d. It's |
2060 | + represented by a dict with baseurl, distribution, components, |
2061 | + and type keys. |
2062 | + """ |
2063 | + sources_list = SourcesList() |
2064 | + return [{"baseurl": entry.uri, "distribution": entry.dist, |
2065 | + "components": " ".join(entry.comps), "type": entry.type} |
2066 | + for entry in sources_list if not entry.disabled] |
2067 | + |
2068 | + def reset_channels(self): |
2069 | + """Remove all the configured channels.""" |
2070 | + sources_list = SourcesList() |
2071 | + for entry in sources_list: |
2072 | + entry.set_enabled(False) |
2073 | + sources_list.save() |
2074 | + |
2075 | + def get_package_stanza(self, deb_path): |
2076 | + """Return a stanza for the package to be included in a Packages file. |
2077 | + |
2078 | + @param deb_path: The path to the deb package. |
2079 | + """ |
2080 | + deb_file = open(deb_path) |
2081 | + deb = apt_inst.DebFile(deb_file) |
2082 | + control = deb.control.extractdata("control") |
2083 | + deb_file.close() |
2084 | + filename = os.path.basename(deb_path) |
2085 | + size = os.path.getsize(deb_path) |
2086 | + contents = read_file(deb_path) |
2087 | + md5 = hashlib.md5(contents).hexdigest() |
2088 | + sha1 = hashlib.sha1(contents).hexdigest() |
2089 | + sha256 = hashlib.sha256(contents).hexdigest() |
2090 | + # Use rewrite_section to ensure that the field order is correct. |
2091 | + return apt_pkg.rewrite_section( |
2092 | + apt_pkg.TagSection(control), apt_pkg.REWRITE_PACKAGE_ORDER, |
2093 | + [("Filename", filename), ("Size", str(size)), |
2094 | + ("MD5sum", md5), ("SHA1", sha1), ("SHA256", sha256)]) |
2095 | + |
2096 | + def get_arch(self): |
2097 | + """Return the architecture APT is configured to use.""" |
2098 | + return apt_pkg.config.get("APT::Architecture") |
2099 | + |
2100 | + def set_arch(self, architecture): |
2101 | + """Set the architecture that APT should use. |
2102 | + |
2103 | + Setting multiple architectures isn't supported. |
2104 | + """ |
2105 | + if architecture is None: |
2106 | + architecture = "" |
2107 | + # From oneiric and onwards Architectures is used to set which |
2108 | + # architectures can be installed, in case multiple architectures |
2109 | + # are supported. We force it to be single architecture, until we |
2110 | + # have a plan for supporting multiple architectures. |
2111 | + apt_pkg.config.clear("APT::Architectures") |
2112 | + apt_pkg.config.set("APT::Architectures::", architecture) |
2113 | + result = apt_pkg.config.set("APT::Architecture", architecture) |
2114 | + # Reload the cache, otherwise architecture change isn't reflected in |
2115 | + # package list |
2116 | + self._cache.open(None) |
2117 | + return result |
2118 | + |
2119 | + def get_package_skeleton(self, pkg, with_info=True): |
2120 | + """Return a skeleton for the provided package. |
2121 | + |
2122 | + The skeleton represents the basic structure of the package. |
2123 | + |
2124 | + @param pkg: Package to build skeleton from. |
2125 | + @param with_info: If True, the skeleton will include information |
2126 | + useful for sending data to the server. Such information isn't |
2127 | + necessary if the skeleton will be used to build a hash. |
2128 | + |
2129 | + @return: a L{PackageSkeleton} object. |
2130 | + """ |
2131 | + return build_skeleton_apt(pkg, with_info=with_info, with_unicode=True) |
2132 | + |
2133 | + def get_package_hash(self, version): |
2134 | + """Return a hash from the given package. |
2135 | + |
2136 | + @param version: an L{apt.package.Version} object. |
2137 | + """ |
2138 | + return self._pkg2hash.get((version.package, version)) |
2139 | + |
2140 | + def get_package_hashes(self): |
2141 | + """Get the hashes of all the packages available in the channels.""" |
2142 | + return self._pkg2hash.values() |
2143 | + |
2144 | + def get_package_by_hash(self, hash): |
2145 | + """Get the package having the provided hash. |
2146 | + |
2147 | + @param hash: The hash the package should have. |
2148 | + |
2149 | + @return: The L{apt.package.Package} that has the given hash. |
2150 | + """ |
2151 | + return self._hash2pkg.get(hash) |
2152 | + |
2153 | + def is_package_installed(self, version): |
2154 | + """Is the package version installed?""" |
2155 | + return version == version.package.installed |
2156 | + |
2157 | + def is_package_available(self, version): |
2158 | + """Is the package available for installation?""" |
2159 | + return version.downloadable |
2160 | + |
2161 | + def is_package_upgrade(self, version): |
2162 | + """Is the package an upgrade for another installed package?""" |
2163 | + if not version.package.is_upgradable or not version.package.installed: |
2164 | + return False |
2165 | + return version > version.package.installed |
2166 | + |
2167 | + def _is_main_architecture(self, package): |
2168 | + """Is the package for the facade's main architecture?""" |
2169 | + # package.name includes the architecture, if it's for a foreign |
2170 | + # architectures. package.shortname never includes the |
2171 | + # architecture. package.shortname doesn't exist on releases that |
2172 | + # don't support multi-arch, though. |
2173 | + if not hasattr(package, "shortname"): |
2174 | + return True |
2175 | + return package.name == package.shortname |
2176 | + |
2177 | + def _is_package_held(self, package): |
2178 | + """Is the package marked as held?""" |
2179 | + return package._pkg.selected_state == apt_pkg.SELSTATE_HOLD |
2180 | + |
2181 | + def get_packages_by_name(self, name): |
2182 | + """Get all available packages matching the provided name. |
2183 | + |
2184 | + @param name: The name the returned packages should have. |
2185 | + """ |
2186 | + return [ |
2187 | + version for version in self.get_packages() |
2188 | + if version.package.name == name] |
2189 | + |
2190 | + def _get_broken_packages(self): |
2191 | + """Return the packages that are in a broken state.""" |
2192 | + return set( |
2193 | + version.package for version in self.get_packages() |
2194 | + if version.package.is_inst_broken) |
2195 | + |
2196 | + def _get_changed_versions(self, package): |
2197 | + """Return the versions that will be changed for the package. |
2198 | + |
2199 | + Apt gives us that a package is going to be changed and have |
2200 | + variables set on the package to indicate what will change. We |
2201 | + need to convert that into a list of versions that will be either |
2202 | + installed or removed, which is what the server expects to get. |
2203 | + """ |
2204 | + if package.marked_install: |
2205 | + return [package.candidate] |
2206 | + if package.marked_upgrade or package.marked_downgrade: |
2207 | + return [package.installed, package.candidate] |
2208 | + if package.marked_delete: |
2209 | + return [package.installed] |
2210 | + return None |
2211 | + |
2212 | + def _check_changes(self, requested_changes): |
2213 | + """Check that the changes Apt will do have all been requested. |
2214 | + |
2215 | + @raises DependencyError: If some change hasn't been explicitly |
2216 | + requested. |
2217 | + @return: C{True} if all the changes that Apt will perform have |
2218 | + been requested. |
2219 | + """ |
2220 | + # Build tuples of (package, version) so that we can do |
2221 | + # comparison checks. Same versions of different packages compare |
2222 | + # as being the same, so we need to include the package as well. |
2223 | + all_changes = [ |
2224 | + (version.package, version) for version in requested_changes] |
2225 | + versions_to_be_changed = set() |
2226 | + for package in self._cache.get_changes(): |
2227 | + if not self._is_main_architecture(package): |
2228 | + continue |
2229 | + versions = self._get_changed_versions(package) |
2230 | + versions_to_be_changed.update( |
2231 | + (package, version) for version in versions) |
2232 | + dependencies = versions_to_be_changed.difference(all_changes) |
2233 | + if dependencies: |
2234 | + raise DependencyError( |
2235 | + [version for package, version in dependencies]) |
2236 | + return len(versions_to_be_changed) > 0 |
2237 | + |
2238 | + def _get_unmet_relation_info(self, dep_relation): |
2239 | + """Return a string representation of a specific dependency relation.""" |
2240 | + info = dep_relation.target_pkg.name |
2241 | + if dep_relation.target_ver: |
2242 | + info += " (%s %s)" % ( |
2243 | + dep_relation.comp_type, dep_relation.target_ver) |
2244 | + reason = " but is not installable" |
2245 | + if dep_relation.target_pkg.name in self._cache: |
2246 | + dep_package = self._cache[dep_relation.target_pkg.name] |
2247 | + if dep_package.installed or dep_package.marked_install: |
2248 | + version = dep_package.candidate.version |
2249 | + if dep_package not in self._cache.get_changes(): |
2250 | + version = dep_package.installed.version |
2251 | + reason = " but %s is to be installed" % version |
2252 | + info += reason |
2253 | + return info |
2254 | + |
2255 | + def _is_dependency_satisfied(self, dependency, dep_type): |
2256 | + """Return whether a dependency is satisfied. |
2257 | + |
2258 | + For positive dependencies (Pre-Depends, Depends) it means that |
2259 | + one of its targets is going to be installed. For negative |
2260 | + dependencies (Conflicts, Breaks), it means that none of its |
2261 | + targets are going to be installed. |
2262 | + """ |
2263 | + is_positive = dep_type not in ["Breaks", "Conflicts"] |
2264 | + depcache = self._cache._depcache |
2265 | + for or_dep in dependency: |
2266 | + for target in or_dep.all_targets(): |
2267 | + package = target.parent_pkg |
2268 | + if ((package.current_state == apt_pkg.CURSTATE_INSTALLED |
2269 | + or depcache.marked_install(package)) |
2270 | + and not depcache.marked_delete(package)): |
2271 | + return is_positive |
2272 | + return not is_positive |
2273 | + |
2274 | + def _get_unmet_dependency_info(self): |
2275 | + """Get information about unmet dependencies in the cache state. |
2276 | + |
2277 | + Go through all the broken packages and say which dependencies |
2278 | + haven't been satisfied. |
2279 | + |
2280 | + @return: A string with dependency information like what you get |
2281 | + from apt-get. |
2282 | + """ |
2283 | + |
2284 | + broken_packages = self._get_broken_packages() |
2285 | + if not broken_packages: |
2286 | + return "" |
2287 | + all_info = ["The following packages have unmet dependencies:"] |
2288 | + for package in sorted(broken_packages, key=attrgetter("name")): |
2289 | + for dep_type in ["PreDepends", "Depends", "Conflicts", "Breaks"]: |
2290 | + dependencies = package.candidate._cand.depends_list.get( |
2291 | + dep_type, []) |
2292 | + for dependency in dependencies: |
2293 | + if self._is_dependency_satisfied(dependency, dep_type): |
2294 | + continue |
2295 | + relation_infos = [] |
2296 | + for dep_relation in dependency: |
2297 | + relation_infos.append( |
2298 | + self._get_unmet_relation_info(dep_relation)) |
2299 | + info = " %s: %s: " % (package.name, dep_type) |
2300 | + or_divider = " or\n" + " " * len(info) |
2301 | + all_info.append(info + or_divider.join(relation_infos)) |
2302 | + return "\n".join(all_info) |
2303 | + |
2304 | + def perform_changes(self): |
2305 | + """Perform the pending package operations.""" |
2306 | + # Try to enforce non-interactivity |
2307 | + os.environ["DEBIAN_FRONTEND"] = "noninteractive" |
2308 | + os.environ["APT_LISTCHANGES_FRONTEND"] = "none" |
2309 | + os.environ["APT_LISTBUGS_FRONTEND"] = "none" |
2310 | + # dpkg will fail if no path is set. |
2311 | + if "PATH" not in os.environ: |
2312 | + os.environ["PATH"] = UBUNTU_PATH |
2313 | + apt_pkg.config.clear("DPkg::options") |
2314 | + apt_pkg.config.set("DPkg::options::", "--force-confold") |
2315 | + |
2316 | + held_package_names = set() |
2317 | + package_installs = set( |
2318 | + version.package for version in self._version_installs) |
2319 | + package_upgrades = set( |
2320 | + version.package for version in self._version_removals |
2321 | + if version.package in package_installs) |
2322 | + version_changes = self._version_installs[:] |
2323 | + version_changes.extend(self._version_removals) |
2324 | + if not version_changes and not self._global_upgrade: |
2325 | + return None |
2326 | + fixer = apt_pkg.ProblemResolver(self._cache._depcache) |
2327 | + already_broken_packages = self._get_broken_packages() |
2328 | + for version in self._version_installs: |
2329 | + # Set the candidate version, so that the version we want to |
2330 | + # install actually is the one getting installed. |
2331 | + version.package.candidate = version |
2332 | + version.package.mark_install(auto_fix=False) |
2333 | + # If we need to resolve dependencies, try avoiding having |
2334 | + # the package we asked to be installed from being removed. |
2335 | + # (This is what would have been done if auto_fix would have |
2336 | + # been True. |
2337 | + fixer.clear(version.package._pkg) |
2338 | + fixer.protect(version.package._pkg) |
2339 | + if self._global_upgrade: |
2340 | + self._cache.upgrade(dist_upgrade=True) |
2341 | + for version in self._version_removals: |
2342 | + if self._is_package_held(version.package): |
2343 | + held_package_names.add(version.package.name) |
2344 | + if version.package in package_upgrades: |
2345 | + # The server requests the old version to be removed for |
2346 | + # upgrades, since Smart worked that way. For Apt we have |
2347 | + # to take care not to mark upgraded packages for # removal. |
2348 | + continue |
2349 | + version.package.mark_delete(auto_fix=False) |
2350 | + # Configure the resolver in the same way |
2351 | + # mark_delete(auto_fix=True) would have done. |
2352 | + fixer.clear(version.package._pkg) |
2353 | + fixer.protect(version.package._pkg) |
2354 | + fixer.remove(version.package._pkg) |
2355 | + fixer.install_protect() |
2356 | + |
2357 | + if held_package_names: |
2358 | + raise TransactionError( |
2359 | + "Can't perform the changes, since the following packages" + |
2360 | + " are held: %s" % ", ".join(sorted(held_package_names))) |
2361 | + |
2362 | + now_broken_packages = self._get_broken_packages() |
2363 | + if now_broken_packages != already_broken_packages: |
2364 | + try: |
2365 | + fixer.resolve(True) |
2366 | + except SystemError, error: |
2367 | + raise TransactionError( |
2368 | + error.args[0] + "\n" + self._get_unmet_dependency_info()) |
2369 | + if not self._check_changes(version_changes): |
2370 | + return None |
2371 | + fetch_output = StringIO() |
2372 | + # Redirect stdout and stderr to a file. We need to work with the |
2373 | + # file descriptors, rather than sys.stdout/stderr, since dpkg is |
2374 | + # run in a subprocess. |
2375 | + fd, install_output_path = tempfile.mkstemp() |
2376 | + old_stdout = os.dup(1) |
2377 | + old_stderr = os.dup(2) |
2378 | + os.dup2(fd, 1) |
2379 | + os.dup2(fd, 2) |
2380 | + install_progress = LandscapeInstallProgress() |
2381 | + try: |
2382 | + self._cache.commit( |
2383 | + fetch_progress=LandscapeAcquireProgress(fetch_output), |
2384 | + install_progress=install_progress) |
2385 | + if not install_progress.dpkg_exited: |
2386 | + raise SystemError("dpkg didn't exit cleanly.") |
2387 | + except SystemError, error: |
2388 | + result_text = ( |
2389 | + fetch_output.getvalue() + read_file(install_output_path)) |
2390 | + raise TransactionError( |
2391 | + error.args[0] + "\n\nPackage operation log:\n" + result_text) |
2392 | + else: |
2393 | + result_text = ( |
2394 | + fetch_output.getvalue() + read_file(install_output_path)) |
2395 | + finally: |
2396 | + # Restore stdout and stderr. |
2397 | + os.dup2(old_stdout, 1) |
2398 | + os.dup2(old_stderr, 2) |
2399 | + os.remove(install_output_path) |
2400 | + return result_text |
2401 | + |
2402 | + def reset_marks(self): |
2403 | + """Clear the pending package operations.""" |
2404 | + del self._version_installs[:] |
2405 | + del self._version_removals[:] |
2406 | + self._global_upgrade = False |
2407 | + self._cache.clear() |
2408 | + |
2409 | + def mark_install(self, version): |
2410 | + """Mark the package for installation.""" |
2411 | + self._version_installs.append(version) |
2412 | + |
2413 | + def mark_global_upgrade(self): |
2414 | + """Upgrade all installed packages.""" |
2415 | + self._global_upgrade = True |
2416 | + |
2417 | + def mark_remove(self, version): |
2418 | + """Mark the package for removal.""" |
2419 | + self._version_removals.append(version) |
2420 | + |
2421 | + |
2422 | +class SmartFacade(object): |
2423 | + """Wrapper for tasks using Smart. |
2424 | + |
2425 | + This object wraps Smart features, in a way that makes using and testing |
2426 | +>>>>>>> MERGE-SOURCE |
2427 | + these features slightly more comfortable. |
2428 | +<<<<<<< TREE |
2429 | + |
2430 | + @param root: The root dir of the Apt configuration files. |
2431 | + @ivar refetch_package_index: Whether to refetch the package indexes |
2432 | + when reloading the channels, or reuse the existing local |
2433 | + database. |
2434 | +======= |
2435 | + |
2436 | + @param smart_init_kwargs: A dictionary that can be used to pass specific |
2437 | + keyword parameters to to L{smart.init}. |
2438 | +>>>>>>> MERGE-SOURCE |
2439 | + """ |
2440 | + |
2441 | +<<<<<<< TREE |
2442 | + _dpkg_status = "/var/lib/dpkg/status" |
2443 | + |
2444 | + def __init__(self, root=None): |
2445 | + self._root = root |
2446 | + self._dpkg_args = [] |
2447 | + if self._root is not None: |
2448 | + self._ensure_dir_structure() |
2449 | + self._dpkg_args.extend(["--root", self._root]) |
2450 | + # don't use memonly=True here because of a python-apt bug on Natty when |
2451 | + # sources.list contains invalid lines (LP: #886208) |
2452 | + self._cache = apt.cache.Cache(rootdir=root) |
2453 | + self._channels_loaded = False |
2454 | +======= |
2455 | + _deb_package_type = None |
2456 | + supports_package_holds = False |
2457 | + supports_package_locks = True |
2458 | + |
2459 | + def __init__(self, smart_init_kwargs={}, sysconf_args=None): |
2460 | + if not has_smart: |
2461 | + raise RuntimeError( |
2462 | + "Smart needs to be installed if SmartFacade is used.") |
2463 | + self._smart_init_kwargs = smart_init_kwargs.copy() |
2464 | + self._smart_init_kwargs.setdefault("interface", "landscape") |
2465 | + self._sysconfig_args = sysconf_args or {} |
2466 | + self._reset() |
2467 | + |
2468 | + def _reset(self): |
2469 | + # This attribute is initialized lazily in the _get_ctrl() method. |
2470 | + self._ctrl = None |
2471 | +>>>>>>> MERGE-SOURCE |
2472 | self._pkg2hash = {} |
2473 | self._hash2pkg = {} |
2474 | self._version_installs = [] |
2475 | @@ -224,6 +910,7 @@ |
2476 | return |
2477 | self.reload_channels() |
2478 | |
2479 | +<<<<<<< TREE |
2480 | def _get_internal_sources_list(self): |
2481 | """Return the path to the source.list file for the facade channels.""" |
2482 | sources_dir = apt_pkg.config.find_dir("Dir::Etc::sourceparts") |
2483 | @@ -338,6 +1025,39 @@ |
2484 | # package list |
2485 | self._cache.open(None) |
2486 | return result |
2487 | +======= |
2488 | + def reload_channels(self, force_reload_binaries=False): |
2489 | + """ |
2490 | + Reload Smart channels, getting all the cache (packages) in memory. |
2491 | + |
2492 | + @raise: L{ChannelError} if Smart fails to reload the channels. |
2493 | + """ |
2494 | + ctrl = self._get_ctrl() |
2495 | + |
2496 | + try: |
2497 | + reload_result = ctrl.reloadChannels(caching=self._caching) |
2498 | + except smart.Error: |
2499 | + failed = True |
2500 | + else: |
2501 | + # Raise an error only if we are trying to download remote lists |
2502 | + failed = not reload_result and self._caching == NEVER |
2503 | + if failed: |
2504 | + raise ChannelError("Smart failed to reload channels (%s)" |
2505 | + % smart.sysconf.get("channels")) |
2506 | + |
2507 | + self._hash2pkg.clear() |
2508 | + self._pkg2hash.clear() |
2509 | + |
2510 | + for pkg in self.get_packages(): |
2511 | + hash = self.get_package_skeleton(pkg, False).get_hash() |
2512 | + self._hash2pkg[hash] = pkg |
2513 | + self._pkg2hash[pkg] = hash |
2514 | + |
2515 | + self.channels_reloaded() |
2516 | + |
2517 | + def channels_reloaded(self): |
2518 | + """Hook called after Smart channels are reloaded.""" |
2519 | +>>>>>>> MERGE-SOURCE |
2520 | |
2521 | def get_package_skeleton(self, pkg, with_info=True): |
2522 | """Return a skeleton for the provided package. |
2523 | @@ -710,7 +1430,14 @@ |
2524 | if len(results) > 0: |
2525 | return " ".join(results) |
2526 | |
2527 | + def mark_global_upgrade(self): |
2528 | + """Upgrade all installed packages.""" |
2529 | + for package in self.get_packages(): |
2530 | + if self.is_package_installed(package): |
2531 | + self.mark_upgrade(package) |
2532 | + |
2533 | def reset_marks(self): |
2534 | +<<<<<<< TREE |
2535 | """Clear the pending package operations.""" |
2536 | del self._version_installs[:] |
2537 | del self._version_removals[:] |
2538 | @@ -738,3 +1465,231 @@ |
2539 | def mark_remove_hold(self, version): |
2540 | """Mark the package to have its hold removed.""" |
2541 | self._version_hold_removals.append(version) |
2542 | +======= |
2543 | + self._marks.clear() |
2544 | + |
2545 | + def perform_changes(self): |
2546 | + ctrl = self._get_ctrl() |
2547 | + cache = ctrl.getCache() |
2548 | + |
2549 | + transaction = Transaction(cache) |
2550 | + |
2551 | + policy = PolicyInstall |
2552 | + |
2553 | + only_remove = True |
2554 | + for pkg, oper in self._marks.items(): |
2555 | + if oper == UPGRADE: |
2556 | + policy = PolicyUpgrade |
2557 | + if oper != REMOVE: |
2558 | + only_remove = False |
2559 | + transaction.enqueue(pkg, oper) |
2560 | + |
2561 | + if only_remove: |
2562 | + policy = PolicyRemove |
2563 | + |
2564 | + transaction.setPolicy(policy) |
2565 | + |
2566 | + try: |
2567 | + transaction.run() |
2568 | + except Failed, e: |
2569 | + raise TransactionError(e.args[0]) |
2570 | + changeset = transaction.getChangeSet() |
2571 | + |
2572 | + if not changeset: |
2573 | + return None # Nothing to do. |
2574 | + |
2575 | + missing = [] |
2576 | + for pkg, op in changeset.items(): |
2577 | + if self._marks.get(pkg) != op: |
2578 | + missing.append(pkg) |
2579 | + if missing: |
2580 | + raise DependencyError(missing) |
2581 | + |
2582 | + try: |
2583 | + self._ctrl.commitChangeSet(changeset) |
2584 | + except smart.Error, e: |
2585 | + raise TransactionError(e.args[0]) |
2586 | + |
2587 | + output = smart.iface.get_output_for_landscape() |
2588 | + failed = smart.iface.has_failed_for_landscape() |
2589 | + |
2590 | + smart.iface.reset_for_landscape() |
2591 | + |
2592 | + if failed: |
2593 | + raise SmartError(output) |
2594 | + return output |
2595 | + |
2596 | + def reload_cache(self): |
2597 | + cache = self._get_ctrl().getCache() |
2598 | + cache.reset() |
2599 | + cache.load() |
2600 | + |
2601 | + def get_arch(self): |
2602 | + """ |
2603 | + Get the host dpkg architecture. |
2604 | + """ |
2605 | + self._get_ctrl() |
2606 | + from smart.backends.deb.loader import DEBARCH |
2607 | + return DEBARCH |
2608 | + |
2609 | + def set_arch(self, arch): |
2610 | + """ |
2611 | + Set the host dpkg architecture. |
2612 | + |
2613 | + To take effect it must be called before L{reload_channels}. |
2614 | + |
2615 | + @param arch: the dpkg architecture to use (e.g. C{"i386"}) |
2616 | + """ |
2617 | + self._get_ctrl() |
2618 | + smart.sysconf.set("deb-arch", arch) |
2619 | + |
2620 | + # XXX workaround Smart setting DEBARCH statically in the |
2621 | + # smart.backends.deb.base module |
2622 | + import smart.backends.deb.loader as loader |
2623 | + loader.DEBARCH = arch |
2624 | + |
2625 | + def set_caching(self, mode): |
2626 | + """ |
2627 | + Set Smart's caching mode. |
2628 | + |
2629 | + @param mode: The caching mode to pass to Smart's C{reloadChannels} |
2630 | + when calling L{reload_channels} (e.g C{smart.const.NEVER} or |
2631 | + C{smart.const.ALWAYS}). |
2632 | + """ |
2633 | + self._caching = mode |
2634 | + |
2635 | + def reset_channels(self): |
2636 | + """Remove all configured Smart channels.""" |
2637 | + self._get_ctrl() |
2638 | + smart.sysconf.set("channels", {}, soft=True) |
2639 | + |
2640 | + def add_channel(self, alias, channel): |
2641 | + """ |
2642 | + Add a Smart channel. |
2643 | + |
2644 | + This method can be called more than once to set multiple channels. |
2645 | + To take effect it must be called before L{reload_channels}. |
2646 | + |
2647 | + @param alias: A string identifying the channel to be added. |
2648 | + @param channel: A C{dict} holding information about the channel to |
2649 | + add (see the Smart API for details about valid keys and values). |
2650 | + """ |
2651 | + channels = self.get_channels() |
2652 | + channels.update({alias: channel}) |
2653 | + smart.sysconf.set("channels", channels, soft=True) |
2654 | + |
2655 | + def add_channel_apt_deb(self, url, codename, components): |
2656 | + """Add a Smart channel of type C{"apt-deb"}. |
2657 | + |
2658 | + @see: L{add_channel} |
2659 | + """ |
2660 | + alias = codename |
2661 | + channel = {"baseurl": url, "distribution": codename, |
2662 | + "components": components, "type": "apt-deb"} |
2663 | + self.add_channel(alias, channel) |
2664 | + |
2665 | + def add_channel_deb_dir(self, path): |
2666 | + """Add a Smart channel of type C{"deb-dir"}. |
2667 | + |
2668 | + @see: L{add_channel} |
2669 | + """ |
2670 | + alias = path |
2671 | + channel = {"path": path, "type": "deb-dir"} |
2672 | + self.add_channel(alias, channel) |
2673 | + |
2674 | + def clear_channels(self): |
2675 | + """Clear channels. |
2676 | + |
2677 | + This method exists to be compatible with AptFacade. Smart |
2678 | + doesn't need to clear its channels. |
2679 | + """ |
2680 | + |
2681 | + def get_channels(self): |
2682 | + """ |
2683 | + @return: A C{dict} of all configured channels. |
2684 | + """ |
2685 | + self._get_ctrl() |
2686 | + return smart.sysconf.get("channels") |
2687 | + |
2688 | + def get_package_locks(self): |
2689 | + """Return all set package locks. |
2690 | + |
2691 | + @return: A C{list} of ternary tuples, contaning the name, relation |
2692 | + and version details for each lock currently set on the system. |
2693 | + """ |
2694 | + self._get_ctrl() |
2695 | + locks = [] |
2696 | + locks_by_name = smart.pkgconf.getFlagTargets("lock") |
2697 | + for name in locks_by_name: |
2698 | + for condition in locks_by_name[name]: |
2699 | + relation = condition[0] or "" |
2700 | + version = condition[1] or "" |
2701 | + locks.append((name, relation, version)) |
2702 | + return locks |
2703 | + |
2704 | + def _validate_lock_condition(self, relation, version): |
2705 | + if relation and not version: |
2706 | + raise RuntimeError("Package lock version not provided") |
2707 | + if version and not relation: |
2708 | + raise RuntimeError("Package lock relation not provided") |
2709 | + |
2710 | + def set_package_lock(self, name, relation=None, version=None): |
2711 | + """Set a new package lock. |
2712 | + |
2713 | + Any package matching the given name and possibly the given version |
2714 | + condition will be locked. |
2715 | + |
2716 | + @param name: The name a package must match in order to be locked. |
2717 | + @param relation: Optionally, the relation of the version condition the |
2718 | + package must satisfy in order to be considered as locked. |
2719 | + @param version: Optionally, the version associated with C{relation}. |
2720 | + |
2721 | + @note: If used at all, the C{relation} and C{version} parameter must be |
2722 | + both provided. |
2723 | + """ |
2724 | + self._validate_lock_condition(relation, version) |
2725 | + self._get_ctrl() |
2726 | + smart.pkgconf.setFlag("lock", name, relation, version) |
2727 | + |
2728 | + def remove_package_lock(self, name, relation=None, version=None): |
2729 | + """Remove a package lock.""" |
2730 | + self._validate_lock_condition(relation, version) |
2731 | + self._get_ctrl() |
2732 | + smart.pkgconf.clearFlag("lock", name=name, relation=relation, |
2733 | + version=version) |
2734 | + |
2735 | + def save_config(self): |
2736 | + """Flush the current smart configuration to disk.""" |
2737 | + control = self._get_ctrl() |
2738 | + control.saveSysConf() |
2739 | + |
2740 | + def is_package_installed(self, package): |
2741 | + """Is the package installed?""" |
2742 | + return package.installed |
2743 | + |
2744 | + def is_package_available(self, package): |
2745 | + """Is the package available for installation?""" |
2746 | + for loader in package.loaders: |
2747 | + # Is the package also in a non-installed |
2748 | + # loader? IOW, "available". |
2749 | + if not loader.getInstalled(): |
2750 | + return True |
2751 | + return False |
2752 | + |
2753 | + def is_package_upgrade(self, package): |
2754 | + """Is the package an upgrade for another installed package?""" |
2755 | + is_upgrade = False |
2756 | + for upgrade in package.upgrades: |
2757 | + for provides in upgrade.providedby: |
2758 | + for provides_package in provides.packages: |
2759 | + if provides_package.installed: |
2760 | + is_upgrade = True |
2761 | + break |
2762 | + else: |
2763 | + continue |
2764 | + break |
2765 | + else: |
2766 | + continue |
2767 | + break |
2768 | + return is_upgrade |
2769 | +>>>>>>> MERGE-SOURCE |
2770 | |
2771 | === added file 'landscape/package/interface.py.OTHER' |
2772 | --- landscape/package/interface.py.OTHER 1970-01-01 00:00:00 +0000 |
2773 | +++ landscape/package/interface.py.OTHER 2014-11-19 18:22:39 +0000 |
2774 | @@ -0,0 +1,84 @@ |
2775 | +import logging |
2776 | +import types |
2777 | +import sys |
2778 | + |
2779 | +try: |
2780 | + import smart.interfaces |
2781 | + from smart.interface import Interface |
2782 | + from smart.const import ERROR, WARNING, INFO, DEBUG |
2783 | +except ImportError: |
2784 | + # Smart is optional if AptFacade is being used. |
2785 | + Interface = object |
2786 | + |
2787 | + |
2788 | +class LandscapeInterface(Interface): |
2789 | + |
2790 | + __output = "" |
2791 | + __failed = False |
2792 | + |
2793 | + def reset_for_landscape(self): |
2794 | + """Reset output and failed flag.""" |
2795 | + self.__failed = False |
2796 | + self.__output = u"" |
2797 | + |
2798 | + def get_output_for_landscape(self): |
2799 | + """showOutput() is cached, and returned by this method.""" |
2800 | + return self.__output |
2801 | + |
2802 | + def has_failed_for_landscape(self): |
2803 | + """Return true if any error() messages were logged.""" |
2804 | + return self.__failed |
2805 | + |
2806 | + def error(self, msg): |
2807 | + self.__failed = True |
2808 | + # Calling these logging.* functions here instead of message() |
2809 | + # below will output the message or not depending on the debug |
2810 | + # level set in landscape-client, rather than the one set in |
2811 | + # Smart's configuration. |
2812 | + logging.error("[Smart] %s", msg) |
2813 | + super(LandscapeInterface, self).error(msg) |
2814 | + |
2815 | + def warning(self, msg): |
2816 | + logging.warning("[Smart] %s", msg) |
2817 | + super(LandscapeInterface, self).warning(msg) |
2818 | + |
2819 | + def info(self, msg): |
2820 | + logging.info("[Smart] %s", msg) |
2821 | + super(LandscapeInterface, self).info(msg) |
2822 | + |
2823 | + def debug(self, msg): |
2824 | + logging.debug("[Smart] %s", msg) |
2825 | + super(LandscapeInterface, self).debug(msg) |
2826 | + |
2827 | + def message(self, level, msg): |
2828 | + prefix = {ERROR: "ERROR", WARNING: "WARNING", |
2829 | + INFO: "INFO", DEBUG: "DEBUG"}.get(level) |
2830 | + self.showOutput("%s: %s\n" % (prefix, msg)) |
2831 | + |
2832 | + def showOutput(self, output): |
2833 | + if not isinstance(output, unicode): |
2834 | + try: |
2835 | + output = output.decode("utf-8") |
2836 | + except UnicodeDecodeError: |
2837 | + output = output.decode("ascii", "replace") |
2838 | + self.__output += output |
2839 | + |
2840 | + |
2841 | +class LandscapeInterfaceModule(types.ModuleType): |
2842 | + |
2843 | + def __init__(self): |
2844 | + super(LandscapeInterfaceModule, self).__init__("landscape") |
2845 | + |
2846 | + def create(self, ctrl, command=None, argv=None): |
2847 | + return LandscapeInterface(ctrl) |
2848 | + |
2849 | + |
2850 | +def install_landscape_interface(): |
2851 | + if "smart.interfaces.landscape" not in sys.modules: |
2852 | + # Plug the interface in a place Smart will recognize. |
2853 | + smart.interfaces.landscape = LandscapeInterfaceModule() |
2854 | + sys.modules["smart.interfaces.landscape"] = smart.interfaces.landscape |
2855 | + |
2856 | + |
2857 | +def uninstall_landscape_interface(): |
2858 | + sys.modules.pop("smart.interfaces.landscape", None) |
2859 | |
2860 | === modified file 'landscape/package/reporter.py' |
2861 | --- landscape/package/reporter.py 2012-06-19 15:12:07 +0000 |
2862 | +++ landscape/package/reporter.py 2014-11-19 18:22:39 +0000 |
2863 | @@ -12,6 +12,7 @@ |
2864 | from landscape.lib.fs import touch_file |
2865 | from landscape.lib import bpickle |
2866 | |
2867 | +from landscape.package.facade import AptFacade |
2868 | from landscape.package.taskhandler import ( |
2869 | PackageTaskHandlerConfiguration, PackageTaskHandler, run_task_handler) |
2870 | from landscape.package.store import UnknownHashIDRequest, FakePackageStore |
2871 | @@ -47,15 +48,31 @@ |
2872 | |
2873 | queue_name = "reporter" |
2874 | |
2875 | +<<<<<<< TREE |
2876 | apt_update_interval = 60 |
2877 | apt_update_filename = "/usr/lib/landscape/apt-update" |
2878 | +======= |
2879 | + smart_update_interval = 60 |
2880 | + smart_update_filename = "/usr/lib/landscape/smart-update" |
2881 | + apt_update_filename = "/usr/lib/landscape/apt-update" |
2882 | +>>>>>>> MERGE-SOURCE |
2883 | sources_list_filename = "/etc/apt/sources.list" |
2884 | sources_list_directory = "/etc/apt/sources.list.d" |
2885 | |
2886 | def run(self): |
2887 | result = Deferred() |
2888 | |
2889 | +<<<<<<< TREE |
2890 | result.addCallback(lambda x: self.run_apt_update()) |
2891 | +======= |
2892 | + if isinstance(self._facade, AptFacade): |
2893 | + # Update APT cache if APT facade is enabled. |
2894 | + result.addCallback(lambda x: self.run_apt_update()) |
2895 | + else: |
2896 | + # Run smart-update before anything else, to make sure that |
2897 | + # the SmartFacade will load freshly updated channels |
2898 | + result.addCallback(lambda x: self.run_smart_update()) |
2899 | +>>>>>>> MERGE-SOURCE |
2900 | |
2901 | # If the appropriate hash=>id db is not there, fetch it |
2902 | result.addCallback(lambda x: self.fetch_hash_id_db()) |
2903 | @@ -219,6 +236,86 @@ |
2904 | return result.addCallback(callback) |
2905 | |
2906 | else: |
2907 | +<<<<<<< TREE |
2908 | + logging.debug("'%s' didn't run, update interval has not passed" % |
2909 | + self.apt_update_filename) |
2910 | + return succeed(("", "", 0)) |
2911 | +======= |
2912 | + args = ("--after", str(self.smart_update_interval)) |
2913 | + result = spawn_process(self.smart_update_filename, args=args) |
2914 | + |
2915 | + def callback((out, err, code)): |
2916 | + # smart-update --after N will exit with error code 1 when it |
2917 | + # doesn't actually run the update code because to enough time |
2918 | + # has passed yet, but we don't actually consider it a failure. |
2919 | + smart_failed = False |
2920 | + if code != 0 and code != 1: |
2921 | + smart_failed = True |
2922 | + if code == 1 and err.strip() != "": |
2923 | + smart_failed = True |
2924 | + if smart_failed: |
2925 | + logging.warning("'%s' exited with status %d (%s)" % ( |
2926 | + self.smart_update_filename, code, err)) |
2927 | + logging.debug("'%s' exited with status %d (out='%s', err='%s'" % ( |
2928 | + self.smart_update_filename, code, out, err)) |
2929 | + touch_file(self._config.update_stamp_filename) |
2930 | + if not smart_failed and not self._facade.get_channels(): |
2931 | + code = 1 |
2932 | + err = "There are no APT sources configured in %s or %s." % ( |
2933 | + self.sources_list_filename, self.sources_list_directory) |
2934 | + deferred = self._broker.call_if_accepted( |
2935 | + "package-reporter-result", self.send_result, code, err) |
2936 | + deferred.addCallback(lambda ignore: (out, err, code)) |
2937 | + return deferred |
2938 | + |
2939 | + result.addCallback(callback) |
2940 | + return result |
2941 | +>>>>>>> MERGE-SOURCE |
2942 | + |
2943 | + def _apt_update_timeout_expired(self, interval): |
2944 | + """Check if the apt-update timeout has passed.""" |
2945 | + stamp = self._config.update_stamp_filename |
2946 | + |
2947 | + if not os.path.exists(stamp): |
2948 | + return True |
2949 | + # check stamp file mtime |
2950 | + last_update = os.stat(stamp)[8] |
2951 | + now = int(time.time()) |
2952 | + return (last_update + interval * 60) < now |
2953 | + |
2954 | + def run_apt_update(self): |
2955 | + """Run apt-update and log a warning in case of non-zero exit code. |
2956 | + |
2957 | + @return: a deferred returning (out, err, code) |
2958 | + """ |
2959 | + if (self._config.force_smart_update or self._apt_sources_have_changed() |
2960 | + or self._apt_update_timeout_expired(self.smart_update_interval)): |
2961 | + |
2962 | + result = spawn_process(self.apt_update_filename) |
2963 | + |
2964 | + def callback((out, err, code)): |
2965 | + touch_file(self._config.update_stamp_filename) |
2966 | + logging.debug( |
2967 | + "'%s' exited with status %d (out='%s', err='%s')" % ( |
2968 | + self.apt_update_filename, code, out, err)) |
2969 | + |
2970 | + if code != 0: |
2971 | + logging.warning("'%s' exited with status %d (%s)" % ( |
2972 | + self.apt_update_filename, code, err)) |
2973 | + elif not self._facade.get_channels(): |
2974 | + code = 1 |
2975 | + err = ("There are no APT sources configured in %s or %s." % |
2976 | + (self.sources_list_filename, |
2977 | + self.sources_list_directory)) |
2978 | + |
2979 | + deferred = self._broker.call_if_accepted( |
2980 | + "package-reporter-result", self.send_result, code, err) |
2981 | + deferred.addCallback(lambda ignore: (out, err, code)) |
2982 | + return deferred |
2983 | + |
2984 | + return result.addCallback(callback) |
2985 | + |
2986 | + else: |
2987 | logging.debug("'%s' didn't run, update interval has not passed" % |
2988 | self.apt_update_filename) |
2989 | return succeed(("", "", 0)) |
2990 | @@ -559,6 +656,116 @@ |
2991 | |
2992 | return result |
2993 | |
2994 | +<<<<<<< TREE |
2995 | + |
2996 | +class FakeGlobalReporter(PackageReporter): |
2997 | + """ |
2998 | + A standard reporter, which additionally stores messages sent into its |
2999 | + package store. |
3000 | + """ |
3001 | + |
3002 | + package_store_class = FakePackageStore |
3003 | + |
3004 | + def send_message(self, message): |
3005 | + self._store.save_message(message) |
3006 | + return super(FakeGlobalReporter, self).send_message(message) |
3007 | + |
3008 | + |
3009 | +class FakeReporter(PackageReporter): |
3010 | + """ |
3011 | + A fake reporter which only sends messages previously stored by a |
3012 | + L{FakeGlobalReporter}. |
3013 | + """ |
3014 | + |
3015 | + package_store_class = FakePackageStore |
3016 | + global_store_filename = None |
3017 | + |
3018 | + def run(self): |
3019 | + result = succeed(None) |
3020 | + |
3021 | + # If the appropriate hash=>id db is not there, fetch it |
3022 | + result.addCallback(lambda x: self.fetch_hash_id_db()) |
3023 | + |
3024 | + result.addCallback(lambda x: self._store.clear_tasks()) |
3025 | + |
3026 | + # Finally, verify if we have anything new to send to the server. |
3027 | + result.addCallback(lambda x: self.send_pending_messages()) |
3028 | +======= |
3029 | + def detect_package_locks_changes(self): |
3030 | + """Detect changes in known package locks. |
3031 | + |
3032 | + This method will verify if there are package locks that: |
3033 | + |
3034 | + - are now set, and were not; |
3035 | + - were previously set but are not anymore; |
3036 | + |
3037 | + In all cases, the server is notified of the new situation |
3038 | + with a "packages" message. |
3039 | + |
3040 | + @return: A deferred resulting in C{True} if package lock changes were |
3041 | + detected with respect to the previous run, or C{False} otherwise. |
3042 | + """ |
3043 | + old_package_locks = set(self._store.get_package_locks()) |
3044 | + current_package_locks = set(self._facade.get_package_locks()) |
3045 | + |
3046 | + set_package_locks = current_package_locks - old_package_locks |
3047 | + unset_package_locks = old_package_locks - current_package_locks |
3048 | + |
3049 | + message = {} |
3050 | + if set_package_locks: |
3051 | + message["created"] = sorted(set_package_locks) |
3052 | + if unset_package_locks: |
3053 | + message["deleted"] = sorted(unset_package_locks) |
3054 | + |
3055 | + if not message: |
3056 | + return succeed(False) |
3057 | + |
3058 | + message["type"] = "package-locks" |
3059 | + result = self.send_message(message) |
3060 | + |
3061 | + logging.info("Queuing message with changes in known package locks:" |
3062 | + " %d created, %d deleted." % |
3063 | + (len(set_package_locks), len(unset_package_locks))) |
3064 | + |
3065 | + def update_currently_known(result): |
3066 | + if set_package_locks: |
3067 | + self._store.add_package_locks(set_package_locks) |
3068 | + if unset_package_locks: |
3069 | + self._store.remove_package_locks(unset_package_locks) |
3070 | + return True |
3071 | + |
3072 | + result.addCallback(update_currently_known) |
3073 | +>>>>>>> MERGE-SOURCE |
3074 | + |
3075 | + return result |
3076 | + |
3077 | + def send_pending_messages(self): |
3078 | + """ |
3079 | + As the last callback of L{PackageReporter}, sends messages stored. |
3080 | + """ |
3081 | + if self.global_store_filename is None: |
3082 | + self.global_store_filename = os.environ["FAKE_PACKAGE_STORE"] |
3083 | + if not os.path.exists(self.global_store_filename): |
3084 | + return succeed(None) |
3085 | + message_sent = set(self._store.get_message_ids()) |
3086 | + global_store = FakePackageStore(self.global_store_filename) |
3087 | + all_message_ids = set(global_store.get_message_ids()) |
3088 | + not_sent = all_message_ids - message_sent |
3089 | + deferred = succeed(None) |
3090 | + got_type = set() |
3091 | + if not_sent: |
3092 | + messages = global_store.get_messages_by_ids(not_sent) |
3093 | + sent = [] |
3094 | + for message_id, message in messages: |
3095 | + message = bpickle.loads(str(message)) |
3096 | + if message["type"] not in got_type: |
3097 | + got_type.add(message["type"]) |
3098 | + sent.append(message_id) |
3099 | + deferred.addCallback( |
3100 | + lambda x, message=message: self.send_message(message)) |
3101 | + self._store.save_message_ids(sent) |
3102 | + return deferred |
3103 | + |
3104 | |
3105 | class FakeGlobalReporter(PackageReporter): |
3106 | """ |
3107 | |
3108 | === modified file 'landscape/package/skeleton.py' |
3109 | --- landscape/package/skeleton.py 2012-06-19 15:12:07 +0000 |
3110 | +++ landscape/package/skeleton.py 2014-11-19 18:22:39 +0000 |
3111 | @@ -131,3 +131,94 @@ |
3112 | skeleton.summary = skeleton.summary.decode("utf-8") |
3113 | skeleton.description = skeleton.description.decode("utf-8") |
3114 | return skeleton |
3115 | +<<<<<<< TREE |
3116 | +======= |
3117 | + |
3118 | +build_skeleton.inited = False |
3119 | + |
3120 | + |
3121 | +def relation_to_string(relation_tuple): |
3122 | + """Convert an apt relation to a string representation. |
3123 | + |
3124 | + @param relation_tuple: A tuple, (name, version, relation). version |
3125 | + and relation can be the empty string, if the relation is on a |
3126 | + name only. |
3127 | + |
3128 | + Returns something like "name > 1.0" |
3129 | + """ |
3130 | + name, version, relation_type = relation_tuple |
3131 | + relation_string = name |
3132 | + if relation_type: |
3133 | + relation_string += " %s %s" % (relation_type, version) |
3134 | + return relation_string |
3135 | + |
3136 | + |
3137 | +def parse_record_field(record, record_field, relation_type, |
3138 | + or_relation_type=None): |
3139 | + """Parse an apt C{Record} field and return skeleton relations |
3140 | + |
3141 | + @param record: An C{apt.package.Record} instance with package information. |
3142 | + @param record_field: The name of the record field to parse. |
3143 | + @param relation_type: The deb relation that can be passed to |
3144 | + C{skeleton.add_relation()} |
3145 | + @param or_relation_type: The deb relation that should be used if |
3146 | + there is more than one value in a relation. |
3147 | + """ |
3148 | + relations = set() |
3149 | + values = apt_pkg.parse_depends(record.get(record_field, "")) |
3150 | + for value in values: |
3151 | + value_strings = [relation_to_string(relation) for relation in value] |
3152 | + value_relation_type = relation_type |
3153 | + if len(value_strings) > 1: |
3154 | + value_relation_type = or_relation_type |
3155 | + relation_string = " | ".join(value_strings) |
3156 | + relations.add((value_relation_type, relation_string)) |
3157 | + return relations |
3158 | + |
3159 | + |
3160 | +def build_skeleton_apt(version, with_info=False, with_unicode=False): |
3161 | + """Build a package skeleton from an apt package. |
3162 | + |
3163 | + @param version: An instance of C{apt.package.Version} |
3164 | + @param with_info: Whether to extract extra information about the |
3165 | + package, like description, summary, size. |
3166 | + @param with_unicode: Whether the C{name} and C{version} of the |
3167 | + skeleton should be unicode strings. |
3168 | + """ |
3169 | + name, version_string = version.package.name, version.version |
3170 | + if with_unicode: |
3171 | + name, version_string = unicode(name), unicode(version_string) |
3172 | + skeleton = PackageSkeleton(DEB_PACKAGE, name, version_string) |
3173 | + relations = set() |
3174 | + relations.update(parse_record_field( |
3175 | + version.record, "Provides", DEB_PROVIDES)) |
3176 | + relations.add(( |
3177 | + DEB_NAME_PROVIDES, |
3178 | + "%s = %s" % (version.package.name, version.version))) |
3179 | + relations.update(parse_record_field( |
3180 | + version.record, "Pre-Depends", DEB_REQUIRES, DEB_OR_REQUIRES)) |
3181 | + relations.update(parse_record_field( |
3182 | + version.record, "Depends", DEB_REQUIRES, DEB_OR_REQUIRES)) |
3183 | + |
3184 | + relations.add(( |
3185 | + DEB_UPGRADES, "%s < %s" % (version.package.name, version.version))) |
3186 | + |
3187 | + relations.update(parse_record_field( |
3188 | + version.record, "Conflicts", DEB_CONFLICTS)) |
3189 | + relations.update(parse_record_field( |
3190 | + version.record, "Breaks", DEB_CONFLICTS)) |
3191 | + skeleton.relations = sorted(relations) |
3192 | + |
3193 | + if with_info: |
3194 | + skeleton.section = version.section |
3195 | + skeleton.summary = version.summary |
3196 | + skeleton.description = version.description |
3197 | + skeleton.size = version.size |
3198 | + if version.installed_size > 0: |
3199 | + skeleton.installed_size = version.installed_size |
3200 | + if with_unicode: |
3201 | + skeleton.section = skeleton.section.decode("utf-8") |
3202 | + skeleton.summary = skeleton.summary.decode("utf-8") |
3203 | + skeleton.description = skeleton.description.decode("utf-8") |
3204 | + return skeleton |
3205 | +>>>>>>> MERGE-SOURCE |
3206 | |
3207 | === modified file 'landscape/package/store.py' |
3208 | === modified file 'landscape/package/taskhandler.py' |
3209 | --- landscape/package/taskhandler.py 2012-06-19 15:12:07 +0000 |
3210 | +++ landscape/package/taskhandler.py 2014-11-19 18:22:39 +0000 |
3211 | @@ -258,11 +258,23 @@ |
3212 | # 0644 so... |
3213 | os.umask(022) |
3214 | |
3215 | +<<<<<<< TREE |
3216 | package_store = cls.package_store_class(config.store_filename) |
3217 | # Delay importing of the facades so that we don't |
3218 | # import Apt unless we need to. |
3219 | from landscape.package.facade import AptFacade |
3220 | package_facade = AptFacade() |
3221 | +======= |
3222 | + package_store = cls.package_store_class(config.store_filename) |
3223 | + # Delay importing of the facades so that we don't |
3224 | + # import Smart unless we need to. |
3225 | + from landscape.package.facade import ( |
3226 | + AptFacade, SmartFacade, has_new_enough_apt) |
3227 | + if has_new_enough_apt: |
3228 | + package_facade = AptFacade() |
3229 | + else: |
3230 | + package_facade = SmartFacade() |
3231 | +>>>>>>> MERGE-SOURCE |
3232 | |
3233 | def finish(): |
3234 | connector.disconnect() |
3235 | |
3236 | === modified file 'landscape/package/tests/helpers.py' |
3237 | --- landscape/package/tests/helpers.py 2012-06-19 15:12:07 +0000 |
3238 | +++ landscape/package/tests/helpers.py 2014-11-19 18:22:39 +0000 |
3239 | @@ -1,121 +1,256 @@ |
3240 | import base64 |
3241 | import os |
3242 | -import textwrap |
3243 | -import time |
3244 | - |
3245 | -import apt_inst |
3246 | -import apt_pkg |
3247 | - |
3248 | -from landscape.lib.fs import append_file, create_file |
3249 | -from landscape.package.facade import AptFacade |
3250 | - |
3251 | - |
3252 | -class AptFacadeHelper(object): |
3253 | - """Helper that sets up an AptFacade with a tempdir as its root.""" |
3254 | - |
3255 | - def set_up(self, test_case): |
3256 | - test_case.apt_root = test_case.makeDir() |
3257 | - self.dpkg_status = os.path.join( |
3258 | - test_case.apt_root, "var", "lib", "dpkg", "status") |
3259 | - test_case.Facade = AptFacade |
3260 | - test_case.facade = AptFacade(root=test_case.apt_root) |
3261 | - test_case.facade.refetch_package_index = True |
3262 | - test_case._add_system_package = self._add_system_package |
3263 | - test_case._install_deb_file = self._install_deb_file |
3264 | - test_case._add_package_to_deb_dir = self._add_package_to_deb_dir |
3265 | - test_case._touch_packages_file = self._touch_packages_file |
3266 | - test_case._hash_packages_by_name = self._hash_packages_by_name |
3267 | - |
3268 | - def _add_package(self, packages_file, name, architecture="all", |
3269 | - version="1.0", control_fields=None): |
3270 | - if control_fields is None: |
3271 | - control_fields = {} |
3272 | - package_stanza = textwrap.dedent(""" |
3273 | - Package: %(name)s |
3274 | - Priority: optional |
3275 | - Section: misc |
3276 | - Installed-Size: 1234 |
3277 | - Maintainer: Someone |
3278 | - Architecture: %(architecture)s |
3279 | - Source: source |
3280 | - Version: %(version)s |
3281 | - Description: description |
3282 | - """ % {"name": name, "version": version, |
3283 | - "architecture": architecture}) |
3284 | - package_stanza = apt_pkg.rewrite_section( |
3285 | - apt_pkg.TagSection(package_stanza), apt_pkg.REWRITE_PACKAGE_ORDER, |
3286 | - control_fields.items()) |
3287 | - append_file(packages_file, "\n" + package_stanza + "\n") |
3288 | - |
3289 | - def _add_system_package(self, name, architecture="all", version="1.0", |
3290 | - control_fields=None): |
3291 | - """Add a package to the dpkg status file.""" |
3292 | - system_control_fields = {"Status": "install ok installed"} |
3293 | - if control_fields is not None: |
3294 | - system_control_fields.update(control_fields) |
3295 | - self._add_package( |
3296 | - self.dpkg_status, name, architecture=architecture, version=version, |
3297 | - control_fields=system_control_fields) |
3298 | - |
3299 | - def _install_deb_file(self, path): |
3300 | - """Fake the the given deb file is installed in the system.""" |
3301 | - deb_file = open(path) |
3302 | - deb = apt_inst.DebFile(deb_file) |
3303 | - control = deb.control.extractdata("control") |
3304 | - deb_file.close() |
3305 | - lines = control.splitlines() |
3306 | - lines.insert(1, "Status: install ok installed") |
3307 | - status = "\n".join(lines) |
3308 | - append_file(self.dpkg_status, status + "\n\n") |
3309 | - |
3310 | - def _add_package_to_deb_dir(self, path, name, architecture="all", |
3311 | - version="1.0", control_fields=None): |
3312 | - """Add fake package information to a directory. |
3313 | - |
3314 | - There will only be basic information about the package |
3315 | - available, so that get_packages() have something to return. |
3316 | - There won't be an actual package in the dir. |
3317 | - """ |
3318 | - if control_fields is None: |
3319 | - control_fields = {} |
3320 | - self._add_package( |
3321 | - os.path.join(path, "Packages"), name, architecture=architecture, |
3322 | - version=version, control_fields=control_fields) |
3323 | - |
3324 | - def _touch_packages_file(self, deb_dir): |
3325 | - """Make sure the Packages file get a newer mtime value. |
3326 | - |
3327 | - If we rely on simply writing to the file to update the mtime, we |
3328 | - might end up with the same as before, since the resolution is |
3329 | - seconds, which causes apt to not reload the file. |
3330 | - """ |
3331 | - packages_path = os.path.join(deb_dir, "Packages") |
3332 | - mtime = int(time.time() + 1) |
3333 | - os.utime(packages_path, (mtime, mtime)) |
3334 | - |
3335 | - def _hash_packages_by_name(self, facade, store, package_name): |
3336 | - """ |
3337 | - Ensure the named L{Package} is correctly recorded in the store so that |
3338 | - we can really test the functions of the facade that depend on it. |
3339 | - """ |
3340 | - hash_ids = {} |
3341 | - for version in facade.get_packages_by_name(package_name): |
3342 | - skeleton = facade.get_package_skeleton( |
3343 | - version, with_info=False) |
3344 | - hash = skeleton.get_hash() |
3345 | - facade._pkg2hash[(version.package, version)] = hash |
3346 | - hash_ids[hash] = version.package.id |
3347 | - store.set_hash_ids(hash_ids) |
3348 | - |
3349 | - |
3350 | -class SimpleRepositoryHelper(object): |
3351 | - """Helper for adding a simple repository to the facade. |
3352 | - |
3353 | - This helper requires that C{test_case.facade} has already been set |
3354 | - up. |
3355 | - """ |
3356 | - |
3357 | - def set_up(self, test_case): |
3358 | +<<<<<<< TREE |
3359 | +import textwrap |
3360 | +import time |
3361 | + |
3362 | +import apt_inst |
3363 | +import apt_pkg |
3364 | + |
3365 | +from landscape.lib.fs import append_file, create_file |
3366 | +from landscape.package.facade import AptFacade |
3367 | + |
3368 | + |
3369 | +class AptFacadeHelper(object): |
3370 | + """Helper that sets up an AptFacade with a tempdir as its root.""" |
3371 | + |
3372 | + def set_up(self, test_case): |
3373 | + test_case.apt_root = test_case.makeDir() |
3374 | + self.dpkg_status = os.path.join( |
3375 | + test_case.apt_root, "var", "lib", "dpkg", "status") |
3376 | + test_case.Facade = AptFacade |
3377 | + test_case.facade = AptFacade(root=test_case.apt_root) |
3378 | + test_case.facade.refetch_package_index = True |
3379 | + test_case._add_system_package = self._add_system_package |
3380 | + test_case._install_deb_file = self._install_deb_file |
3381 | + test_case._add_package_to_deb_dir = self._add_package_to_deb_dir |
3382 | + test_case._touch_packages_file = self._touch_packages_file |
3383 | + test_case._hash_packages_by_name = self._hash_packages_by_name |
3384 | + |
3385 | + def _add_package(self, packages_file, name, architecture="all", |
3386 | + version="1.0", control_fields=None): |
3387 | + if control_fields is None: |
3388 | + control_fields = {} |
3389 | + package_stanza = textwrap.dedent(""" |
3390 | + Package: %(name)s |
3391 | + Priority: optional |
3392 | + Section: misc |
3393 | + Installed-Size: 1234 |
3394 | + Maintainer: Someone |
3395 | + Architecture: %(architecture)s |
3396 | + Source: source |
3397 | + Version: %(version)s |
3398 | + Description: description |
3399 | + """ % {"name": name, "version": version, |
3400 | + "architecture": architecture}) |
3401 | + package_stanza = apt_pkg.rewrite_section( |
3402 | + apt_pkg.TagSection(package_stanza), apt_pkg.REWRITE_PACKAGE_ORDER, |
3403 | + control_fields.items()) |
3404 | + append_file(packages_file, "\n" + package_stanza + "\n") |
3405 | + |
3406 | + def _add_system_package(self, name, architecture="all", version="1.0", |
3407 | + control_fields=None): |
3408 | + """Add a package to the dpkg status file.""" |
3409 | + system_control_fields = {"Status": "install ok installed"} |
3410 | + if control_fields is not None: |
3411 | + system_control_fields.update(control_fields) |
3412 | + self._add_package( |
3413 | + self.dpkg_status, name, architecture=architecture, version=version, |
3414 | + control_fields=system_control_fields) |
3415 | + |
3416 | + def _install_deb_file(self, path): |
3417 | + """Fake the the given deb file is installed in the system.""" |
3418 | + deb_file = open(path) |
3419 | + deb = apt_inst.DebFile(deb_file) |
3420 | + control = deb.control.extractdata("control") |
3421 | + deb_file.close() |
3422 | + lines = control.splitlines() |
3423 | + lines.insert(1, "Status: install ok installed") |
3424 | + status = "\n".join(lines) |
3425 | + append_file(self.dpkg_status, status + "\n\n") |
3426 | + |
3427 | + def _add_package_to_deb_dir(self, path, name, architecture="all", |
3428 | + version="1.0", control_fields=None): |
3429 | + """Add fake package information to a directory. |
3430 | + |
3431 | + There will only be basic information about the package |
3432 | + available, so that get_packages() have something to return. |
3433 | + There won't be an actual package in the dir. |
3434 | + """ |
3435 | + if control_fields is None: |
3436 | + control_fields = {} |
3437 | + self._add_package( |
3438 | + os.path.join(path, "Packages"), name, architecture=architecture, |
3439 | + version=version, control_fields=control_fields) |
3440 | + |
3441 | + def _touch_packages_file(self, deb_dir): |
3442 | + """Make sure the Packages file get a newer mtime value. |
3443 | + |
3444 | + If we rely on simply writing to the file to update the mtime, we |
3445 | + might end up with the same as before, since the resolution is |
3446 | + seconds, which causes apt to not reload the file. |
3447 | + """ |
3448 | + packages_path = os.path.join(deb_dir, "Packages") |
3449 | + mtime = int(time.time() + 1) |
3450 | + os.utime(packages_path, (mtime, mtime)) |
3451 | + |
3452 | + def _hash_packages_by_name(self, facade, store, package_name): |
3453 | + """ |
3454 | + Ensure the named L{Package} is correctly recorded in the store so that |
3455 | + we can really test the functions of the facade that depend on it. |
3456 | + """ |
3457 | + hash_ids = {} |
3458 | + for version in facade.get_packages_by_name(package_name): |
3459 | + skeleton = facade.get_package_skeleton( |
3460 | + version, with_info=False) |
3461 | + hash = skeleton.get_hash() |
3462 | + facade._pkg2hash[(version.package, version)] = hash |
3463 | + hash_ids[hash] = version.package.id |
3464 | + store.set_hash_ids(hash_ids) |
3465 | + |
3466 | + |
3467 | +class SimpleRepositoryHelper(object): |
3468 | + """Helper for adding a simple repository to the facade. |
3469 | + |
3470 | + This helper requires that C{test_case.facade} has already been set |
3471 | + up. |
3472 | + """ |
3473 | + |
3474 | + def set_up(self, test_case): |
3475 | +======= |
3476 | +import textwrap |
3477 | +import time |
3478 | + |
3479 | +try: |
3480 | + import smart |
3481 | +except ImportError: |
3482 | + # Smart is optional if AptFacade is being used. |
3483 | + pass |
3484 | + |
3485 | +import apt_inst |
3486 | +import apt_pkg |
3487 | + |
3488 | +from landscape.lib.fs import append_file, create_file |
3489 | +from landscape.package.facade import AptFacade |
3490 | + |
3491 | + |
3492 | +class AptFacadeHelper(object): |
3493 | + """Helper that sets up an AptFacade with a tempdir as its root.""" |
3494 | + |
3495 | + def set_up(self, test_case): |
3496 | + test_case.apt_root = test_case.makeDir() |
3497 | + self.dpkg_status = os.path.join( |
3498 | + test_case.apt_root, "var", "lib", "dpkg", "status") |
3499 | + test_case.Facade = AptFacade |
3500 | + test_case.facade = AptFacade(root=test_case.apt_root) |
3501 | + test_case.facade.refetch_package_index = True |
3502 | + test_case._add_system_package = self._add_system_package |
3503 | + test_case._install_deb_file = self._install_deb_file |
3504 | + test_case._add_package_to_deb_dir = self._add_package_to_deb_dir |
3505 | + test_case._touch_packages_file = self._touch_packages_file |
3506 | + test_case._hash_packages_by_name = self._hash_packages_by_name |
3507 | + |
3508 | + def _add_package(self, packages_file, name, architecture="all", |
3509 | + version="1.0", control_fields=None): |
3510 | + if control_fields is None: |
3511 | + control_fields = {} |
3512 | + package_stanza = textwrap.dedent(""" |
3513 | + Package: %(name)s |
3514 | + Priority: optional |
3515 | + Section: misc |
3516 | + Installed-Size: 1234 |
3517 | + Maintainer: Someone |
3518 | + Architecture: %(architecture)s |
3519 | + Source: source |
3520 | + Version: %(version)s |
3521 | + Description: description |
3522 | + """ % {"name": name, "version": version, |
3523 | + "architecture": architecture}) |
3524 | + package_stanza = apt_pkg.rewrite_section( |
3525 | + apt_pkg.TagSection(package_stanza), apt_pkg.REWRITE_PACKAGE_ORDER, |
3526 | + control_fields.items()) |
3527 | + append_file(packages_file, "\n" + package_stanza + "\n") |
3528 | + |
3529 | + def _add_system_package(self, name, architecture="all", version="1.0", |
3530 | + control_fields=None): |
3531 | + """Add a package to the dpkg status file.""" |
3532 | + system_control_fields = {"Status": "install ok installed"} |
3533 | + if control_fields is not None: |
3534 | + system_control_fields.update(control_fields) |
3535 | + self._add_package( |
3536 | + self.dpkg_status, name, architecture=architecture, version=version, |
3537 | + control_fields=system_control_fields) |
3538 | + |
3539 | + def _install_deb_file(self, path): |
3540 | + """Fake the the given deb file is installed in the system.""" |
3541 | + deb_file = open(path) |
3542 | + deb = apt_inst.DebFile(deb_file) |
3543 | + control = deb.control.extractdata("control") |
3544 | + deb_file.close() |
3545 | + lines = control.splitlines() |
3546 | + lines.insert(1, "Status: install ok installed") |
3547 | + status = "\n".join(lines) |
3548 | + append_file(self.dpkg_status, status + "\n\n") |
3549 | + |
3550 | + def _add_package_to_deb_dir(self, path, name, architecture="all", |
3551 | + version="1.0", control_fields=None): |
3552 | + """Add fake package information to a directory. |
3553 | + |
3554 | + There will only be basic information about the package |
3555 | + available, so that get_packages() have something to return. |
3556 | + There won't be an actual package in the dir. |
3557 | + """ |
3558 | + if control_fields is None: |
3559 | + control_fields = {} |
3560 | + self._add_package( |
3561 | + os.path.join(path, "Packages"), name, architecture=architecture, |
3562 | + version=version, control_fields=control_fields) |
3563 | + |
3564 | + def _touch_packages_file(self, deb_dir): |
3565 | + """Make sure the Packages file get a newer mtime value. |
3566 | + |
3567 | + If we rely on simply writing to the file to update the mtime, we |
3568 | + might end up with the same as before, since the resolution is |
3569 | + seconds, which causes apt to not reload the file. |
3570 | + """ |
3571 | + packages_path = os.path.join(deb_dir, "Packages") |
3572 | + mtime = int(time.time() + 1) |
3573 | + os.utime(packages_path, (mtime, mtime)) |
3574 | + |
3575 | + def _hash_packages_by_name(self, facade, store, package_name): |
3576 | + """ |
3577 | + Ensure the named L{Package} is correctly recorded in the store so that |
3578 | + we can really test the functions of the facade that depend on it. |
3579 | + """ |
3580 | + hash_ids = {} |
3581 | + for version in facade.get_packages_by_name(package_name): |
3582 | + skeleton = facade.get_package_skeleton( |
3583 | + version, with_info=False) |
3584 | + hash = skeleton.get_hash() |
3585 | + facade._pkg2hash[(version.package, version)] = hash |
3586 | + hash_ids[hash] = version.package.id |
3587 | + store.set_hash_ids(hash_ids) |
3588 | + |
3589 | + |
3590 | +class SimpleRepositoryHelper(object): |
3591 | + """Helper for adding a simple repository to the facade. |
3592 | + |
3593 | + This helper requires that C{test_case.facade} has already been set |
3594 | + up. |
3595 | + """ |
3596 | + |
3597 | + def set_up(self, test_case): |
3598 | + test_case.repository_dir = test_case.makeDir() |
3599 | + create_simple_repository(test_case.repository_dir) |
3600 | + test_case.facade.add_channel_deb_dir(test_case.repository_dir) |
3601 | + |
3602 | + |
3603 | +class SmartHelper(object): |
3604 | + |
3605 | + def set_up(self, test_case): |
3606 | + test_case.smart_dir = test_case.makeDir() |
3607 | + test_case.smart_config = test_case.makeFile("") |
3608 | +>>>>>>> MERGE-SOURCE |
3609 | test_case.repository_dir = test_case.makeDir() |
3610 | create_simple_repository(test_case.repository_dir) |
3611 | test_case.facade.add_channel_deb_dir(test_case.repository_dir) |
3612 | |
3613 | === modified file 'landscape/package/tests/test_changer.py' |
3614 | --- landscape/package/tests/test_changer.py 2012-06-19 15:12:07 +0000 |
3615 | +++ landscape/package/tests/test_changer.py 2014-11-19 18:22:39 +0000 |
3616 | @@ -6,20 +6,35 @@ |
3617 | |
3618 | from twisted.internet.defer import Deferred |
3619 | |
3620 | -from landscape.lib.fs import create_file, read_file, touch_file |
3621 | +<<<<<<< TREE |
3622 | +from landscape.lib.fs import create_file, read_file, touch_file |
3623 | +======= |
3624 | +try: |
3625 | + from smart.cache import Provides |
3626 | +except ImportError: |
3627 | + # Smart is optional if AptFacade is being used. |
3628 | + pass |
3629 | + |
3630 | +from landscape.lib.fs import create_file, read_file, touch_file |
3631 | +>>>>>>> MERGE-SOURCE |
3632 | from landscape.package.changer import ( |
3633 | PackageChanger, main, find_changer_command, UNKNOWN_PACKAGE_DATA_TIMEOUT, |
3634 | SUCCESS_RESULT, DEPENDENCY_ERROR_RESULT, POLICY_ALLOW_INSTALLS, |
3635 | POLICY_ALLOW_ALL_CHANGES, ERROR_RESULT) |
3636 | from landscape.package.store import PackageStore |
3637 | from landscape.package.facade import ( |
3638 | +<<<<<<< TREE |
3639 | DependencyError, TransactionError) |
3640 | +======= |
3641 | + DependencyError, TransactionError, SmartError, has_new_enough_apt) |
3642 | +>>>>>>> MERGE-SOURCE |
3643 | from landscape.package.changer import ( |
3644 | PackageChangerConfiguration, ChangePackagesResult) |
3645 | from landscape.tests.mocker import ANY |
3646 | from landscape.tests.helpers import ( |
3647 | LandscapeTest, BrokerServiceHelper) |
3648 | from landscape.package.tests.helpers import ( |
3649 | +<<<<<<< TREE |
3650 | HASH1, HASH2, HASH3, PKGDEB1, PKGDEB2, |
3651 | AptFacadeHelper, SimpleRepositoryHelper) |
3652 | from landscape.manager.manager import FAILED |
3653 | @@ -47,7 +62,25 @@ |
3654 | |
3655 | result = super(AptPackageChangerTest, self).setUp() |
3656 | return result.addCallback(set_up) |
3657 | - |
3658 | +======= |
3659 | + SmartFacadeHelper, HASH1, HASH2, HASH3, PKGDEB1, PKGDEB2, PKGNAME2, |
3660 | + AptFacadeHelper, SimpleRepositoryHelper) |
3661 | +from landscape.manager.manager import FAILED, SUCCEEDED |
3662 | + |
3663 | + |
3664 | +class PackageChangerTestMixin(object): |
3665 | + |
3666 | + def disable_clear_channels(self): |
3667 | + """Disable clear_channels(), so that it doesn't remove test setup. |
3668 | + |
3669 | + This is useful for change-packages tests, which will call |
3670 | + facade.clear_channels(). Normally that's safe, but since we used |
3671 | + the facade to set up channels, we don't want them to be removed. |
3672 | + """ |
3673 | + self.facade.clear_channels = lambda: None |
3674 | +>>>>>>> MERGE-SOURCE |
3675 | + |
3676 | +<<<<<<< TREE |
3677 | def set_pkg1_installed(self): |
3678 | """Return the hash of a package that is installed.""" |
3679 | self._add_system_package("foo") |
3680 | @@ -135,6 +168,19 @@ |
3681 | |
3682 | self.addCleanup(reset_perform_changes, self.Facade) |
3683 | self.Facade.perform_changes = func |
3684 | +======= |
3685 | + def get_pending_messages(self): |
3686 | + return self.broker_service.message_store.get_pending_messages() |
3687 | + |
3688 | + def replace_perform_changes(self, func): |
3689 | + old_perform_changes = self.Facade.perform_changes |
3690 | + |
3691 | + def reset_perform_changes(Facade): |
3692 | + Facade.perform_changes = old_perform_changes |
3693 | + |
3694 | + self.addCleanup(reset_perform_changes, self.Facade) |
3695 | + self.Facade.perform_changes = func |
3696 | +>>>>>>> MERGE-SOURCE |
3697 | |
3698 | def test_unknown_package_id_for_dependency(self): |
3699 | hash1, hash2 = self.set_pkg1_and_pkg2_satisfied() |
3700 | @@ -511,6 +557,7 @@ |
3701 | {"type": "change-packages", "upgrade-all": True, |
3702 | "operation-id": 124}) |
3703 | |
3704 | +<<<<<<< TREE |
3705 | result = self.changer.handle_tasks() |
3706 | |
3707 | def got_result(result): |
3708 | @@ -554,6 +601,51 @@ |
3709 | self.assertEqual(124, message2["operation-id"]) |
3710 | self.assertEqual("change-packages-result", message2["type"]) |
3711 | self.assertEqual(ERROR_RESULT, message2["result-code"]) |
3712 | +======= |
3713 | + result = self.changer.handle_tasks() |
3714 | + |
3715 | + def got_result(result): |
3716 | + message = self.get_pending_messages()[1] |
3717 | + self.assertEqual(124, message["operation-id"]) |
3718 | + self.assertEqual("change-packages-result", message["type"]) |
3719 | + self.assertNotEqual(0, message["result-code"]) |
3720 | + |
3721 | + return result.addCallback(got_result) |
3722 | + |
3723 | + def test_tasks_are_isolated_cache(self): |
3724 | + """ |
3725 | + The package (apt/smart) cache should be reset between task runs. |
3726 | + In this test, we try to run two different operations, first |
3727 | + installing package 2, then removing package 1. Both tasks will |
3728 | + fail for lack of superuser privileges. If the package cache |
3729 | + isn't reset between tasks, the second operation will fail with a |
3730 | + dependency error, since it will be marked for installation, but |
3731 | + we haven't explicitly marked it so. |
3732 | + """ |
3733 | + self.log_helper.ignore_errors(".*dpkg") |
3734 | + |
3735 | + installable_hash = self.set_pkg2_satisfied() |
3736 | + installed_hash = self.set_pkg1_installed() |
3737 | + self.store.set_hash_ids({installed_hash: 1, installable_hash: 2}) |
3738 | + |
3739 | + self.store.add_task("changer", |
3740 | + {"type": "change-packages", "install": [2], |
3741 | + "operation-id": 123}) |
3742 | + self.store.add_task("changer", |
3743 | + {"type": "change-packages", "remove": [1], |
3744 | + "operation-id": 124}) |
3745 | + |
3746 | + result = self.changer.handle_tasks() |
3747 | + |
3748 | + def got_result(result): |
3749 | + message1, message2 = self.get_pending_messages() |
3750 | + self.assertEqual(123, message1["operation-id"]) |
3751 | + self.assertEqual("change-packages-result", message1["type"]) |
3752 | + self.assertEqual(ERROR_RESULT, message1["result-code"]) |
3753 | + self.assertEqual(124, message2["operation-id"]) |
3754 | + self.assertEqual("change-packages-result", message2["type"]) |
3755 | + self.assertEqual(ERROR_RESULT, message2["result-code"]) |
3756 | +>>>>>>> MERGE-SOURCE |
3757 | |
3758 | return result.addCallback(got_result) |
3759 | |
3760 | @@ -873,27 +965,72 @@ |
3761 | |
3762 | def raise_error(self): |
3763 | raise TransactionError(u"áéÃóú") |
3764 | - self.replace_perform_changes(raise_error) |
3765 | - self.disable_clear_channels() |
3766 | - |
3767 | - result = self.changer.handle_tasks() |
3768 | - |
3769 | - def got_result(result): |
3770 | - self.assertMessages(self.get_pending_messages(), |
3771 | - [{"operation-id": 123, |
3772 | - "result-code": 100, |
3773 | - "result-text": u"áéÃóú", |
3774 | - "type": "change-packages-result"}]) |
3775 | - return result.addCallback(got_result) |
3776 | - |
3777 | - def test_update_stamp_exists(self): |
3778 | - """ |
3779 | - L{PackageChanger.update_exists} returns C{True} if the |
3780 | - update-stamp file is there, C{False} otherwise. |
3781 | - """ |
3782 | - self.assertTrue(self.changer.update_stamp_exists()) |
3783 | - os.remove(self.config.update_stamp_filename) |
3784 | - self.assertFalse(self.changer.update_stamp_exists()) |
3785 | +<<<<<<< TREE |
3786 | + self.replace_perform_changes(raise_error) |
3787 | + self.disable_clear_channels() |
3788 | + |
3789 | + result = self.changer.handle_tasks() |
3790 | + |
3791 | + def got_result(result): |
3792 | + self.assertMessages(self.get_pending_messages(), |
3793 | + [{"operation-id": 123, |
3794 | + "result-code": 100, |
3795 | + "result-text": u"áéÃóú", |
3796 | + "type": "change-packages-result"}]) |
3797 | + return result.addCallback(got_result) |
3798 | + |
3799 | + def test_update_stamp_exists(self): |
3800 | + """ |
3801 | + L{PackageChanger.update_exists} returns C{True} if the |
3802 | + update-stamp file is there, C{False} otherwise. |
3803 | + """ |
3804 | + self.assertTrue(self.changer.update_stamp_exists()) |
3805 | + os.remove(self.config.update_stamp_filename) |
3806 | + self.assertFalse(self.changer.update_stamp_exists()) |
3807 | +======= |
3808 | + self.replace_perform_changes(raise_error) |
3809 | + self.disable_clear_channels() |
3810 | + |
3811 | + result = self.changer.handle_tasks() |
3812 | + |
3813 | + def got_result(result): |
3814 | + self.assertMessages(self.get_pending_messages(), |
3815 | + [{"operation-id": 123, |
3816 | + "result-code": 100, |
3817 | + "result-text": u"áéÃóú", |
3818 | + "type": "change-packages-result"}]) |
3819 | + return result.addCallback(got_result) |
3820 | + |
3821 | + def test_smart_error_with_unicode_data(self): |
3822 | + self.store.set_hash_ids({HASH1: 1}) |
3823 | + self.store.add_task("changer", |
3824 | + {"type": "change-packages", "install": [1], |
3825 | + "operation-id": 123}) |
3826 | + |
3827 | + def raise_error(self): |
3828 | + raise SmartError(u"áéÃóú") |
3829 | + self.replace_perform_changes(raise_error) |
3830 | + self.disable_clear_channels() |
3831 | + |
3832 | + result = self.changer.handle_tasks() |
3833 | + |
3834 | + def got_result(result): |
3835 | + self.assertMessages(self.get_pending_messages(), |
3836 | + [{"operation-id": 123, |
3837 | + "result-code": 100, |
3838 | + "result-text": u"áéÃóú", |
3839 | + "type": "change-packages-result"}]) |
3840 | + return result.addCallback(got_result) |
3841 | + |
3842 | + def test_update_stamp_exists(self): |
3843 | + """ |
3844 | + L{PackageChanger.update_exists} returns C{True} if the |
3845 | + update-stamp file is there, C{False} otherwise. |
3846 | + """ |
3847 | + self.assertTrue(self.changer.update_stamp_exists()) |
3848 | + os.remove(self.config.update_stamp_filename) |
3849 | + self.assertFalse(self.changer.update_stamp_exists()) |
3850 | +>>>>>>> MERGE-SOURCE |
3851 | |
3852 | def test_binaries_path(self): |
3853 | self.assertEqual( |
3854 | @@ -946,6 +1083,7 @@ |
3855 | self.changer.init_channels([]) |
3856 | self.assertFalse(os.path.exists(existing_deb_path)) |
3857 | |
3858 | +<<<<<<< TREE |
3859 | |
3860 | def test_binaries_available_in_cache(self): |
3861 | """ |
3862 | @@ -974,6 +1112,101 @@ |
3863 | return result.addCallback(got_result) |
3864 | |
3865 | def test_change_package_holds(self): |
3866 | +======= |
3867 | + |
3868 | +class SmartPackageChangerTest(LandscapeTest, PackageChangerTestMixin): |
3869 | + |
3870 | + helpers = [SmartFacadeHelper, BrokerServiceHelper] |
3871 | + |
3872 | + def setUp(self): |
3873 | + |
3874 | + def set_up(ignored): |
3875 | + |
3876 | + self.store = PackageStore(self.makeFile()) |
3877 | + self.config = PackageChangerConfiguration() |
3878 | + self.config.data_path = self.makeDir() |
3879 | + os.mkdir(self.config.package_directory) |
3880 | + os.mkdir(self.config.binaries_path) |
3881 | + touch_file(self.config.update_stamp_filename) |
3882 | + self.changer = PackageChanger( |
3883 | + self.store, self.facade, self.remote, self.config) |
3884 | + service = self.broker_service |
3885 | + service.message_store.set_accepted_types(["change-packages-result", |
3886 | + "operation-result"]) |
3887 | + |
3888 | + result = super(SmartPackageChangerTest, self).setUp() |
3889 | + return result.addCallback(set_up) |
3890 | + |
3891 | + def set_pkg1_installed(self): |
3892 | + previous = self.Facade.channels_reloaded |
3893 | + |
3894 | + def callback(self): |
3895 | + previous(self) |
3896 | + self.get_packages_by_name("name1")[0].installed = True |
3897 | + self.Facade.channels_reloaded = callback |
3898 | + return HASH1 |
3899 | + |
3900 | + def set_pkg2_upgrades_pkg1(self): |
3901 | + previous = self.Facade.channels_reloaded |
3902 | + |
3903 | + def callback(self): |
3904 | + from smart.backends.deb.base import DebUpgrades |
3905 | + previous(self) |
3906 | + [pkg2] = self.get_packages_by_name("name2") |
3907 | + pkg2.upgrades += (DebUpgrades("name1", "=", "version1-release1"),) |
3908 | + self.reload_cache() # Relink relations. |
3909 | + self.Facade.channels_reloaded = callback |
3910 | + self.set_pkg2_satisfied() |
3911 | + self.set_pkg1_installed() |
3912 | + return HASH1, HASH2 |
3913 | + |
3914 | + def set_pkg2_satisfied(self): |
3915 | + previous = self.Facade.channels_reloaded |
3916 | + |
3917 | + def callback(self): |
3918 | + previous(self) |
3919 | + [pkg2] = self.get_packages_by_name("name2") |
3920 | + pkg2.requires = () |
3921 | + self.reload_cache() # Relink relations. |
3922 | + self.Facade.channels_reloaded = callback |
3923 | + return HASH2 |
3924 | + |
3925 | + def set_pkg1_and_pkg2_satisfied(self): |
3926 | + previous = self.Facade.channels_reloaded |
3927 | + |
3928 | + def callback(self): |
3929 | + previous(self) |
3930 | + |
3931 | + provide1 = Provides("prerequirename1", "prerequireversion1") |
3932 | + provide2 = Provides("requirename1", "requireversion1") |
3933 | + [pkg2] = self.get_packages_by_name("name2") |
3934 | + pkg2.provides += (provide1, provide2) |
3935 | + |
3936 | + provide1 = Provides("prerequirename2", "prerequireversion2") |
3937 | + provide2 = Provides("requirename2", "requireversion2") |
3938 | + [pkg1] = self.get_packages_by_name("name1") |
3939 | + pkg1.provides += (provide1, provide2) |
3940 | + |
3941 | + # Ask Smart to reprocess relationships. |
3942 | + self.reload_cache() |
3943 | + self.Facade.channels_reloaded = callback |
3944 | + return HASH1, HASH2 |
3945 | + |
3946 | + def remove_pkg2(self): |
3947 | + os.remove(os.path.join(self.repository_dir, PKGNAME2)) |
3948 | + |
3949 | + def get_transaction_error_message(self): |
3950 | + return "requirename1 = requireversion1" |
3951 | + |
3952 | + def get_binaries_channels(self, binaries_path): |
3953 | + return {binaries_path: {"type": "deb-dir", |
3954 | + "path": binaries_path}} |
3955 | + |
3956 | + def get_package_name(self, package): |
3957 | + return package.name |
3958 | + |
3959 | + def test_change_package_locks(self): |
3960 | +>>>>>>> MERGE-SOURCE |
3961 | """ |
3962 | The L{PackageChanger.handle_tasks} method appropriately creates and |
3963 | deletes package holds as requested by the C{change-packages} |
3964 | @@ -1331,42 +1564,738 @@ |
3965 | "operation-id": 123}) |
3966 | |
3967 | def assert_result(result): |
3968 | - self.assertMessages( |
3969 | - self.get_pending_messages(), |
3970 | - [{"type": "operation-result", |
3971 | - "operation-id": 123, |
3972 | - "status": FAILED, |
3973 | - "result-text": "This client doesn't support package locks.", |
3974 | - "result-code": 1}]) |
3975 | - |
3976 | - result = self.changer.handle_tasks() |
3977 | - return result.addCallback(assert_result) |
3978 | - |
3979 | - def test_change_packages_with_binaries_removes_binaries(self): |
3980 | - """ |
3981 | - After the C{change-packages} handler has installed the binaries, |
3982 | - the binaries and the internal facade deb source is removed. |
3983 | - """ |
3984 | - self.store.add_task("changer", |
3985 | - {"type": "change-packages", "install": [2], |
3986 | - "binaries": [(HASH2, 2, PKGDEB2)], |
3987 | - "operation-id": 123}) |
3988 | - |
3989 | - def return_good_result(self): |
3990 | - return "Yeah, I did whatever you've asked for!" |
3991 | - self.replace_perform_changes(return_good_result) |
3992 | - |
3993 | - result = self.changer.handle_tasks() |
3994 | - |
3995 | - def got_result(result): |
3996 | - self.assertMessages(self.get_pending_messages(), |
3997 | - [{"operation-id": 123, |
3998 | - "result-code": 1, |
3999 | - "result-text": "Yeah, I did whatever you've " |
4000 | - "asked for!", |
4001 | - "type": "change-packages-result"}]) |
4002 | - self.assertEqual([], os.listdir(self.config.binaries_path)) |
4003 | - self.assertFalse( |
4004 | - os.path.exists(self.facade._get_internal_sources_list())) |
4005 | - |
4006 | - return result.addCallback(got_result) |
4007 | +<<<<<<< TREE |
4008 | + self.assertMessages( |
4009 | + self.get_pending_messages(), |
4010 | + [{"type": "operation-result", |
4011 | + "operation-id": 123, |
4012 | + "status": FAILED, |
4013 | + "result-text": "This client doesn't support package locks.", |
4014 | + "result-code": 1}]) |
4015 | + |
4016 | + result = self.changer.handle_tasks() |
4017 | + return result.addCallback(assert_result) |
4018 | + |
4019 | + def test_change_packages_with_binaries_removes_binaries(self): |
4020 | + """ |
4021 | + After the C{change-packages} handler has installed the binaries, |
4022 | + the binaries and the internal facade deb source is removed. |
4023 | + """ |
4024 | + self.store.add_task("changer", |
4025 | + {"type": "change-packages", "install": [2], |
4026 | + "binaries": [(HASH2, 2, PKGDEB2)], |
4027 | + "operation-id": 123}) |
4028 | + |
4029 | + def return_good_result(self): |
4030 | + return "Yeah, I did whatever you've asked for!" |
4031 | + self.replace_perform_changes(return_good_result) |
4032 | + |
4033 | + result = self.changer.handle_tasks() |
4034 | + |
4035 | + def got_result(result): |
4036 | + self.assertMessages(self.get_pending_messages(), |
4037 | + [{"operation-id": 123, |
4038 | + "result-code": 1, |
4039 | + "result-text": "Yeah, I did whatever you've " |
4040 | + "asked for!", |
4041 | + "type": "change-packages-result"}]) |
4042 | + self.assertEqual([], os.listdir(self.config.binaries_path)) |
4043 | + self.assertFalse( |
4044 | + os.path.exists(self.facade._get_internal_sources_list())) |
4045 | + |
4046 | + return result.addCallback(got_result) |
4047 | +======= |
4048 | + self.facade.deinit() |
4049 | + self.assertEqual(self.facade.get_package_locks(), |
4050 | + [("foo", ">=", "1.0")]) |
4051 | + self.assertIn("Queuing message with change package locks results " |
4052 | + "to exchange urgently.", self.logfile.getvalue()) |
4053 | + self.assertMessages(self.get_pending_messages(), |
4054 | + [{"type": "operation-result", |
4055 | + "operation-id": 123, |
4056 | + "status": SUCCEEDED, |
4057 | + "result-text": "Package locks successfully" |
4058 | + " changed.", |
4059 | + "result-code": 0}]) |
4060 | + |
4061 | + result = self.changer.handle_tasks() |
4062 | + return result.addCallback(assert_result) |
4063 | + |
4064 | + def test_change_package_locks_create_with_already_existing(self): |
4065 | + """ |
4066 | + The L{PackageChanger.handle_tasks} method gracefully handles requests |
4067 | + for creating package locks that already exist. |
4068 | + """ |
4069 | + self.facade.set_package_lock("foo") |
4070 | + self.store.add_task("changer", {"type": "change-package-locks", |
4071 | + "create": [("foo", None, None)], |
4072 | + "operation-id": 123}) |
4073 | + |
4074 | + def assert_result(result): |
4075 | + self.facade.deinit() |
4076 | + self.assertEqual(self.facade.get_package_locks(), |
4077 | + [("foo", "", "")]) |
4078 | + self.assertMessages(self.get_pending_messages(), |
4079 | + [{"type": "operation-result", |
4080 | + "operation-id": 123, |
4081 | + "status": SUCCEEDED, |
4082 | + "result-text": "Package locks successfully" |
4083 | + " changed.", |
4084 | + "result-code": 0}]) |
4085 | + |
4086 | + result = self.changer.handle_tasks() |
4087 | + return result.addCallback(assert_result) |
4088 | + |
4089 | + def test_change_package_locks_delete_without_already_existing(self): |
4090 | + """ |
4091 | + The L{PackageChanger.handle_tasks} method gracefully handles requests |
4092 | + for deleting package locks that don't exist. |
4093 | + """ |
4094 | + self.store.add_task("changer", {"type": "change-package-locks", |
4095 | + "delete": [("foo", ">=", "1.0")], |
4096 | + "operation-id": 123}) |
4097 | + |
4098 | + def assert_result(result): |
4099 | + self.facade.deinit() |
4100 | + self.assertEqual(self.facade.get_package_locks(), []) |
4101 | + self.assertMessages(self.get_pending_messages(), |
4102 | + [{"type": "operation-result", |
4103 | + "operation-id": 123, |
4104 | + "status": SUCCEEDED, |
4105 | + "result-text": "Package locks successfully" |
4106 | + " changed.", |
4107 | + "result-code": 0}]) |
4108 | + |
4109 | + result = self.changer.handle_tasks() |
4110 | + return result.addCallback(assert_result) |
4111 | + |
4112 | + def test_dpkg_error(self): |
4113 | + """ |
4114 | + Verify that errors emitted by dpkg are correctly reported to |
4115 | + the server as problems. |
4116 | + |
4117 | + This test is to make sure that Smart reports the problem |
4118 | + correctly. It doesn't make sense for AptFacade, since there we |
4119 | + don't call dpkg. |
4120 | + """ |
4121 | + self.log_helper.ignore_errors(".*dpkg") |
4122 | + |
4123 | + installed_hash = self.set_pkg1_installed() |
4124 | + self.store.set_hash_ids({installed_hash: 1}) |
4125 | + self.store.add_task("changer", |
4126 | + {"type": "change-packages", "remove": [1], |
4127 | + "operation-id": 123}) |
4128 | + |
4129 | + result = self.changer.handle_tasks() |
4130 | + |
4131 | + def got_result(result): |
4132 | + messages = self.get_pending_messages() |
4133 | + self.assertEqual(len(messages), 1, "Too many messages") |
4134 | + message = messages[0] |
4135 | + self.assertEqual(message["operation-id"], 123) |
4136 | + self.assertEqual(message["result-code"], 100) |
4137 | + self.assertEqual(message["type"], "change-packages-result") |
4138 | + text = message["result-text"] |
4139 | + # We can't test the actual content of the message because the dpkg |
4140 | + # error can be localized |
4141 | + self.assertIn("\n[remove] name1_version1-release1\ndpkg: ", text) |
4142 | + self.assertIn("ERROR", text) |
4143 | + self.assertIn("(2)", text) |
4144 | + return result.addCallback(got_result) |
4145 | + |
4146 | + def test_change_package_holds(self): |
4147 | + """ |
4148 | + If C{SmartFacade} is used, the L{PackageChanger.handle_tasks} |
4149 | + method fails the activity, since it can't add or remove dpkg holds. |
4150 | + """ |
4151 | + self.facade.reload_channels() |
4152 | + self.store.add_task("changer", {"type": "change-package-holds", |
4153 | + "create": [1], |
4154 | + "delete": [2], |
4155 | + "operation-id": 123}) |
4156 | + |
4157 | + def assert_result(result): |
4158 | + self.assertIn("Queuing message with change package holds results " |
4159 | + "to exchange urgently.", self.logfile.getvalue()) |
4160 | + self.assertMessages( |
4161 | + self.get_pending_messages(), |
4162 | + [{"type": "operation-result", |
4163 | + "operation-id": 123, |
4164 | + "status": FAILED, |
4165 | + "result-text": "This client doesn't support package holds.", |
4166 | + "result-code": 1}]) |
4167 | + |
4168 | + result = self.changer.handle_tasks() |
4169 | + return result.addCallback(assert_result) |
4170 | + |
4171 | + def test_global_upgrade(self): |
4172 | + """ |
4173 | + Besides asking for individual changes, the server may also request |
4174 | + the client to perform a global upgrade. This would be the equivalent |
4175 | + of a "smart upgrade" command being executed in the command line. |
4176 | + |
4177 | + This test should be run for both C{AptFacade} and |
4178 | + C{SmartFacade}, but due to the smart test setting up that two |
4179 | + packages with different names upgrade each other, the message |
4180 | + doesn't correctly report that the old version should be |
4181 | + uninstalled. The test is still useful, since it shows that the |
4182 | + message contains the changes that smart says are needed. |
4183 | + |
4184 | + Making the test totally correct is a lot of work, that is not |
4185 | + worth doing, since we're removing smart soon. |
4186 | + """ |
4187 | + hash1, hash2 = self.set_pkg2_upgrades_pkg1() |
4188 | + self.store.set_hash_ids({hash1: 1, hash2: 2}) |
4189 | + |
4190 | + self.store.add_task("changer", |
4191 | + {"type": "change-packages", "upgrade-all": True, |
4192 | + "operation-id": 123}) |
4193 | + |
4194 | + result = self.changer.handle_tasks() |
4195 | + |
4196 | + def got_result(result): |
4197 | + self.assertMessages(self.get_pending_messages(), |
4198 | + [{"operation-id": 123, |
4199 | + "must-install": [2], |
4200 | + "result-code": 101, |
4201 | + "type": "change-packages-result"}]) |
4202 | + |
4203 | + return result.addCallback(got_result) |
4204 | + |
4205 | + |
4206 | +class AptPackageChangerTest(LandscapeTest, PackageChangerTestMixin): |
4207 | + |
4208 | + if not has_new_enough_apt: |
4209 | + skip = "Can't use AptFacade on hardy" |
4210 | + |
4211 | + helpers = [AptFacadeHelper, SimpleRepositoryHelper, BrokerServiceHelper] |
4212 | + |
4213 | + def setUp(self): |
4214 | + |
4215 | + def set_up(ignored): |
4216 | + |
4217 | + self.store = PackageStore(self.makeFile()) |
4218 | + self.config = PackageChangerConfiguration() |
4219 | + self.config.data_path = self.makeDir() |
4220 | + os.mkdir(self.config.package_directory) |
4221 | + os.mkdir(self.config.binaries_path) |
4222 | + touch_file(self.config.update_stamp_filename) |
4223 | + self.changer = PackageChanger( |
4224 | + self.store, self.facade, self.remote, self.config) |
4225 | + service = self.broker_service |
4226 | + service.message_store.set_accepted_types(["change-packages-result", |
4227 | + "operation-result"]) |
4228 | + |
4229 | + result = super(AptPackageChangerTest, self).setUp() |
4230 | + return result.addCallback(set_up) |
4231 | + |
4232 | + def set_pkg1_installed(self): |
4233 | + """Return the hash of a package that is installed.""" |
4234 | + self._add_system_package("foo") |
4235 | + self.facade.reload_channels() |
4236 | + [foo] = self.facade.get_packages_by_name("foo") |
4237 | + return self.facade.get_package_hash(foo) |
4238 | + |
4239 | + def set_pkg2_satisfied(self): |
4240 | + """Return the hash of a package that can be installed.""" |
4241 | + self._add_package_to_deb_dir(self.repository_dir, "bar") |
4242 | + self.facade.reload_channels() |
4243 | + [bar] = self.facade.get_packages_by_name("bar") |
4244 | + return self.facade.get_package_hash(bar) |
4245 | + |
4246 | + def set_pkg1_and_pkg2_satisfied(self): |
4247 | + """Make a package depend on another package. |
4248 | + |
4249 | + Return the hashes of the two packages. |
4250 | + """ |
4251 | + self._add_package_to_deb_dir( |
4252 | + self.repository_dir, "foo", control_fields={"Depends": "bar"}) |
4253 | + self._add_package_to_deb_dir(self.repository_dir, "bar") |
4254 | + self.facade.reload_channels() |
4255 | + [foo] = self.facade.get_packages_by_name("foo") |
4256 | + [bar] = self.facade.get_packages_by_name("bar") |
4257 | + return ( |
4258 | + self.facade.get_package_hash(foo), |
4259 | + self.facade.get_package_hash(bar)) |
4260 | + |
4261 | + def set_pkg2_upgrades_pkg1(self): |
4262 | + """Make it so that one package upgrades another. |
4263 | + |
4264 | + Return the hashes of the two packages. |
4265 | + """ |
4266 | + self._add_system_package("foo", version="1.0") |
4267 | + self._add_package_to_deb_dir(self.repository_dir, "foo", version="2.0") |
4268 | + self.facade.reload_channels() |
4269 | + foo_1, foo_2 = sorted(self.facade.get_packages_by_name("foo")) |
4270 | + return ( |
4271 | + self.facade.get_package_hash(foo_1), |
4272 | + self.facade.get_package_hash(foo_2)) |
4273 | + |
4274 | + def remove_pkg2(self): |
4275 | + """Remove package name2 from its repository.""" |
4276 | + packages_file = os.path.join(self.repository_dir, "Packages") |
4277 | + packages_contents = read_file(packages_file) |
4278 | + packages_contents = "\n\n".join( |
4279 | + [stanza for stanza in packages_contents.split("\n\n") |
4280 | + if "Package: name2" not in stanza]) |
4281 | + create_file(packages_file, packages_contents) |
4282 | + |
4283 | + def get_transaction_error_message(self): |
4284 | + """Return part of the apt transaction error message.""" |
4285 | + return "Unable to correct problems" |
4286 | + |
4287 | + def get_binaries_channels(self, binaries_path): |
4288 | + """Return the channels that will be used for the binaries.""" |
4289 | + return [{"baseurl": "file://%s" % binaries_path, |
4290 | + "components": "", |
4291 | + "distribution": "./", |
4292 | + "type": "deb"}] |
4293 | + |
4294 | + def get_package_name(self, version): |
4295 | + """Return the name of the package.""" |
4296 | + return version.package.name |
4297 | + |
4298 | + def test_binaries_available_in_cache(self): |
4299 | + """ |
4300 | + If binaries are included in the changes-packages message, those |
4301 | + will be added to the facade's cache. |
4302 | + """ |
4303 | + # Make sure to turn off automatic rereading of Packages file, |
4304 | + # like it is by default. |
4305 | + self.facade.refetch_package_index = False |
4306 | + self.assertEqual(None, self.facade.get_package_by_hash(HASH2)) |
4307 | + self.store.add_task("changer", |
4308 | + {"type": "change-packages", "install": [2], |
4309 | + "binaries": [(HASH2, 2, PKGDEB2)], |
4310 | + "operation-id": 123}) |
4311 | + |
4312 | + def return_good_result(self): |
4313 | + return "Yeah, I did whatever you've asked for!" |
4314 | + self.replace_perform_changes(return_good_result) |
4315 | + |
4316 | + result = self.changer.handle_tasks() |
4317 | + |
4318 | + def got_result(result): |
4319 | + self.assertNotEqual(None, self.facade.get_package_by_hash(HASH2)) |
4320 | + self.assertFalse(self.facade.refetch_package_index) |
4321 | + |
4322 | + return result.addCallback(got_result) |
4323 | + |
4324 | + def test_change_package_holds(self): |
4325 | + """ |
4326 | + The L{PackageChanger.handle_tasks} method appropriately creates and |
4327 | + deletes package holds as requested by the C{change-package-holds} |
4328 | + message. |
4329 | + """ |
4330 | + self._add_system_package("foo") |
4331 | + self._add_system_package("bar") |
4332 | + self.facade.reload_channels() |
4333 | + self._hash_packages_by_name(self.facade, self.store, "foo") |
4334 | + self._hash_packages_by_name(self.facade, self.store, "bar") |
4335 | + [foo] = self.facade.get_packages_by_name("foo") |
4336 | + [bar] = self.facade.get_packages_by_name("bar") |
4337 | + self.facade.set_package_hold(bar) |
4338 | + # Make sure that the mtime of the dpkg status file is old when |
4339 | + # apt loads it, so that it will be reloaded when asserting the |
4340 | + # test result. |
4341 | + old_mtime = time.time() - 10 |
4342 | + os.utime(self.facade._dpkg_status, (old_mtime, old_mtime)) |
4343 | + self.facade.reload_channels() |
4344 | + self.store.add_task("changer", {"type": "change-package-holds", |
4345 | + "create": [foo.package.id], |
4346 | + "delete": [bar.package.id], |
4347 | + "operation-id": 123}) |
4348 | + |
4349 | + def assert_result(result): |
4350 | + self.facade.reload_channels() |
4351 | + self.assertEqual(["foo"], self.facade.get_package_holds()) |
4352 | + self.assertIn("Queuing message with change package holds results " |
4353 | + "to exchange urgently.", self.logfile.getvalue()) |
4354 | + self.assertMessages( |
4355 | + self.get_pending_messages(), |
4356 | + [{"type": "operation-result", |
4357 | + "operation-id": 123, |
4358 | + "status": SUCCEEDED, |
4359 | + "result-text": "Package holds successfully changed.", |
4360 | + "result-code": 0}]) |
4361 | + |
4362 | + result = self.changer.handle_tasks() |
4363 | + return result.addCallback(assert_result) |
4364 | + |
4365 | + def test_create_package_holds_with_identical_version(self): |
4366 | + """ |
4367 | + The L{PackageChanger.handle_tasks} method appropriately creates |
4368 | + holds as requested by the C{change-package-holds} message even |
4369 | + when versions from two different packages are the same. |
4370 | + """ |
4371 | + self._add_system_package("foo", version="1.1") |
4372 | + self._add_system_package("bar", version="1.1") |
4373 | + self.facade.reload_channels() |
4374 | + self._hash_packages_by_name(self.facade, self.store, "foo") |
4375 | + self._hash_packages_by_name(self.facade, self.store, "bar") |
4376 | + [foo] = self.facade.get_packages_by_name("foo") |
4377 | + [bar] = self.facade.get_packages_by_name("bar") |
4378 | + self.facade.reload_channels() |
4379 | + self.store.add_task("changer", {"type": "change-package-holds", |
4380 | + "create": [foo.package.id, |
4381 | + bar.package.id], |
4382 | + "operation-id": 123}) |
4383 | + |
4384 | + def assert_result(result): |
4385 | + self.assertEqual(["foo", "bar"], self.facade.get_package_holds()) |
4386 | + |
4387 | + result = self.changer.handle_tasks() |
4388 | + return result.addCallback(assert_result) |
4389 | + |
4390 | + def test_delete_package_holds_with_identical_version(self): |
4391 | + """ |
4392 | + The L{PackageChanger.handle_tasks} method appropriately deletes |
4393 | + holds as requested by the C{change-package-holds} message even |
4394 | + when versions from two different packages are the same. |
4395 | + """ |
4396 | + self._add_system_package("foo", version="1.1") |
4397 | + self._add_system_package("bar", version="1.1") |
4398 | + self.facade.reload_channels() |
4399 | + self._hash_packages_by_name(self.facade, self.store, "foo") |
4400 | + self._hash_packages_by_name(self.facade, self.store, "bar") |
4401 | + [foo] = self.facade.get_packages_by_name("foo") |
4402 | + [bar] = self.facade.get_packages_by_name("bar") |
4403 | + self.facade.set_package_hold(foo) |
4404 | + self.facade.set_package_hold(bar) |
4405 | + self.facade.reload_channels() |
4406 | + self.store.add_task("changer", {"type": "change-package-holds", |
4407 | + "delete": [foo.package.id, |
4408 | + bar.package.id], |
4409 | + "operation-id": 123}) |
4410 | + |
4411 | + def assert_result(result): |
4412 | + self.assertEqual([], self.facade.get_package_holds()) |
4413 | + |
4414 | + result = self.changer.handle_tasks() |
4415 | + return result.addCallback(assert_result) |
4416 | + |
4417 | + def test_change_package_holds_create_already_held(self): |
4418 | + """ |
4419 | + If the C{change-package-holds} message requests to add holds for |
4420 | + packages that are already held, the activity succeeds, since the |
4421 | + end result is that the requested package holds are there. |
4422 | + """ |
4423 | + self._add_system_package("foo") |
4424 | + self.facade.reload_channels() |
4425 | + self._hash_packages_by_name(self.facade, self.store, "foo") |
4426 | + [foo] = self.facade.get_packages_by_name("foo") |
4427 | + self.facade.set_package_hold(foo) |
4428 | + self.facade.reload_channels() |
4429 | + self.store.add_task("changer", {"type": "change-package-holds", |
4430 | + "create": [foo.package.id], |
4431 | + "operation-id": 123}) |
4432 | + |
4433 | + def assert_result(result): |
4434 | + self.facade.reload_channels() |
4435 | + self.assertEqual(["foo"], self.facade.get_package_holds()) |
4436 | + self.assertIn("Queuing message with change package holds results " |
4437 | + "to exchange urgently.", self.logfile.getvalue()) |
4438 | + self.assertMessages( |
4439 | + self.get_pending_messages(), |
4440 | + [{"type": "operation-result", |
4441 | + "operation-id": 123, |
4442 | + "status": SUCCEEDED, |
4443 | + "result-text": "Package holds successfully changed.", |
4444 | + "result-code": 0}]) |
4445 | + |
4446 | + result = self.changer.handle_tasks() |
4447 | + return result.addCallback(assert_result) |
4448 | + |
4449 | + def test_change_package_holds_create_other_version_installed(self): |
4450 | + """ |
4451 | + If the C{change-package-holds} message requests to add holds for |
4452 | + packages that have a different version installed than the one |
4453 | + being requested to hold, the activity fails. |
4454 | + |
4455 | + The whole activity is failed, meaning that other valid hold |
4456 | + requests won't get processed. |
4457 | + """ |
4458 | + self._add_system_package("foo", version="1.0") |
4459 | + self._add_package_to_deb_dir( |
4460 | + self.repository_dir, "foo", version="2.0") |
4461 | + self._add_system_package("bar", version="1.0") |
4462 | + self._add_package_to_deb_dir( |
4463 | + self.repository_dir, "bar", version="2.0") |
4464 | + self.facade.reload_channels() |
4465 | + [foo1, foo2] = sorted(self.facade.get_packages_by_name("foo")) |
4466 | + [bar1, bar2] = sorted(self.facade.get_packages_by_name("bar")) |
4467 | + self.store.set_hash_ids({self.facade.get_package_hash(foo1): 1, |
4468 | + self.facade.get_package_hash(foo2): 2, |
4469 | + self.facade.get_package_hash(bar1): 3, |
4470 | + self.facade.get_package_hash(bar2): 4}) |
4471 | + self.facade.reload_channels() |
4472 | + self.store.add_task("changer", {"type": "change-package-holds", |
4473 | + "create": [2, 3], |
4474 | + "operation-id": 123}) |
4475 | + |
4476 | + def assert_result(result): |
4477 | + self.facade.reload_channels() |
4478 | + self.assertEqual([], self.facade.get_package_holds()) |
4479 | + self.assertIn("Queuing message with change package holds results " |
4480 | + "to exchange urgently.", self.logfile.getvalue()) |
4481 | + self.assertMessages( |
4482 | + self.get_pending_messages(), |
4483 | + [{"type": "operation-result", |
4484 | + "operation-id": 123, |
4485 | + "status": FAILED, |
4486 | + "result-text": "Package holds not changed, since the" + |
4487 | + " following packages are not installed: 2", |
4488 | + "result-code": 1}]) |
4489 | + |
4490 | + result = self.changer.handle_tasks() |
4491 | + return result.addCallback(assert_result) |
4492 | + |
4493 | + def test_change_package_holds_create_not_installed(self): |
4494 | + """ |
4495 | + If the C{change-package-holds} message requests to add holds for |
4496 | + packages that aren't installed, the whole activity is failed. If |
4497 | + multiple holds are specified, those won't be added. There's no |
4498 | + difference between a package that is available in some |
4499 | + repository and a package that the facade doesn't know about at |
4500 | + all. |
4501 | + """ |
4502 | + self._add_system_package("foo") |
4503 | + self._add_package_to_deb_dir(self.repository_dir, "bar") |
4504 | + self._add_package_to_deb_dir(self.repository_dir, "baz") |
4505 | + self.facade.reload_channels() |
4506 | + self._hash_packages_by_name(self.facade, self.store, "foo") |
4507 | + self._hash_packages_by_name(self.facade, self.store, "bar") |
4508 | + self._hash_packages_by_name(self.facade, self.store, "baz") |
4509 | + [foo] = self.facade.get_packages_by_name("foo") |
4510 | + [bar] = self.facade.get_packages_by_name("bar") |
4511 | + [baz] = self.facade.get_packages_by_name("baz") |
4512 | + self.store.add_task("changer", {"type": "change-package-holds", |
4513 | + "create": [foo.package.id, |
4514 | + bar.package.id, |
4515 | + baz.package.id], |
4516 | + "operation-id": 123}) |
4517 | + |
4518 | + def assert_result(result): |
4519 | + self.facade.reload_channels() |
4520 | + self.assertEqual([], self.facade.get_package_holds()) |
4521 | + self.assertIn("Queuing message with change package holds results " |
4522 | + "to exchange urgently.", self.logfile.getvalue()) |
4523 | + self.assertMessages( |
4524 | + self.get_pending_messages(), |
4525 | + [{"type": "operation-result", |
4526 | + "operation-id": 123, |
4527 | + "status": FAILED, |
4528 | + "result-text": "Package holds not changed, since the " |
4529 | + "following packages are not installed: " |
4530 | + "%s, %s" % tuple(sorted([bar.package.id, |
4531 | + baz.package.id])), |
4532 | + "result-code": 1}]) |
4533 | + |
4534 | + result = self.changer.handle_tasks() |
4535 | + return result.addCallback(assert_result) |
4536 | + |
4537 | + def test_change_package_holds_create_unknown_hash(self): |
4538 | + """ |
4539 | + If the C{change-package-holds} message requests to add holds for |
4540 | + packages that the client doesn't know about, it's being treated |
4541 | + as the packages not being installed. |
4542 | + """ |
4543 | + self.facade.reload_channels() |
4544 | + self.store.add_task("changer", {"type": "change-package-holds", |
4545 | + "create": [1], |
4546 | + "operation-id": 123}) |
4547 | + |
4548 | + def assert_result(result): |
4549 | + self.facade.reload_channels() |
4550 | + self.assertEqual([], self.facade.get_package_holds()) |
4551 | + self.assertIn("Queuing message with change package holds results " |
4552 | + "to exchange urgently.", self.logfile.getvalue()) |
4553 | + self.assertMessages( |
4554 | + self.get_pending_messages(), |
4555 | + [{"type": "operation-result", |
4556 | + "operation-id": 123, |
4557 | + "status": FAILED, |
4558 | + "result-text": "Package holds not changed, since the" + |
4559 | + " following packages are not installed: 1", |
4560 | + "result-code": 1}]) |
4561 | + |
4562 | + result = self.changer.handle_tasks() |
4563 | + return result.addCallback(assert_result) |
4564 | + |
4565 | + def test_change_package_holds_delete_not_held(self): |
4566 | + """ |
4567 | + If the C{change-package-holds} message requests to remove holds |
4568 | + for packages that aren't held, the activity succeeds if the |
4569 | + right version is installed, since the end result is that the |
4570 | + hold is removed. |
4571 | + """ |
4572 | + self._add_package_to_deb_dir(self.repository_dir, "foo") |
4573 | + self.facade.reload_channels() |
4574 | + self._hash_packages_by_name(self.facade, self.store, "foo") |
4575 | + [foo] = self.facade.get_packages_by_name("foo") |
4576 | + self.store.add_task("changer", {"type": "change-package-holds", |
4577 | + "delete": [foo.package.id], |
4578 | + "operation-id": 123}) |
4579 | + |
4580 | + def assert_result(result): |
4581 | + self.facade.reload_channels() |
4582 | + self.assertEqual([], self.facade.get_package_holds()) |
4583 | + self.assertIn("Queuing message with change package holds results " |
4584 | + "to exchange urgently.", self.logfile.getvalue()) |
4585 | + self.assertMessages( |
4586 | + self.get_pending_messages(), |
4587 | + [{"type": "operation-result", |
4588 | + "operation-id": 123, |
4589 | + "status": SUCCEEDED, |
4590 | + "result-text": "Package holds successfully changed.", |
4591 | + "result-code": 0}]) |
4592 | + |
4593 | + result = self.changer.handle_tasks() |
4594 | + return result.addCallback(assert_result) |
4595 | + |
4596 | + def test_change_package_holds_delete_different_version_held(self): |
4597 | + """ |
4598 | + If the C{change-package-holds} message requests to remove holds |
4599 | + for packages that aren't held, the activity succeeds if the |
4600 | + right version is installed, since the end result is that the |
4601 | + hold is removed. |
4602 | + """ |
4603 | + self._add_system_package("foo", version="1.0") |
4604 | + self._add_package_to_deb_dir( |
4605 | + self.repository_dir, "foo", version="2.0") |
4606 | + self.facade.reload_channels() |
4607 | + [foo1, foo2] = sorted(self.facade.get_packages_by_name("foo")) |
4608 | + self.facade.set_package_hold(foo1) |
4609 | + self.store.set_hash_ids({self.facade.get_package_hash(foo1): 1, |
4610 | + self.facade.get_package_hash(foo2): 2}) |
4611 | + self.facade.reload_channels() |
4612 | + self.store.add_task("changer", {"type": "change-package-holds", |
4613 | + "delete": [2], |
4614 | + "operation-id": 123}) |
4615 | + |
4616 | + def assert_result(result): |
4617 | + self.facade.reload_channels() |
4618 | + self.assertEqual(["foo"], self.facade.get_package_holds()) |
4619 | + self.assertIn("Queuing message with change package holds results " |
4620 | + "to exchange urgently.", self.logfile.getvalue()) |
4621 | + self.assertMessages( |
4622 | + self.get_pending_messages(), |
4623 | + [{"type": "operation-result", |
4624 | + "operation-id": 123, |
4625 | + "status": SUCCEEDED, |
4626 | + "result-text": "Package holds successfully changed.", |
4627 | + "result-code": 0}]) |
4628 | + |
4629 | + result = self.changer.handle_tasks() |
4630 | + return result.addCallback(assert_result) |
4631 | + |
4632 | + def test_change_package_holds_delete_unknown_hash(self): |
4633 | + """ |
4634 | + If the C{change-package-holds} message requests to remove holds |
4635 | + for packages that aren't known by the client, the activity |
4636 | + succeeds, since the end result is that the package isn't |
4637 | + held at that version. |
4638 | + """ |
4639 | + self.store.add_task("changer", {"type": "change-package-holds", |
4640 | + "delete": [1], |
4641 | + "operation-id": 123}) |
4642 | + |
4643 | + def assert_result(result): |
4644 | + self.facade.reload_channels() |
4645 | + self.assertEqual([], self.facade.get_package_holds()) |
4646 | + self.assertIn("Queuing message with change package holds results " |
4647 | + "to exchange urgently.", self.logfile.getvalue()) |
4648 | + self.assertMessages( |
4649 | + self.get_pending_messages(), |
4650 | + [{"type": "operation-result", |
4651 | + "operation-id": 123, |
4652 | + "status": SUCCEEDED, |
4653 | + "result-text": "Package holds successfully changed.", |
4654 | + "result-code": 0}]) |
4655 | + |
4656 | + result = self.changer.handle_tasks() |
4657 | + return result.addCallback(assert_result) |
4658 | + |
4659 | + def test_change_package_holds_delete_not_installed(self): |
4660 | + """ |
4661 | + If the C{change-package-holds} message requests to remove holds |
4662 | + for packages that aren't installed, the activity succeeds, since |
4663 | + the end result is still that the package isn't held at the |
4664 | + requested version. |
4665 | + """ |
4666 | + self._add_system_package("foo") |
4667 | + self.facade.reload_channels() |
4668 | + self._hash_packages_by_name(self.facade, self.store, "foo") |
4669 | + [foo] = self.facade.get_packages_by_name("foo") |
4670 | + self.store.add_task("changer", {"type": "change-package-holds", |
4671 | + "delete": [foo.package.id], |
4672 | + "operation-id": 123}) |
4673 | + |
4674 | + def assert_result(result): |
4675 | + self.facade.reload_channels() |
4676 | + self.assertEqual([], self.facade.get_package_holds()) |
4677 | + self.assertIn("Queuing message with change package holds results " |
4678 | + "to exchange urgently.", self.logfile.getvalue()) |
4679 | + self.assertMessages( |
4680 | + self.get_pending_messages(), |
4681 | + [{"type": "operation-result", |
4682 | + "operation-id": 123, |
4683 | + "status": SUCCEEDED, |
4684 | + "result-text": "Package holds successfully changed.", |
4685 | + "result-code": 0}]) |
4686 | + |
4687 | + result = self.changer.handle_tasks() |
4688 | + return result.addCallback(assert_result) |
4689 | + |
4690 | + def test_change_package_locks(self): |
4691 | + """ |
4692 | + If C{AptFacade} is used, the L{PackageChanger.handle_tasks} |
4693 | + method fails the activity, since it can't add or remove locks because |
4694 | + apt doesn't support this. |
4695 | + """ |
4696 | + self.store.add_task("changer", {"type": "change-package-locks", |
4697 | + "create": [("foo", ">=", "1.0")], |
4698 | + "delete": [("bar", None, None)], |
4699 | + "operation-id": 123}) |
4700 | + |
4701 | + def assert_result(result): |
4702 | + self.assertMessages( |
4703 | + self.get_pending_messages(), |
4704 | + [{"type": "operation-result", |
4705 | + "operation-id": 123, |
4706 | + "status": FAILED, |
4707 | + "result-text": "This client doesn't support package locks.", |
4708 | + "result-code": 1}]) |
4709 | + |
4710 | + result = self.changer.handle_tasks() |
4711 | + return result.addCallback(assert_result) |
4712 | + |
4713 | + def test_change_packages_with_binaries_removes_binaries(self): |
4714 | + """ |
4715 | + After the C{change-packages} handler has installed the binaries, |
4716 | + the binaries and the internal facade deb source is removed. |
4717 | + """ |
4718 | + self.store.add_task("changer", |
4719 | + {"type": "change-packages", "install": [2], |
4720 | + "binaries": [(HASH2, 2, PKGDEB2)], |
4721 | + "operation-id": 123}) |
4722 | + |
4723 | + def return_good_result(self): |
4724 | + return "Yeah, I did whatever you've asked for!" |
4725 | + self.replace_perform_changes(return_good_result) |
4726 | + |
4727 | + result = self.changer.handle_tasks() |
4728 | + |
4729 | + def got_result(result): |
4730 | + self.assertMessages(self.get_pending_messages(), |
4731 | + [{"operation-id": 123, |
4732 | + "result-code": 1, |
4733 | + "result-text": "Yeah, I did whatever you've " |
4734 | + "asked for!", |
4735 | + "type": "change-packages-result"}]) |
4736 | + self.assertEqual([], os.listdir(self.config.binaries_path)) |
4737 | + self.assertFalse( |
4738 | + os.path.exists(self.facade._get_internal_sources_list())) |
4739 | + |
4740 | + return result.addCallback(got_result) |
4741 | +>>>>>>> MERGE-SOURCE |
4742 | |
4743 | === modified file 'landscape/package/tests/test_facade.py' |
4744 | --- landscape/package/tests/test_facade.py 2012-06-19 15:12:07 +0000 |
4745 | +++ landscape/package/tests/test_facade.py 2014-11-19 18:22:39 +0000 |
4746 | @@ -1,19 +1,53 @@ |
4747 | import os |
4748 | -import textwrap |
4749 | -import tempfile |
4750 | - |
4751 | -import apt_pkg |
4752 | -from apt.package import Package |
4753 | -from aptsources.sourceslist import SourcesList |
4754 | - |
4755 | -from landscape.constants import UBUNTU_PATH |
4756 | -from landscape.lib.fs import read_file, create_file |
4757 | +<<<<<<< TREE |
4758 | +import textwrap |
4759 | +import tempfile |
4760 | + |
4761 | +import apt_pkg |
4762 | +from apt.package import Package |
4763 | +from aptsources.sourceslist import SourcesList |
4764 | + |
4765 | +from landscape.constants import UBUNTU_PATH |
4766 | +from landscape.lib.fs import read_file, create_file |
4767 | +======= |
4768 | +import re |
4769 | +import sys |
4770 | +import textwrap |
4771 | +import tempfile |
4772 | + |
4773 | +try: |
4774 | + import smart |
4775 | + from smart.control import Control |
4776 | + from smart.cache import Provides |
4777 | + from smart.const import NEVER, ALWAYS |
4778 | +except ImportError: |
4779 | + # Smart is optional if AptFacade is being used. |
4780 | + pass |
4781 | + |
4782 | +import apt_pkg |
4783 | +from apt.package import Package |
4784 | +from aptsources.sourceslist import SourcesList |
4785 | + |
4786 | +from twisted.internet import reactor |
4787 | +from twisted.internet.defer import Deferred |
4788 | +from twisted.internet.utils import getProcessOutputAndValue |
4789 | + |
4790 | +from landscape.constants import UBUNTU_PATH |
4791 | +from landscape.lib.fs import read_file, create_file |
4792 | +from landscape.package import facade as facade_module |
4793 | +>>>>>>> MERGE-SOURCE |
4794 | from landscape.package.facade import ( |
4795 | +<<<<<<< TREE |
4796 | TransactionError, DependencyError, ChannelError, AptFacade) |
4797 | +======= |
4798 | + TransactionError, DependencyError, ChannelError, SmartError, AptFacade, |
4799 | + has_new_enough_apt) |
4800 | +>>>>>>> MERGE-SOURCE |
4801 | |
4802 | from landscape.tests.mocker import ANY |
4803 | from landscape.tests.helpers import LandscapeTest, EnvironSaverHelper |
4804 | from landscape.package.tests.helpers import ( |
4805 | +<<<<<<< TREE |
4806 | HASH1, HASH2, HASH3, PKGNAME1, PKGNAME2, PKGNAME3, |
4807 | PKGDEB1, PKGNAME_MINIMAL, PKGDEB_MINIMAL, |
4808 | create_deb, AptFacadeHelper, |
4809 | @@ -622,42 +656,2524 @@ |
4810 | ["amd64-package"], |
4811 | sorted(version.package.name |
4812 | for version in self.facade.get_packages())) |
4813 | - |
4814 | - def test_get_package_skeleton(self): |
4815 | - """ |
4816 | - C{get_package_skeleton} returns a C{PackageSkeleton} for a |
4817 | - package. By default extra information is included, but it's |
4818 | - possible to specify that only basic information should be |
4819 | - included. |
4820 | - |
4821 | - The information about the package are unicode strings. |
4822 | - """ |
4823 | - deb_dir = self.makeDir() |
4824 | - create_simple_repository(deb_dir) |
4825 | - self.facade.add_channel_deb_dir(deb_dir) |
4826 | - self.facade.reload_channels() |
4827 | - [pkg1] = self.facade.get_packages_by_name("name1") |
4828 | - [pkg2] = self.facade.get_packages_by_name("name2") |
4829 | - skeleton1 = self.facade.get_package_skeleton(pkg1) |
4830 | - self.assertTrue(isinstance(skeleton1.summary, unicode)) |
4831 | - self.assertEqual("Summary1", skeleton1.summary) |
4832 | - skeleton2 = self.facade.get_package_skeleton(pkg2, with_info=False) |
4833 | - self.assertIs(None, skeleton2.summary) |
4834 | - self.assertEqual(HASH1, skeleton1.get_hash()) |
4835 | - self.assertEqual(HASH2, skeleton2.get_hash()) |
4836 | - |
4837 | - def test_get_package_hash(self): |
4838 | - """ |
4839 | - C{get_package_hash} returns the hash for a given package. |
4840 | - """ |
4841 | - deb_dir = self.makeDir() |
4842 | - create_simple_repository(deb_dir) |
4843 | - self.facade.add_channel_deb_dir(deb_dir) |
4844 | - self.facade.reload_channels() |
4845 | - [pkg] = self.facade.get_packages_by_name("name1") |
4846 | - self.assertEqual(HASH1, self.facade.get_package_hash(pkg)) |
4847 | - [pkg] = self.facade.get_packages_by_name("name2") |
4848 | - self.assertEqual(HASH2, self.facade.get_package_hash(pkg)) |
4849 | +======= |
4850 | + SmartFacadeHelper, HASH1, HASH2, HASH3, PKGNAME1, PKGNAME2, PKGNAME3, |
4851 | + PKGNAME4, PKGDEB4, PKGDEB1, PKGNAME_MINIMAL, PKGDEB_MINIMAL, |
4852 | + create_full_repository, create_deb, AptFacadeHelper, |
4853 | + create_simple_repository) |
4854 | + |
4855 | + |
4856 | +class FakeOwner(object): |
4857 | + """Fake Owner object that apt.progress.text.AcquireProgress expects.""" |
4858 | + |
4859 | + def __init__(self, filesize, error_text=""): |
4860 | + self.id = None |
4861 | + self.filesize = filesize |
4862 | + self.complete = False |
4863 | + self.status = None |
4864 | + self.STAT_DONE = object() |
4865 | + self.error_text = error_text |
4866 | + |
4867 | + |
4868 | +class FakeFetchItem(object): |
4869 | + """Fake Item object that apt.progress.text.AcquireProgress expects.""" |
4870 | + |
4871 | + def __init__(self, owner, description): |
4872 | + self.owner = owner |
4873 | + self.description = description |
4874 | + |
4875 | + |
4876 | +class AptFacadeTest(LandscapeTest): |
4877 | + |
4878 | + if not has_new_enough_apt: |
4879 | + skip = "Can't use AptFacade on hardy" |
4880 | + |
4881 | + helpers = [AptFacadeHelper, EnvironSaverHelper] |
4882 | + |
4883 | + def version_sortkey(self, version): |
4884 | + """Return a key by which a Version object can be sorted.""" |
4885 | + return (version.package, version) |
4886 | + |
4887 | + def patch_cache_commit(self, commit_function=None): |
4888 | + """Patch the apt cache's commit function as to not call dpkg. |
4889 | + |
4890 | + @param commit_function: A function accepting two parameters, |
4891 | + fetch_progress and install_progress. |
4892 | + """ |
4893 | + |
4894 | + def commit(fetch_progress, install_progress): |
4895 | + install_progress.dpkg_exited = True |
4896 | + if commit_function: |
4897 | + commit_function(fetch_progress, install_progress) |
4898 | + |
4899 | + self.facade._cache.commit = commit |
4900 | + |
4901 | + def test_default_root(self): |
4902 | + """ |
4903 | + C{AptFacade} can be created by not providing a root directory, |
4904 | + which means that the currently configured root (most likely /) |
4905 | + will be used. |
4906 | + """ |
4907 | + original_dpkg_root = apt_pkg.config.get("Dir") |
4908 | + facade = AptFacade() |
4909 | + self.assertEqual(original_dpkg_root, apt_pkg.config.get("Dir")) |
4910 | + # Make sure that at least reloading the channels work. |
4911 | + facade.reload_channels() |
4912 | + |
4913 | + def test_custom_root_create_required_files(self): |
4914 | + """ |
4915 | + If a custom root is passed to the constructor, the directory and |
4916 | + files that apt expects to be there will be created. |
4917 | + """ |
4918 | + root = self.makeDir() |
4919 | + AptFacade(root=root) |
4920 | + self.assertTrue(os.path.exists(os.path.join(root, "etc", "apt"))) |
4921 | + self.assertTrue( |
4922 | + os.path.exists(os.path.join(root, "etc", "apt", "sources.list.d"))) |
4923 | + self.assertTrue(os.path.exists( |
4924 | + os.path.join(root, "var", "cache", "apt", "archives", "partial"))) |
4925 | + self.assertTrue(os.path.exists( |
4926 | + os.path.join(root, "var", "lib", "apt", "lists", "partial"))) |
4927 | + self.assertTrue( |
4928 | + os.path.exists(os.path.join(root, "var", "lib", "dpkg", "status"))) |
4929 | + |
4930 | + def test_no_system_packages(self): |
4931 | + """ |
4932 | + If the dpkg status file is empty, not packages are reported by |
4933 | + C{get_packages()}. |
4934 | + """ |
4935 | + self.facade.reload_channels() |
4936 | + self.assertEqual([], list(self.facade.get_packages())) |
4937 | + |
4938 | + def test_get_packages_single_version(self): |
4939 | + """ |
4940 | + If the dpkg status file contains some packages, those packages |
4941 | + are reported by C{get_packages()}. |
4942 | + """ |
4943 | + self._add_system_package("foo") |
4944 | + self._add_system_package("bar") |
4945 | + self.facade.reload_channels() |
4946 | + self.assertEqual( |
4947 | + ["bar", "foo"], |
4948 | + sorted(version.package.name |
4949 | + for version in self.facade.get_packages())) |
4950 | + |
4951 | + def test_get_packages_multiple_version(self): |
4952 | + """ |
4953 | + If there are multiple versions of a package, C{get_packages()} |
4954 | + returns one object per version. |
4955 | + """ |
4956 | + deb_dir = self.makeDir() |
4957 | + self._add_system_package("foo", version="1.0") |
4958 | + self._add_package_to_deb_dir(deb_dir, "foo", version="1.5") |
4959 | + self.facade.add_channel_apt_deb("file://%s" % deb_dir, "./") |
4960 | + self.facade.reload_channels() |
4961 | + self.assertEqual( |
4962 | + [("foo", "1.0"), ("foo", "1.5")], |
4963 | + sorted((version.package.name, version.version) |
4964 | + for version in self.facade.get_packages())) |
4965 | + |
4966 | + def test_get_packages_multiple_architectures(self): |
4967 | + """ |
4968 | + If there are multiple architectures for a package, only the native |
4969 | + architecture is reported by C{get_packages()}. |
4970 | + """ |
4971 | + apt_pkg.config.clear("APT::Architectures") |
4972 | + apt_pkg.config.set("APT::Architecture", "amd64") |
4973 | + apt_pkg.config.set("APT::Architectures::", "amd64") |
4974 | + apt_pkg.config.set("APT::Architectures::", "i386") |
4975 | + facade = AptFacade(apt_pkg.config.get("Dir")) |
4976 | + |
4977 | + self._add_system_package("foo", version="1.0", architecture="amd64") |
4978 | + self._add_system_package("bar", version="1.1", architecture="i386") |
4979 | + facade.reload_channels() |
4980 | + self.assertEqual([("foo", "1.0")], |
4981 | + [(version.package.name, version.version) |
4982 | + for version in facade.get_packages()]) |
4983 | + |
4984 | + def test_add_channel_apt_deb_without_components(self): |
4985 | + """ |
4986 | + C{add_channel_apt_deb()} adds a new deb URL to a file in |
4987 | + sources.list.d. |
4988 | + |
4989 | + If no components are given, nothing is written after the dist. |
4990 | + """ |
4991 | + self.facade.add_channel_apt_deb("http://example.com/ubuntu", "lucid") |
4992 | + list_filename = ( |
4993 | + self.apt_root + |
4994 | + "/etc/apt/sources.list.d/_landscape-internal-facade.list") |
4995 | + sources_contents = read_file(list_filename) |
4996 | + self.assertEqual( |
4997 | + "deb http://example.com/ubuntu lucid\n", |
4998 | + sources_contents) |
4999 | + |
5000 | + def test_add_channel_apt_deb_no_duplicate(self): |