Merge lp:~ubuntu-branches/ubuntu/natty/landscape-client/natty-updates-201411191718 into lp:ubuntu/natty-updates/landscape-client

Proposed by Ubuntu Package Importer
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
Reviewer Review Type Date Requested Status
Ubuntu Development Team Pending
Review via email: mp+242252@code.launchpad.net

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://bugs.launchpad.net/udd linking to this merge proposal.

(this is an automatically generated message)

To post a comment you must log in.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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):
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: