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
=== added directory 'applications'
=== renamed directory 'applications' => 'applications.moved'
=== added file 'applications/landscape-client-settings.desktop.in'
--- applications/landscape-client-settings.desktop.in 1970-01-01 00:00:00 +0000
+++ applications/landscape-client-settings.desktop.in 2014-11-19 18:22:39 +0000
@@ -0,0 +1,17 @@
1[Desktop Entry]
2_Name=Management Service
3_Comment=Management Service Preferences
4Exec=landscape-client-ui-install
5Icon=preferences-management-service
6Terminal=False
7Type=Application
8StartupNotify=true
9Categories=GNOME;GTK;Settings;X-GNOME-SystemSettings;X-GNOME-Settings-Panel;
10OnlyShowIn=GNOME;Unity;
11X-GNOME-Bugzilla-Bugzilla=GNOME
12X-GNOME-Bugzilla-Product=gnome-control-center
13X-GNOME-Bugzilla-Component=sample
14X-GNOME-Bugzilla-Version=1.0.0
15X-GNOME-Settings-Panel=sample
16X-GNOME-Keywords=device;system;information;memory;processor;version;default;application;fallback;preferred;
17X-Ubuntu-Gettext-Domain=landscape-client
018
=== added directory 'apt-update'
=== renamed directory 'apt-update' => 'apt-update.moved'
=== added file 'apt-update/Makefile'
--- apt-update/Makefile 1970-01-01 00:00:00 +0000
+++ apt-update/Makefile 2014-11-19 18:22:39 +0000
@@ -0,0 +1,7 @@
1NAME = apt-update
2
3$(NAME): $(NAME).c
4 $(CC) $(CFLAGS) -Wall $< -o $@
5
6clean:
7 rm -f $(NAME)
08
=== added file 'apt-update/apt-update.c'
--- apt-update/apt-update.c 1970-01-01 00:00:00 +0000
+++ apt-update/apt-update.c 2014-11-19 18:22:39 +0000
@@ -0,0 +1,81 @@
1/*
2
3 Copyright (c) 2011 Canonical, Ltd.
4
5*/
6
7#define _GNU_SOURCE
8#include <sys/resource.h>
9#include <sys/types.h>
10#include <sys/stat.h>
11#include <grp.h>
12#include <unistd.h>
13#include <stdlib.h>
14#include <string.h>
15#include <errno.h>
16#include <stdio.h>
17#include <pwd.h>
18
19int main(int argc, char *argv[], char *envp[])
20{
21 char *apt_argv[] = {"/usr/bin/apt-get", "-q", "update", NULL};
22 char *apt_envp[] = {"PATH=/bin:/usr/bin", NULL, NULL};
23
24 // Set the HOME environment variable
25 struct passwd *pwd = getpwuid(geteuid());
26 if (!pwd) {
27 fprintf(stderr, "error: Unable to find passwd entry for uid %d (%s)\n",
28 geteuid(), strerror(errno));
29 exit(1);
30 }
31 if (asprintf(&apt_envp[1], "HOME=%s", pwd->pw_dir) == -1) {
32 perror("error: Unable to create HOME environment variable");
33 exit(1);
34 }
35
36 // Drop any supplementary group
37 if (setgroups(0, NULL) == -1) {
38 perror("error: Unable to set supplementary groups IDs");
39 exit(1);
40 }
41
42 // Set real/effective gid and uid
43 if (setregid(pwd->pw_gid, pwd->pw_gid) == -1) {
44 fprintf(stderr, "error: Unable to set real and effective gid (%s)\n",
45 strerror(errno));
46 exit(1);
47 }
48 if (setreuid(pwd->pw_uid, pwd->pw_uid) == -1) {
49 perror("error: Unable to set real and effective uid");
50 exit(1);
51 }
52
53 // Close all file descriptors except the standard ones
54 struct rlimit rlp;
55 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1) {
56 perror("error: Unable to determine file descriptor limits");
57 exit(1);
58 }
59 int file_max;
60 if (rlp.rlim_max == RLIM_INFINITY || rlp.rlim_max > 4096)
61 file_max = 4096;
62 else
63 file_max = rlp.rlim_max;
64 int file;
65 for (file = 3; file < file_max; file++) {
66 close(file);
67 }
68
69 // Set umask to 022
70 umask(S_IWGRP | S_IWOTH);
71
72 if (chdir("/") == -1) {
73 perror("error: Unable to change working directory");
74 exit(1);
75 }
76
77 // Run apt-get update
78 execve(apt_argv[0], apt_argv, apt_envp);
79 perror("error: Unable to execute apt-get");
80 return 1;
81}
082
=== added directory 'dbus-1'
=== renamed directory 'dbus-1' => 'dbus-1.moved'
=== added file 'dbus-1/com.canonical.LandscapeClientRegistration.conf'
--- dbus-1/com.canonical.LandscapeClientRegistration.conf 1970-01-01 00:00:00 +0000
+++ dbus-1/com.canonical.LandscapeClientRegistration.conf 2014-11-19 18:22:39 +0000
@@ -0,0 +1,16 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
3"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
4<busconfig>
5 <type>system</type>
6 <policy user="root">
7 <allow own="com.canonical.LandscapeClientRegistration"/>
8 <allow send_destination="com.canonical.LandscapeClientRegistration"/>
9 <allow send_interface="com.canonical.LandscapeClientRegistration"/>
10 </policy>
11
12 <policy context="default">
13 <allow send_destination="com.canonical.LandscapeClientRegistration"/>
14 <allow send_interface="com.canonical.LandscapeClientRegistration"/>
15 </policy>
16</busconfig>
017
=== added file 'dbus-1/com.canonical.LandscapeClientRegistration.service'
--- dbus-1/com.canonical.LandscapeClientRegistration.service 1970-01-01 00:00:00 +0000
+++ dbus-1/com.canonical.LandscapeClientRegistration.service 2014-11-19 18:22:39 +0000
@@ -0,0 +1,4 @@
1[D-BUS Service]
2Name=com.canonical.LandscapeClientRegistration
3Exec=/usr/bin/landscape-client-registration-mechanism
4User=root
0\ No newline at end of file5\ No newline at end of file
16
=== added file 'dbus-1/com.canonical.LandscapeClientSettings.conf'
--- dbus-1/com.canonical.LandscapeClientSettings.conf 1970-01-01 00:00:00 +0000
+++ dbus-1/com.canonical.LandscapeClientSettings.conf 2014-11-19 18:22:39 +0000
@@ -0,0 +1,16 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
3"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
4<busconfig>
5 <type>system</type>
6 <policy user="root">
7 <allow own="com.canonical.LandscapeClientSettings"/>
8 <allow send_destination="com.canonical.LandscapeClientSettings"/>
9 <allow send_interface="com.canonical.LandscapeClientSettings"/>
10 </policy>
11
12 <policy context="default">
13 <allow send_destination="com.canonical.LandscapeClientSettings"/>
14 <allow send_interface="com.canonical.LandscapeClientSettings"/>
15 </policy>
16</busconfig>
017
=== added file 'dbus-1/com.canonical.LandscapeClientSettings.service'
--- dbus-1/com.canonical.LandscapeClientSettings.service 1970-01-01 00:00:00 +0000
+++ dbus-1/com.canonical.LandscapeClientSettings.service 2014-11-19 18:22:39 +0000
@@ -0,0 +1,4 @@
1[D-BUS Service]
2Name=com.canonical.LandscapeClientSettings
3Exec=/usr/bin/landscape-client-settings-mechanism
4User=root
0\ No newline at end of file5\ No newline at end of file
16
=== modified file 'debian/changelog'
--- debian/changelog 2012-06-19 15:12:07 +0000
+++ debian/changelog 2014-11-19 18:22:39 +0000
@@ -1,3 +1,4 @@
1<<<<<<< TREE
1landscape-client (12.05-0ubuntu0.11.04) natty-proposed; urgency=low2landscape-client (12.05-0ubuntu0.11.04) natty-proposed; urgency=low
23
3 * New upstream release 12.05 (r561 in trunk) (LP: #1004678).4 * New upstream release 12.05 (r561 in trunk) (LP: #1004678).
@@ -115,6 +116,84 @@
115116
116 -- Andreas Hasenack <andreas@canonical.com> Tue, 10 Apr 2012 14:14:16 -0300117 -- Andreas Hasenack <andreas@canonical.com> Tue, 10 Apr 2012 14:14:16 -0300
117118
119=======
120landscape-client (12.04.3-0ubuntu0.11.04) UNRELEASED; urgency=low
121
122 Tracking bug: LP: #978884
123
124 [ David Britton ]
125 * Warn on unicode entry into settings UI (LP: #956612).
126 * Sanitise hostname field in settings UI (LP: #954507).
127 * Make it clear that the Landscape service is commercial (LP: #965850)
128 * Further internationalize the settings UI (LP: #962899)
129 * Depend on python-aptdaemon.gtk3widgets instead of python-aptdaemon and
130 replace dependency on python-gobject by python-gi (LP: #961894)
131 * Add i18n to the landscape-client-ui-install script. (LP: #961891)
132
133 [ Andreas Hasenack ]
134 * Fix default landscape hostname in glib schema.
135 * dpkg test improvements to fix intermittent failures.
136 * If ssl_public_key is supplied, use it also when fetching script
137 attachments. This fixes the case of using script execution with
138 attachments when the Landscape server is using a custom CA,
139 most common in LDS deployments. (LP: #959846)
140 * Make sure we have a PATH variable set before doing package
141 activities, and also set it in the initscript for good measure. If
142 the client was configured and restarted by the new UI configuration
143 tool, PATH wasn't set, triggering an error in dpkg. (LP: #961190)
144 * Make landscape-client-ui depend on landscape-client-ui-install, so
145 that we get an entry in the system settings if just
146 landscape-client-ui is installed. The actual entry comes from
147 landscape-client-ui-install.
148 * Optimization: when adding binaries, don't reload every repo, only the one
149 containing the binaries. (LP: #954822)
150 * Handle the case where the user clicks twice inadvertently on the
151 Landscape icon in system settings and don't start a second copy of
152 itself. (LP: #960211)
153 * Change package management features to use APT instead of Smart (LP: #856244,
154 #861707, #859615, #861345, #863239, #863259, #865270, #865272, #865285,
155 #865273, #871641, #865299, #873196, #873939, #876493, #881973, #882438,
156 #866014, #881998, #884142, #884151, #884131, #887037, #886208, #887578,
157 #887947, #889067, #889069, #889087, #889099, #865303, #889113, #890605,
158 #890606, #890609, #897416, #891855, #898681, #898683, #897656, #898542,
159 #862212, #903202, #914734, #914735, #914737, #916301, #915280, #914742,
160 #918925, #918175, #919179, #921664, #921699, #922582, #922511, #921712,
161 #928750, #932136, #928941, #937411, #937567, #925543, #947803, #952973,
162 #948142, #953136, #953906, #956590).
163 * Add a GTK interface to configure the client (LP: #911279, #911666, #912163,
164 #911665, #916300, #931937, #931937, #943622, #945025, #911279, #944652,
165 #948464, #948416, #949158, #911671, #950864, #949208, #949147, #953070,
166 #953292, #953463, #953034, #949200, #953026, #954499, #954516, #954285,
167 #953065, #954414, #954332, #954542, #955966, #955139, #956030, #956119).
168 * Add the ability to auto discover the server location on local deployment
169 (LP: #917422, #927620, #917422, #928585, #929087, #932325, #948564)
170 * Allow the client to accept arbitrary environment variables from the
171 server for script execution (LP: #954999).
172 * Make landscape-config exit non-zero when registration fails and
173 --ok-no-register is not passed (LP: #271759).
174 * Check for the content of /sys/bus/xen/devices to report a machine as a Xen
175 VM instead of just relying on the existence of /sys/bus/xen (LP: #921970).
176 * Make sure cloud registration succeeds if there is no kernel specified in
177 the meta-data service (LP: #920453).
178 * Report private and public IP adresses from the metadata service at cloud
179 registration time (LP: #918366).
180 * Add support for reporting hardware information using lshw (LP: #899002,
181 #943975, #955734).
182 * Add support for the new attachment service in script execution
183 (LP: #893040).
184 * Adds a new message type, 'register-provisioned-machine', which is meant
185 to register computers using an OTP (LP: #881405).
186 * Add local cloning option for load testing (LP: #872830, #925924).
187 * Add more variables to preseeding (LP: #863204, #867710).
188 * Allow the configuration of the ping interval (LP: #397884).
189 * Add fake package reporters for load testing purposes (LP: #821571,
190 #821570).
191 * Report a package reporter error to the server if no APT sources are
192 configured, to trigger a package reporter alert (LP: #823769).
193
194 -- Andreas Hasenack <andreas@canonical.com> Tue, 10 Apr 2012 14:14:16 -0300
195
196>>>>>>> MERGE-SOURCE
118landscape-client (11.07.1.1-0ubuntu0.11.04.0) natty-proposed; urgency=low197landscape-client (11.07.1.1-0ubuntu0.11.04.0) natty-proposed; urgency=low
119198
120 * Try to load the old persist file if the current one doesn't exist or is199 * Try to load the old persist file if the current one doesn't exist or is
121200
=== modified file 'debian/control'
--- debian/control 2012-06-19 15:12:07 +0000
+++ debian/control 2014-11-19 18:22:39 +0000
@@ -3,8 +3,13 @@
3Priority: optional3Priority: optional
4Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>4Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
5XSBC-Original-Maintainer: Landscape Team <landscape-team@canonical.com>5XSBC-Original-Maintainer: Landscape Team <landscape-team@canonical.com>
6<<<<<<< TREE
6Build-Depends: debhelper (>= 5), po-debconf, python-dev, python-central | python-support, lsb-release, gawk, python-twisted-core, python-distutils-extra7Build-Depends: debhelper (>= 5), po-debconf, python-dev, python-central | python-support, lsb-release, gawk, python-twisted-core, python-distutils-extra
7Standards-Version: 3.8.28Standards-Version: 3.8.2
9=======
10Build-Depends: debhelper (>= 5), po-debconf, python-dev, python-central | python-support, lsb-release, gawk, python-twisted-core, python-distutils-extra
11Standards-Version: 3.8.0
12>>>>>>> MERGE-SOURCE
8XS-Python-Version: >= 2.4, << 2.813XS-Python-Version: >= 2.4, << 2.8
914
10Package: landscape-common15Package: landscape-common
@@ -36,8 +41,13 @@
36Depends: ${python:Depends}, ${misc:Depends}, ${extra:Depends},41Depends: ${python:Depends}, ${misc:Depends}, ${extra:Depends},
37 ${shlibs:Depends},42 ${shlibs:Depends},
38 python-twisted-web,43 python-twisted-web,
44<<<<<<< TREE
39 python-twisted-names,45 python-twisted-names,
40 landscape-common (= ${binary:Version})46 landscape-common (= ${binary:Version})
47=======
48 python-twisted-names,
49 landscape-common (>= ${Source-Version})
50>>>>>>> MERGE-SOURCE
41Suggests: ${extra:Suggests}51Suggests: ${extra:Suggests}
42Description: The Landscape administration system client52Description: The Landscape administration system client
43 Landscape is a web-based tool for managing Ubuntu systems. This53 Landscape is a web-based tool for managing Ubuntu systems. This
@@ -46,6 +56,7 @@
46 .56 .
47 This package provides the Landscape client and requires a Landscape account.57 This package provides the Landscape client and requires a Landscape account.
48XB-Python-Version: ${python:Versions}58XB-Python-Version: ${python:Versions}
59<<<<<<< TREE
4960
50Package: landscape-client-ui61Package: landscape-client-ui
51Architecture: any62Architecture: any
@@ -76,3 +87,35 @@
76 .87 .
77 This package provides an automatic installer for landscape-client-ui.88 This package provides an automatic installer for landscape-client-ui.
78XB-Python-Version: ${python:Versions}89XB-Python-Version: ${python:Versions}
90=======
91
92Package: landscape-client-ui
93Architecture: any
94Depends: ${python:Depends}, ${misc:Depends},
95 landscape-client (>= ${Source-Version}),
96 landscape-client-ui-install (>= ${Source-Version}),
97 python-gi,
98 python-dbus,
99 policykit-1,
100 gir1.2-notify-0.7,
101 gir1.2-gtk-3.0
102Description: The Landscape administration system client - UI configuration
103 Landscape is a web-based tool for managing Ubuntu systems.
104 .
105 This package provides the Landscape client configuration UI.
106XB-Python-Version: ${python:Versions}
107
108Package: landscape-client-ui-install
109Architecture: any
110Depends: ${python:Depends}, ${misc:Depends},
111 python-gi,
112 python-dbus,
113 policykit-1,
114 gir1.2-gtk-3.0,
115 python-aptdaemon.gtk3widgets
116Description: The Landscape administration system client - UI installer
117 Landscape is a web-based tool for managing Ubuntu systems.
118 .
119 This package provides an automatic installer for landscape-client-ui.
120XB-Python-Version: ${python:Versions}
121>>>>>>> MERGE-SOURCE
79122
=== added file 'debian/landscape-client-ui-install.install'
--- debian/landscape-client-ui-install.install 1970-01-01 00:00:00 +0000
+++ debian/landscape-client-ui-install.install 2014-11-19 18:22:39 +0000
@@ -0,0 +1,4 @@
1usr/bin/landscape-client-ui-install
2usr/share/applications/landscape-client-settings.desktop
3usr/share/icons/hicolor/scalable/apps/preferences-management-service.svg
4usr/share/locale
05
=== renamed file 'debian/landscape-client-ui-install.install' => 'debian/landscape-client-ui-install.install.moved'
=== added file 'debian/landscape-client-ui.install'
--- debian/landscape-client-ui.install 1970-01-01 00:00:00 +0000
+++ debian/landscape-client-ui.install 2014-11-19 18:22:39 +0000
@@ -0,0 +1,9 @@
1usr/bin/landscape-client-settings-mechanism
2usr/bin/landscape-client-registration-mechanism
3usr/bin/landscape-client-settings-ui
4usr/share/dbus-1/system-services/com.canonical.LandscapeClientSettings.service
5usr/share/dbus-1/system-services/com.canonical.LandscapeClientRegistration.service
6usr/share/polkit-1/actions/com.canonical.LandscapeClientSettings.policy
7etc/dbus-1/system.d/com.canonical.LandscapeClientSettings.conf
8etc/dbus-1/system.d/com.canonical.LandscapeClientRegistration.conf
9usr/share/glib-2.0/schemas/com.canonical.landscape-client-settings.gschema.xml
010
=== renamed file 'debian/landscape-client-ui.install' => 'debian/landscape-client-ui.install.moved'
=== modified file 'debian/landscape-client.templates'
--- debian/landscape-client.templates 2012-06-19 15:12:07 +0000
+++ debian/landscape-client.templates 2014-11-19 18:22:39 +0000
@@ -17,6 +17,7 @@
17 needed if the given account is requesting a client registration17 needed if the given account is requesting a client registration
18 password.18 password.
1919
20<<<<<<< TREE
20Template: landscape-client/url21Template: landscape-client/url
21Type: string22Type: string
22Default: https://landscape.canonical.com/message-system23Default: https://landscape.canonical.com/message-system
@@ -69,6 +70,60 @@
69 first registration. Once the machine is registered, these tags can only be70 first registration. Once the machine is registered, these tags can only be
70 changed using the Landscape server.71 changed using the Landscape server.
7172
73=======
74Template: landscape-client/url
75Type: string
76Default: https://landscape.canonical.com/message-system
77_Description: Landscape Server URL:
78 The server URL to connect to.
79
80Template: landscape-client/exchange_interval
81Type: string
82Default: 900
83_Description: Message Exchange Interval:
84 Interval, in seconds, between normal message exchanges with the Landscape
85 server.
86
87Template: landscape-client/urgent_exchange_interval
88Type: string
89Default: 60
90_Description: Urgent Message Exchange Interval:
91 Interval, in seconds, between urgent message exchanges with the Landscape
92 server.
93
94Template: landscape-client/ping_url
95Type: string
96Default: http://landscape.canonical.com/ping
97_Description: Landscape PingServer URL:
98 The URL to perform lightweight exchange initiation with.
99
100Template: landscape-client/ping_interval
101Type: string
102Default: 30
103_Description: Ping Interval:
104 Interval, in seconds, between client ping exchanges with the Landscape server.
105
106Template: landscape-client/http_proxy
107Type: string
108Default:
109_Description: HTTP proxy (blank for none):
110 The URL of the HTTP proxy, if one is needed.
111
112Template: landscape-client/https_proxy
113Type: string
114Default:
115_Description: HTTPS proxy (blank for none):
116 The URL of the HTTPS proxy, if one is needed.
117
118Template: landscape-client/tags
119Type: string
120Default:
121_Description: Initial tags for first registration
122 Comma separated list of tags which will be assigned to this computer on its
123 first registration. Once the machine is registered, these tags can only be
124 changed using the Landscape server.
125
126>>>>>>> MERGE-SOURCE
72Template: landscape-client/register_system127Template: landscape-client/register_system
73Type: boolean128Type: boolean
74Default: false129Default: false
75130
=== modified file 'debian/po/templates.pot'
--- debian/po/templates.pot 2012-06-19 15:12:07 +0000
+++ debian/po/templates.pot 2014-11-19 18:22:39 +0000
@@ -8,7 +8,11 @@
8msgstr ""8msgstr ""
9"Project-Id-Version: landscape-client\n"9"Project-Id-Version: landscape-client\n"
10"Report-Msgid-Bugs-To: landscape-client@packages.debian.org\n"10"Report-Msgid-Bugs-To: landscape-client@packages.debian.org\n"
11<<<<<<< TREE
11"POT-Creation-Date: 2012-06-01 18:35-0300\n"12"POT-Creation-Date: 2012-06-01 18:35-0300\n"
13=======
14"POT-Creation-Date: 2012-03-07 17:07+0100\n"
15>>>>>>> MERGE-SOURCE
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"16"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"17"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"18"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -59,6 +63,7 @@
59"if the given account is requesting a client registration password."63"if the given account is requesting a client registration password."
60msgstr ""64msgstr ""
6165
66<<<<<<< TREE
62#. Type: string67#. Type: string
63#. Description68#. Description
64#: ../landscape-client.templates:400169#: ../landscape-client.templates:4001
@@ -164,6 +169,113 @@
164"changed using the Landscape server."169"changed using the Landscape server."
165msgstr ""170msgstr ""
166171
172=======
173#. Type: string
174#. Description
175#: ../landscape-client.templates:4001
176msgid "Landscape Server URL:"
177msgstr ""
178
179#. Type: string
180#. Description
181#: ../landscape-client.templates:4001
182msgid "The server URL to connect to."
183msgstr ""
184
185#. Type: string
186#. Description
187#: ../landscape-client.templates:5001
188msgid "Message Exchange Interval:"
189msgstr ""
190
191#. Type: string
192#. Description
193#: ../landscape-client.templates:5001
194msgid ""
195"Interval, in seconds, between normal message exchanges with the Landscape "
196"server."
197msgstr ""
198
199#. Type: string
200#. Description
201#: ../landscape-client.templates:6001
202msgid "Urgent Message Exchange Interval:"
203msgstr ""
204
205#. Type: string
206#. Description
207#: ../landscape-client.templates:6001
208msgid ""
209"Interval, in seconds, between urgent message exchanges with the Landscape "
210"server."
211msgstr ""
212
213#. Type: string
214#. Description
215#: ../landscape-client.templates:7001
216msgid "Landscape PingServer URL:"
217msgstr ""
218
219#. Type: string
220#. Description
221#: ../landscape-client.templates:7001
222msgid "The URL to perform lightweight exchange initiation with."
223msgstr ""
224
225#. Type: string
226#. Description
227#: ../landscape-client.templates:8001
228msgid "Ping Interval:"
229msgstr ""
230
231#. Type: string
232#. Description
233#: ../landscape-client.templates:8001
234msgid ""
235"Interval, in seconds, between client ping exchanges with the Landscape "
236"server."
237msgstr ""
238
239#. Type: string
240#. Description
241#: ../landscape-client.templates:9001
242msgid "HTTP proxy (blank for none):"
243msgstr ""
244
245#. Type: string
246#. Description
247#: ../landscape-client.templates:9001
248msgid "The URL of the HTTP proxy, if one is needed."
249msgstr ""
250
251#. Type: string
252#. Description
253#: ../landscape-client.templates:10001
254msgid "HTTPS proxy (blank for none):"
255msgstr ""
256
257#. Type: string
258#. Description
259#: ../landscape-client.templates:10001
260msgid "The URL of the HTTPS proxy, if one is needed."
261msgstr ""
262
263#. Type: string
264#. Description
265#: ../landscape-client.templates:11001
266msgid "Initial tags for first registration"
267msgstr ""
268
269#. Type: string
270#. Description
271#: ../landscape-client.templates:11001
272msgid ""
273"Comma separated list of tags which will be assigned to this computer on its "
274"first registration. Once the machine is registered, these tags can only be "
275"changed using the Landscape server."
276msgstr ""
277
278>>>>>>> MERGE-SOURCE
167#. Type: boolean279#. Type: boolean
168#. Description280#. Description
169#: ../landscape-client.templates:12001281#: ../landscape-client.templates:12001
@@ -172,6 +284,7 @@
172284
173#. Type: boolean285#. Type: boolean
174#. Description286#. Description
287<<<<<<< TREE
175#: ../landscape-client.templates:12001288#: ../landscape-client.templates:12001
176msgid ""289msgid ""
177"Register this system with a preexisting Landscape account. Please go to "290"Register this system with a preexisting Landscape account. Please go to "
@@ -212,6 +325,26 @@
212#. this will break the choices shown to users325#. this will break the choices shown to users
213#: ../landscape-common.templates:1001326#: ../landscape-common.templates:1001
214msgid "Run sysinfo on every login"327msgid "Run sysinfo on every login"
328=======
329#: ../landscape-client.templates:12001
330msgid ""
331"Register this system with a preexisting Landscape account. Please go to "
332"http://landscape.canonical.com if you need a Landscape account."
333msgstr ""
334
335#. Type: select
336#. Choices
337#. Translators beware! the following three strings form a single
338#. Choices menu. - Every one of these strings has to fit in a standard
339#. 80 characters console, as the fancy screen setup takes up some space
340#. try to keep below ~71 characters.
341#. DO NOT USE commas (,) in Choices translations otherwise
342#. this will break the choices shown to users
343#: ../landscape-common.templates:1001
344msgid ""
345"Do not display sysinfo on login, Cache sysinfo in /etc/motd, Run sysinfo on "
346"every login"
347>>>>>>> MERGE-SOURCE
215msgstr ""348msgstr ""
216349
217#. Type: select350#. Type: select
218351
=== modified file 'debian/rules'
--- debian/rules 2012-06-19 15:12:07 +0000
+++ debian/rules 2014-11-19 18:22:39 +0000
@@ -2,6 +2,7 @@
22
3dist_release := $(shell lsb_release -cs)3dist_release := $(shell lsb_release -cs)
4ifneq ($(dist_release),dapper)4ifneq ($(dist_release),dapper)
5<<<<<<< TREE
5 use_pycentral = yes6 use_pycentral = yes
6endif7endif
7ifeq (,$(filter $(dist_release), hardy lucid))8ifeq (,$(filter $(dist_release), hardy lucid))
@@ -12,6 +13,18 @@
12ifeq (,$(filter $(dist_release),hardy lucid natty oneiric))13ifeq (,$(filter $(dist_release),hardy lucid natty oneiric))
13 # We want landscape-client-ui only from precise onward14 # We want landscape-client-ui only from precise onward
14 dh_extra_flags += -plandscape-client-ui -plandscape-client-ui-install15 dh_extra_flags += -plandscape-client-ui -plandscape-client-ui-install
16=======
17 use_pycentral = yes
18endif
19ifeq (,$(filter $(dist_release), hardy lucid))
20 use_dhpython2 = yes
21endif
22
23dh_extra_flags = -plandscape-common -plandscape-client
24ifneq (,$(filter $(dist_release),precise))
25 # We want landscape-client-ui only from precise onward
26 dh_extra_flags += -plandscape-client-ui -plandscape-client-ui-install
27>>>>>>> MERGE-SOURCE
15endif28endif
1629
17-include /usr/share/python/python.mk30-include /usr/share/python/python.mk
1831
=== added file 'dev/dns-server'
--- dev/dns-server 1970-01-01 00:00:00 +0000
+++ dev/dns-server 2014-11-19 18:22:39 +0000
@@ -0,0 +1,99 @@
1#!/usr/bin/env python
2"""
3This script creates a DNS server that has enough functionality to test
4the server autodiscovery feature of landscape client.
5
6Landscape client uses /etc/resolv.conf to find the location of name servers.
7To have landscape client use this server, make sure that:
8
9nameserver 127.0.0.1
10
11is the first nameserver in /etc/resolv.conf. Linux name lookups only support
12port 53, so this program must run on port 53 in order to work. Be aware that
13NetworkManager overwrites this file any time your network configuration
14changes.
15
16This program does not return enough information for a tool like dig to complete
17successfully. If this is needed, use Bind 9, detailed at
18https://wiki.canonical.com/Landscape/SpecRegistry/0082
19"""
20
21import argparse
22import sys
23from twisted.internet import reactor, defer
24from twisted.names import dns, common
25from twisted.names.server import DNSServerFactory
26
27
28PORT = 5553
29SRV_RESPONSE = 'lds1.mylandscapehost.com'
30A_RESPONSE = '127.0.0.1'
31
32
33class SimpleResolver(common.ResolverBase):
34 def _lookup(self, name, cls, typ, timeout):
35 """
36 Respond to DNS requests. See documentation for
37 L{twisted.names.common.ResolverBase}.
38 """
39 # This nameserver returns the same result all the time, regardless
40 # of what name the client asks for.
41 results = []
42 ttl = 60
43 if typ == dns.SRV:
44 record = dns.Record_SRV(0, 1, 80, SRV_RESPONSE, ttl)
45 owner = '_landscape._tcp.mylandscapehost.com'
46 results.append(dns.RRHeader(owner, record.TYPE, dns.IN, ttl,
47 record, auth=True))
48 elif typ == dns.A:
49 record = dns.Record_A(A_RESPONSE)
50 owner = 'landscape.localdomain'
51 results.append(dns.RRHeader(owner, record.TYPE, dns.IN, ttl,
52 record, auth=True))
53
54 authority = []
55 return defer.succeed((results, authority, []))
56
57
58def parse_command_line(args):
59 global SRV_RESPONSE, A_RESPONSE, PORT
60 description = """
61 This test tool responds to DNS queries for SRV and A records. It always
62 responds with the same result regardless of the query string sent by the
63 client.
64
65 To test this tool, try the following commands:
66 dig -p 5553 @127.0.0.1 SRV _landscape._tcp.mylandscapehost.com
67
68 dig -p 5553 @127.0.0.1 localhost.localdomain
69 """
70 parser = argparse.ArgumentParser(description=description)
71 parser.add_argument("--srv-response", type=str, default=SRV_RESPONSE,
72 help="Give this reply to SRV queries (eg: localhost)")
73 parser.add_argument("--a-response", type=str, default=A_RESPONSE,
74 help="Give this reply to A queries (eg: 127.0.0.1)")
75 parser.add_argument("--port", type=int, default=PORT,
76 help="Listen on this port (default 5553). DNS "
77 "normally runs on port 53")
78
79 args = vars(parser.parse_args())
80 SRV_RESPONSE = args["srv_response"]
81 A_RESPONSE = args["a_response"]
82 PORT = args["port"]
83
84
85def main():
86 parse_command_line(sys.argv)
87
88 simple_resolver = SimpleResolver()
89 factory = DNSServerFactory(authorities=[simple_resolver], verbose=1)
90 protocol = dns.DNSDatagramProtocol(factory)
91 print "starting reactor on port %s.." % PORT
92 reactor.listenTCP(PORT, factory)
93 reactor.listenUDP(PORT, protocol)
94 reactor.run()
95 print "reactor stopped..."
96
97
98if __name__ == "__main__":
99 main()
0100
=== renamed file 'dev/dns-server' => 'dev/dns-server.moved'
=== added directory 'glib-2.0'
=== renamed directory 'glib-2.0' => 'glib-2.0.moved'
=== added directory 'glib-2.0/schemas'
=== added file 'glib-2.0/schemas/com.canonical.landscape-client-settings.gschema.xml'
--- glib-2.0/schemas/com.canonical.landscape-client-settings.gschema.xml 1970-01-01 00:00:00 +0000
+++ glib-2.0/schemas/com.canonical.landscape-client-settings.gschema.xml 2014-11-19 18:22:39 +0000
@@ -0,0 +1,37 @@
1<?xml version="1.0" encoding="utf-8"?>
2<schemalist>
3 <schema id="com.canonical.landscape-client-settings" path="/com/canonical/landscape-client-settings/">
4 <key type="s" name="management-type">
5 <default>"not"</default>
6 <summary>Whether the client settings UI currently set to a hosted client configuration, an LDS instance or no management service.</summary>
7 </key>
8 <key type="s" name="computer-title">
9 <default>""</default>
10 <summary>The title to register the machine with.</summary>
11 </key>
12 <key type="s" name="hosted-landscape-host">
13 <default>"landscape.canonical.com"</default>
14 <summary>The hostname of the Canonical hosted landscape system.</summary>
15 </key>
16 <key type="s" name="hosted-account-name">
17 <default>""</default>
18 <summary>An account name for use with the Canonical hosted landscape system.</summary>
19 </key>
20 <key type="s" name="hosted-password">
21 <default>""</default>
22 <summary>A password for use with the Canonical hosted landscape system</summary>
23 </key>
24 <key type="s" name="local-landscape-host">
25 <default>""</default>
26 <summary>The hostname of the local landscape system.</summary>
27 </key>
28 <key type="s" name="local-account-name">
29 <default>""</default>
30 <summary>An account name for use with the local landscape system.</summary>
31 </key>
32 <key type="s" name="local-password">
33 <default>""</default>
34 <summary>A password for use with the local landscape system</summary>
35 </key>
36 </schema>
37</schemalist>
038
=== added directory 'icons'
=== renamed directory 'icons' => 'icons.moved'
=== added file 'icons/preferences-management-service.svg'
--- icons/preferences-management-service.svg 1970-01-01 00:00:00 +0000
+++ icons/preferences-management-service.svg 2014-11-19 18:22:39 +0000
@@ -0,0 +1,62 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In -->
3
4<svg
5 xmlns:dc="http://purl.org/dc/elements/1.1/"
6 xmlns:cc="http://creativecommons.org/ns#"
7 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8 xmlns:svg="http://www.w3.org/2000/svg"
9 xmlns="http://www.w3.org/2000/svg"
10 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12 version="1.1"
13 x="0px"
14 y="0px"
15 width="32"
16 height="32"
17 viewBox="-0.787 -0.867 32 32"
18 enable-background="new -0.787 -0.867 285 285"
19 xml:space="preserve"
20 id="svg2"
21 inkscape:version="0.48.2 r9819"
22 sodipodi:docname="canonical_aubergine_hex.svg"><metadata
23 id="metadata12"><rdf:RDF><cc:Work
24 rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
25 rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><sodipodi:namedview
26 pagecolor="#ffffff"
27 bordercolor="#666666"
28 borderopacity="1"
29 objecttolerance="10"
30 gridtolerance="10"
31 guidetolerance="10"
32 inkscape:pageopacity="0"
33 inkscape:pageshadow="2"
34 inkscape:window-width="697"
35 inkscape:window-height="480"
36 id="namedview10"
37 showgrid="false"
38 fit-margin-top="0"
39 fit-margin-left="0"
40 fit-margin-right="0"
41 fit-margin-bottom="0"
42 inkscape:zoom="7.38"
43 inkscape:cx="15.9035"
44 inkscape:cy="16.176"
45 inkscape:window-x="449"
46 inkscape:window-y="100"
47 inkscape:window-maximized="0"
48 inkscape:current-layer="svg2" />
49<defs
50 id="defs4">
51</defs>
52<path
53 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"
54 id="path6"
55 inkscape:connector-curvature="0"
56 style="fill:#772953" />
57<path
58 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"
59 id="path8"
60 inkscape:connector-curvature="0"
61 style="fill:#772953" />
62</svg>
0\ No newline at end of file63\ No newline at end of file
164
=== modified file 'landscape/__init__.py'
--- landscape/__init__.py 2012-06-19 15:12:07 +0000
+++ landscape/__init__.py 2014-11-19 18:22:39 +0000
@@ -1,7 +1,11 @@
1import sys1import sys
22
3DEBIAN_REVISION = ""3DEBIAN_REVISION = ""
4<<<<<<< TREE
4UPSTREAM_VERSION = "12.05"5UPSTREAM_VERSION = "12.05"
6=======
7UPSTREAM_VERSION = "12.04.3"
8>>>>>>> MERGE-SOURCE
5VERSION = "%s%s" % (UPSTREAM_VERSION, DEBIAN_REVISION)9VERSION = "%s%s" % (UPSTREAM_VERSION, DEBIAN_REVISION)
610
7# The "server-api" field of outgoing messages will be set to this value, and11# The "server-api" field of outgoing messages will be set to this value, and
812
=== modified file 'landscape/constants.py'
=== modified file 'landscape/deployment.py'
--- landscape/deployment.py 2012-06-19 15:12:07 +0000
+++ landscape/deployment.py 2014-11-19 18:22:39 +0000
@@ -282,6 +282,7 @@
282 - C{quiet} (C{False})282 - C{quiet} (C{False})
283 - C{log_dir} (C{"/var/log/landscape"})283 - C{log_dir} (C{"/var/log/landscape"})
284 - C{log_level} (C{"info"})284 - C{log_level} (C{"info"})
285<<<<<<< TREE
285 - C{url} (C{"http://landscape.canonical.com/message-system"})286 - C{url} (C{"http://landscape.canonical.com/message-system"})
286 - C{ping_url} (C{"http://landscape.canonical.com/ping"})287 - C{ping_url} (C{"http://landscape.canonical.com/ping"})
287 - C{ssl_public_key}288 - C{ssl_public_key}
@@ -289,6 +290,15 @@
289 - C{autodiscover_srv_query_string}290 - C{autodiscover_srv_query_string}
290 (C{"_tcp._landscape.localdomain"})291 (C{"_tcp._landscape.localdomain"})
291 - C{autodiscover_a_query_string} (C{"landscape.localdomain"})292 - C{autodiscover_a_query_string} (C{"landscape.localdomain"})
293=======
294 - C{url} (C{"http://landcape.canonical.com/message-system"})
295 - C{ping_url} (C{"http://landscape.canonical.com/ping"})
296 - C{ssl_public_key}
297 - C{server_autodiscover} (C{"false"})
298 - C{autodiscover_srv_query_string}
299 (C{"_tcp._landscape.localdomain"})
300 - C{autodiscover_a_query_string} (C{"landscape.localdomain"})
301>>>>>>> MERGE-SOURCE
292 - C{ignore_sigint} (C{False})302 - C{ignore_sigint} (C{False})
293 """303 """
294 parser = super(Configuration, self).make_parser()304 parser = super(Configuration, self).make_parser()
295305
=== added file 'landscape/lib/dns.py'
--- landscape/lib/dns.py 1970-01-01 00:00:00 +0000
+++ landscape/lib/dns.py 2014-11-19 18:22:39 +0000
@@ -0,0 +1,78 @@
1"""DNS lookups for server autodiscovery."""
2
3import logging
4from twisted.names import dns
5from twisted.names.client import Resolver
6
7
8def discover_server(autodiscover_srv_query_string="",
9 autodiscover_a_query_string="", resolver=None):
10 """
11 Look up the dns location of the landscape server.
12
13 @param autodiscover_srv_query_string: The query string to send to the DNS
14 server when making a SRV query.
15 @param autodiscover_a_query_string: The query string to send to the DNS
16 server when making a A query.
17 @type resolver: The resolver to use. If none is specified a resolver that
18 uses settings from /etc/resolv.conf will be created. (Testing only)
19 """
20 if not resolver:
21 resolver = Resolver("/etc/resolv.conf")
22 d = _lookup_server_record(resolver, autodiscover_srv_query_string)
23 d.addErrback(_lookup_hostname, resolver, autodiscover_a_query_string)
24 return d
25
26
27def _lookup_server_record(resolver, service_name):
28 """
29 Do a DNS SRV record lookup for the location of the landscape server.
30
31 @type resolver: A resolver to use for DNS lookups
32 L{twisted.names.client.Resolver}.
33 @param service_name: The query string to send to the DNS server when
34 making a SRV query.
35 @return: A deferred containing either the hostname of the landscape server
36 if found or an empty string if not found.
37 """
38 def lookup_done(result):
39 name = ""
40 for item in result:
41 for row in item:
42 if row.type == dns.SRV:
43 name = row.payload.target.name
44 break
45 return name
46
47 def lookup_failed(result):
48 logging.info("SRV lookup of %s failed." % service_name)
49 return result
50
51 d = resolver.lookupService(service_name)
52 d.addCallback(lookup_done)
53 d.addErrback(lookup_failed)
54 return d
55
56
57def _lookup_hostname(result, resolver, hostname):
58 """
59 Do a DNS name lookup for the location of the landscape server.
60
61 @param result: The result from a call to lookup_server_record.
62 @param resolver: The resolver to use for DNS lookups.
63 @param hostname: The query string to send to the DNS server when making
64 a A query.
65 @param return: A deferred containing the ip address of the landscape
66 server if found or None if not found.
67 """
68 def lookup_done(result):
69 return result
70
71 def lookup_failed(result):
72 logging.info("Name lookup of %s failed." % hostname)
73 return result
74
75 d = resolver.getHostByName(hostname)
76 d.addCallback(lookup_done)
77 d.addErrback(lookup_failed)
78 return d
079
=== renamed file 'landscape/lib/dns.py' => 'landscape/lib/dns.py.moved'
=== added file 'landscape/lib/tests/test_dns.py'
--- landscape/lib/tests/test_dns.py 1970-01-01 00:00:00 +0000
+++ landscape/lib/tests/test_dns.py 2014-11-19 18:22:39 +0000
@@ -0,0 +1,208 @@
1from landscape.tests.helpers import LandscapeTest
2from landscape.lib.dns import (
3 _lookup_server_record, _lookup_hostname, discover_server)
4
5from twisted.internet import defer
6from twisted.names import dns
7from twisted.names.error import ResolverError
8
9
10class FakeResolverResult(object):
11 """
12 A fake resolver result returned by L{FakeResolver}.
13
14 @param type: The result type L{twisted.names.dns.SRV}
15 @param payload: The result contents
16 """
17 def __init__(self):
18 self.type = None
19
20 class Payload(object):
21 """
22 A payload result returned by fake resolver.
23
24 @param target: The result of the lookup
25 """
26 def __init__(self):
27 self.target = ""
28
29 class Target(object):
30 """
31 A payload target returned by fake resolver.
32
33 @param name: The name contained by the target.
34 """
35 def __init__(self):
36 self.name = ""
37
38 self.payload = Payload()
39 self.payload.target = Target()
40
41
42class FakeResolver(object):
43 """
44 A fake resolver that mimics L{twisted.names.client.Resolver}
45 """
46 def __init__(self):
47 self.results = None
48 self.name = None
49 self.queried = None
50
51 def lookupService(self, arg1):
52 self.queried = arg1
53 deferred = defer.Deferred()
54 deferred.callback(self.results)
55 return deferred
56
57 def getHostByName(self, arg1):
58 self.queried = arg1
59 deferred = defer.Deferred()
60 deferred.callback(self.name)
61 return deferred
62
63
64class BadResolver(object):
65 """
66 A resolver that mimics L{twisted.names.client.Resolver} and always returns
67 an error.
68 """
69 def lookupService(self, arg1):
70 deferred = defer.Deferred()
71 deferred.errback(ResolverError("Couldn't connect"))
72 return deferred
73
74 def getHostByName(self, arg1):
75 deferred = defer.Deferred()
76 deferred.errback(ResolverError("Couldn't connect"))
77 return deferred
78
79
80class DnsSrvLookupTest(LandscapeTest):
81 def test_with_server_found(self):
82 """
83 Looking up a DNS SRV record should return the result of the lookup.
84 """
85 fake_result = FakeResolverResult()
86 fake_result.type = dns.SRV
87 fake_result.payload.target.name = "a.b.com"
88 fake_resolver = FakeResolver()
89 fake_resolver.results = [[fake_result]]
90 query_string = "_landscape._tcp.mylandscapehost.com"
91
92 def check(result):
93 self.assertEqual(fake_resolver.queried, query_string)
94 self.assertEqual("a.b.com", result)
95
96 d = _lookup_server_record(fake_resolver, query_string)
97 d.addCallback(check)
98 return d
99
100 def test_with_server_not_found(self):
101 """
102 Looking up a DNS SRV record and finding nothing exists should return
103 an empty string.
104 """
105 fake_resolver = FakeResolver()
106 fake_resolver.results = [[]]
107
108 def check(result):
109 self.assertEqual("", result)
110
111 d = _lookup_server_record(fake_resolver,
112 "_landscape._tcp.mylandscapehost.com")
113 d.addCallback(check)
114 return d
115
116 def test_with_resolver_error(self):
117 """A resolver error triggers error handling code."""
118 # The failure should be properly logged
119 logging_mock = self.mocker.replace("logging.info")
120 logging_mock("SRV lookup of _landscape._tcp.mylandscapehost.com "
121 "failed.")
122 self.mocker.replay()
123
124 d = _lookup_server_record(BadResolver(),
125 "_landscape._tcp.mylandscapehost.com")
126 self.assertFailure(d, ResolverError)
127 return d
128
129
130class DnsNameLookupTest(LandscapeTest):
131 def test_with_name_found(self):
132 """
133 Looking up a DNS name record should return the result of the lookup.
134 """
135 fake_resolver = FakeResolver()
136 fake_resolver.name = "a.b.com"
137 query_string = "landscape.localdomain"
138
139 def check(result):
140 self.assertEqual(fake_resolver.queried, query_string)
141 self.assertEqual("a.b.com", result)
142
143 d = _lookup_hostname(None, fake_resolver, query_string)
144 d.addCallback(check)
145 return d
146
147 def test_with_name_not_found(self):
148 """
149 Looking up a DNS NAME record and not finding a result should return
150 None.
151 """
152 fake_resolver = FakeResolver()
153 fake_resolver.name = None
154
155 def check(result):
156 self.assertEqual(None, result)
157
158 d = _lookup_hostname(None, fake_resolver, "landscape.localdomain")
159 d.addCallback(check)
160 return d
161
162 def test_with_resolver_error(self):
163 """A resolver error triggers error handling code."""
164 # The failure should be properly logged
165 logging_mock = self.mocker.replace("logging.info")
166 logging_mock("Name lookup of landscape.localdomain failed.")
167 self.mocker.replay()
168
169 d = _lookup_hostname(None, BadResolver(), "landscape.localdomain")
170 self.assertFailure(d, ResolverError)
171 return d
172
173
174class DiscoverServerTest(LandscapeTest):
175 def test_srv_lookup(self):
176 """The DNS name of the server is found using a SRV lookup."""
177 fake_result = FakeResolverResult()
178 fake_result.type = dns.SRV
179 fake_result.payload.target.name = "a.b.com"
180 fake_resolver = FakeResolver()
181 fake_resolver.results = [[fake_result]]
182
183 d = discover_server(resolver=fake_resolver)
184
185 def check(result):
186 self.assertEqual("a.b.com", result)
187
188 d.addCallback(check)
189 return d
190
191 def test_a_name_lookup(self):
192 """The DNS name of the server is found using an A name lookup."""
193 fake_resolver = FakeResolver()
194 fake_resolver.name = "x.y.com"
195
196 d = discover_server(resolver=fake_resolver)
197
198 def check(result):
199 self.assertEqual("x.y.com", result)
200
201 d.addCallback(check)
202 return d
203
204 def test_failed_lookup(self):
205 """A resolver error is returned when server autodiscovery fails."""
206 d = _lookup_server_record(BadResolver(), "landscape.localdomain")
207 self.assertFailure(d, ResolverError)
208 return d
0209
=== renamed file 'landscape/lib/tests/test_dns.py' => 'landscape/lib/tests/test_dns.py.moved'
=== added file 'landscape/manager/fakepackagemanager.py'
--- landscape/manager/fakepackagemanager.py 1970-01-01 00:00:00 +0000
+++ landscape/manager/fakepackagemanager.py 2014-11-19 18:22:39 +0000
@@ -0,0 +1,57 @@
1import logging
2import os
3import random
4
5from twisted.internet.utils import getProcessOutput
6from twisted.internet.defer import succeed
7
8from landscape.package.store import PackageStore
9from landscape.package.changer import PackageChanger
10from landscape.package.releaseupgrader import ReleaseUpgrader
11from landscape.manager.plugin import ManagerPlugin
12from landscape.manager.manager import SUCCEEDED
13
14
15class FakePackageManager(ManagerPlugin):
16
17 run_interval = 1800
18 randint = random.randint
19
20 def register(self, registry):
21 super(FakePackageManager, self).register(registry)
22 self.config = registry.config
23
24 registry.register_message("change-packages",
25 self.handle_change_packages)
26 registry.register_message("change-package-locks",
27 self.handle_change_package_locks)
28 registry.register_message("release-upgrade",
29 self.handle_release_upgrade)
30
31 def _handle(self, response):
32 delay = self.randint(30, 300)
33 self.registry.reactor.call_later(
34 delay, self.manager.broker.send_message, response, urgent=True)
35
36 def handle_change_packages(self, message):
37 response = {"type": "change-packages-result",
38 "operation-id": message.get("operation-id"),
39 "result-code": 1,
40 "result-text": "OK done."}
41 return self._handle(response)
42
43 def handle_change_package_locks(self, message):
44 response = {"type": "operation-result",
45 "operation-id": message.get("operation-id"),
46 "status": SUCCEEDED,
47 "result-text": "Package locks successfully changed.",
48 "result-code": 0}
49 return self._handle(response)
50
51 def handle_release_upgrade(self, message):
52 response = {"type": "operation-result",
53 "operation-id": message.get("operation-id"),
54 "status": SUCCEEDED,
55 "result-text": "Successful release upgrade.",
56 "result-code": 0}
57 return self._handle(response)
058
=== renamed file 'landscape/manager/fakepackagemanager.py' => 'landscape/manager/fakepackagemanager.py.moved'
=== added file 'landscape/manager/hardwareinfo.py'
--- landscape/manager/hardwareinfo.py 1970-01-01 00:00:00 +0000
+++ landscape/manager/hardwareinfo.py 2014-11-19 18:22:39 +0000
@@ -0,0 +1,28 @@
1import os
2
3from twisted.internet.utils import getProcessOutput
4
5from landscape.manager.plugin import ManagerPlugin
6
7
8class HardwareInfo(ManagerPlugin):
9 """A plugin to retrieve hardware information."""
10
11 message_type = "hardware-info"
12 run_interval = 60 * 60 * 24
13 run_immediately = True
14 command = "/usr/bin/lshw"
15
16 def run(self):
17 self.call_on_accepted(self.message_type, self.send_message)
18 return self.registry.broker.call_if_accepted(
19 self.message_type, self.send_message)
20
21 def send_message(self):
22 result = getProcessOutput(
23 self.command, args=["-xml", "-quiet"], env=os.environ, path=None)
24 return result.addCallback(self._got_output)
25
26 def _got_output(self, output):
27 message = {"type": self.message_type, "data": output}
28 return self.registry.broker.send_message(message)
029
=== renamed file 'landscape/manager/hardwareinfo.py' => 'landscape/manager/hardwareinfo.py.moved'
=== modified file 'landscape/manager/tests/test_aptsources.py'
=== added file 'landscape/manager/tests/test_fakepackagemanager.py'
--- landscape/manager/tests/test_fakepackagemanager.py 1970-01-01 00:00:00 +0000
+++ landscape/manager/tests/test_fakepackagemanager.py 2014-11-19 18:22:39 +0000
@@ -0,0 +1,70 @@
1from landscape.manager.plugin import SUCCEEDED
2
3from landscape.manager.fakepackagemanager import FakePackageManager
4from landscape.tests.helpers import LandscapeTest, ManagerHelper
5
6
7class FakePackageManagerTest(LandscapeTest):
8 """Tests for the fake package manager plugin."""
9
10 helpers = [ManagerHelper]
11
12 def setUp(self):
13 super(FakePackageManagerTest, self).setUp()
14 self.package_manager = FakePackageManager()
15 self.package_manager.randint = lambda x, y: 0
16
17 def test_handle_change_packages(self):
18 """
19 L{FakePackageManager} is able to handle a C{change-packages} message,
20 creating a C{change-packages-result} in response.
21 """
22 self.manager.add(self.package_manager)
23 service = self.broker_service
24 service.message_store.set_accepted_types(["change-packages-result"])
25 message = {"type": "change-packages", "operation-id": 1}
26 self.manager.dispatch_message(message)
27 self.manager.reactor.advance(1)
28
29 self.assertMessages(service.message_store.get_pending_messages(),
30 [{"type": "change-packages-result",
31 "result-text": "OK done.",
32 "result-code": 1, "operation-id": 1}])
33
34 def test_handle_change_package_locks(self):
35 """
36 L{FakePackageManager} is able to handle a C{change-package-locks}
37 message, creating a C{operation-result} in response.
38 """
39 self.manager.add(self.package_manager)
40 service = self.broker_service
41 service.message_store.set_accepted_types(["operation-result"])
42 message = {"type": "change-package-locks", "operation-id": 1}
43 self.manager.dispatch_message(message)
44 self.manager.reactor.advance(1)
45
46 self.assertMessages(service.message_store.get_pending_messages(),
47 [{"type": "operation-result",
48 "result-text":
49 "Package locks successfully changed.",
50 "result-code": 0, "status": SUCCEEDED,
51 "operation-id": 1}])
52
53 def test_handle_release_upgrade(self):
54 """
55 L{FakePackageManager} is able to handle a C{release-upgrade} message,
56 creating a C{operation-result} in response.
57 """
58 self.manager.add(self.package_manager)
59 service = self.broker_service
60 service.message_store.set_accepted_types(["operation-result"])
61 message = {"type": "release-upgrade", "operation-id": 1}
62 self.manager.dispatch_message(message)
63 self.manager.reactor.advance(1)
64
65 self.assertMessages(service.message_store.get_pending_messages(),
66 [{"type": "operation-result",
67 "result-text":
68 "Successful release upgrade.",
69 "result-code": 0, "status": SUCCEEDED,
70 "operation-id": 1}])
071
=== renamed file 'landscape/manager/tests/test_fakepackagemanager.py' => 'landscape/manager/tests/test_fakepackagemanager.py.moved'
=== added file 'landscape/manager/tests/test_hardwareinfo.py'
--- landscape/manager/tests/test_hardwareinfo.py 1970-01-01 00:00:00 +0000
+++ landscape/manager/tests/test_hardwareinfo.py 2014-11-19 18:22:39 +0000
@@ -0,0 +1,45 @@
1from landscape.tests.helpers import LandscapeTest, ManagerHelper
2
3from landscape.manager.hardwareinfo import HardwareInfo
4
5
6class HardwareInfoTests(LandscapeTest):
7 helpers = [ManagerHelper]
8
9 def setUp(self):
10 super(HardwareInfoTests, self).setUp()
11 self.info = HardwareInfo()
12 self.info.command = "/bin/echo"
13 self.manager.add(self.info)
14
15 service = self.broker_service
16 service.message_store.set_accepted_types(["hardware-info"])
17
18 def test_message(self):
19 """
20 L{HardwareInfo} sends the output of its command when running.
21 """
22 deferred = self.info.send_message()
23
24 def check(ignored):
25 self.assertMessages(
26 self.broker_service.message_store.get_pending_messages(),
27 [{"data": u"-xml -quiet\n", "type": "hardware-info"}])
28
29 return deferred.addCallback(check)
30
31 def test_run_upgraded_system(self):
32 """
33 L{HardwareInfo} sends the output of its command when running on
34 a system that has been upgraded to include this plugin, i.e.
35 where the client already knows that it can send the
36 hardware-info message.
37 """
38 deferred = self.info.run()
39
40 def check(ignored):
41 self.assertMessages(
42 self.broker_service.message_store.get_pending_messages(),
43 [{"data": u"-xml -quiet\n", "type": "hardware-info"}])
44
45 return deferred.addCallback(check)
046
=== renamed file 'landscape/manager/tests/test_hardwareinfo.py' => 'landscape/manager/tests/test_hardwareinfo.py.moved'
=== modified file 'landscape/monitor/packagemonitor.py'
--- landscape/monitor/packagemonitor.py 2012-06-19 15:12:07 +0000
+++ landscape/monitor/packagemonitor.py 2014-11-19 18:22:39 +0000
@@ -53,6 +53,7 @@
53 if "packages" in message_types:53 if "packages" in message_types:
54 self.spawn_reporter()54 self.spawn_reporter()
5555
56<<<<<<< TREE
56 def _run_fake_reporter(self, args):57 def _run_fake_reporter(self, args):
57 """Run a fake-reporter in-process."""58 """Run a fake-reporter in-process."""
5859
@@ -103,6 +104,58 @@
103104
104 return result.addBoth(done)105 return result.addBoth(done)
105106
107=======
108 def _run_fake_reporter(self, args):
109 """Run a fake-reporter in-process."""
110
111 class FakeFacade(object):
112 """
113 A fake facade to workaround the issue that the SmartFacade
114 essentially allows only once instance per process.
115 """
116
117 def get_arch(self):
118 arch = os.uname()[-1]
119 result = {"pentium": "i386",
120 "i86pc": "i386",
121 "x86_64": "amd64"}.get(arch)
122 if result:
123 arch = result
124 elif (arch[0] == "i" and arch.endswith("86")):
125 arch = "i386"
126 return arch
127
128 if getattr(self, "_fake_reporter", None) is None:
129
130 from landscape.package.reporter import (
131 FakeReporter, PackageReporterConfiguration)
132 from landscape.package.store import FakePackageStore
133 package_facade = FakeFacade()
134 package_config = PackageReporterConfiguration()
135 package_config.load(args + ["-d", self.config.data_path,
136 "-l", self.config.log_dir])
137 package_store = FakePackageStore(package_config.store_filename)
138 self._fake_reporter = FakeReporter(package_store, package_facade,
139 self.registry.broker,
140 package_config)
141 self._fake_reporter.global_store_filename = os.path.join(
142 self.config.master_data_path, "package", "database")
143 self._fake_reporter_running = False
144
145 if self._fake_reporter_running:
146 from twisted.internet.defer import succeed
147 return succeed(None)
148
149 self._fake_reporter_running = True
150 result = self._fake_reporter.run()
151
152 def done(passthrough):
153 self._fake_reporter_running = False
154 return passthrough
155
156 return result.addBoth(done)
157
158>>>>>>> MERGE-SOURCE
106 def spawn_reporter(self):159 def spawn_reporter(self):
107 args = ["--quiet"]160 args = ["--quiet"]
108 if self.config.config:161 if self.config.config:
109162
=== modified file 'landscape/package/changer.py'
--- landscape/package/changer.py 2012-06-19 15:12:07 +0000
+++ landscape/package/changer.py 2014-11-19 18:22:39 +0000
@@ -18,7 +18,11 @@
18from landscape.package.taskhandler import (18from landscape.package.taskhandler import (
19 PackageTaskHandler, PackageTaskHandlerConfiguration, PackageTaskError,19 PackageTaskHandler, PackageTaskHandlerConfiguration, PackageTaskError,
20 run_task_handler)20 run_task_handler)
21<<<<<<< TREE
21from landscape.manager.manager import FAILED22from landscape.manager.manager import FAILED
23=======
24from landscape.manager.manager import FAILED, SUCCEEDED
25>>>>>>> MERGE-SOURCE
2226
2327
24class UnknownPackageData(Exception):28class UnknownPackageData(Exception):
@@ -99,6 +103,8 @@
99 return result.addErrback(self.unknown_package_data_error, task)103 return result.addErrback(self.unknown_package_data_error, task)
100 if message["type"] == "change-package-locks":104 if message["type"] == "change-package-locks":
101 return self.handle_change_package_locks(message)105 return self.handle_change_package_locks(message)
106 if message["type"] == "change-package-holds":
107 return self.handle_change_package_holds(message)
102108
103 def unknown_package_data_error(self, failure, task):109 def unknown_package_data_error(self, failure, task):
104 """Handle L{UnknownPackageData} data errors.110 """Handle L{UnknownPackageData} data errors.
@@ -293,13 +299,105 @@
293 Package locks aren't supported anymore.299 Package locks aren't supported anymore.
294 """300 """
295301
302<<<<<<< TREE
296 response = {303 response = {
297 "type": "operation-result",304 "type": "operation-result",
298 "operation-id": message.get("operation-id"),305 "operation-id": message.get("operation-id"),
299 "status": FAILED,306 "status": FAILED,
300 "result-text": "This client doesn't support package locks.",307 "result-text": "This client doesn't support package locks.",
301 "result-code": 1}308 "result-code": 1}
302 return self._broker.send_message(response, True)309=======
310 if not self._facade.supports_package_locks:
311 response = {
312 "type": "operation-result",
313 "operation-id": message.get("operation-id"),
314 "status": FAILED,
315 "result-text": "This client doesn't support package locks.",
316 "result-code": 1}
317 return self._broker.send_message(response, True)
318
319 for lock in message.get("create", ()):
320 self._facade.set_package_lock(*lock)
321 for lock in message.get("delete", ()):
322 self._facade.remove_package_lock(*lock)
323 self._facade.save_config()
324
325 response = {"type": "operation-result",
326 "operation-id": message.get("operation-id"),
327 "status": SUCCEEDED,
328 "result-text": "Package locks successfully changed.",
329 "result-code": 0}
330
331 logging.info("Queuing message with change package locks results to "
332 "exchange urgently.")
333>>>>>>> MERGE-SOURCE
334 return self._broker.send_message(response, True)
335
336 def _send_change_package_holds_response(self, response):
337 """Log that a package holds result is sent and send the response."""
338 logging.info("Queuing message with change package holds results to "
339 "exchange urgently.")
340 return self._broker.send_message(response, True)
341
342 def handle_change_package_holds(self, message):
343 """Handle a C{change-package-holds} message.
344
345 Create and delete package holds as requested by the given C{message}.
346 """
347 if not self._facade.supports_package_holds:
348 response = {
349 "type": "operation-result",
350 "operation-id": message.get("operation-id"),
351 "status": FAILED,
352 "result-text": "This client doesn't support package holds.",
353 "result-code": 1}
354 return self._send_change_package_holds_response(response)
355
356 not_installed = set()
357 holds_to_create = message.get("create", [])
358 versions_to_create = set()
359 for id in holds_to_create:
360 hash = self._store.get_id_hash(id)
361 hold_version = self._facade.get_package_by_hash(hash)
362 if (hold_version
363 and self._facade.is_package_installed(hold_version)):
364 versions_to_create.add((hold_version.package, hold_version))
365 else:
366 not_installed.add(str(id))
367 holds_to_remove = message.get("delete", [])
368 versions_to_remove = set()
369 for id in holds_to_remove:
370 hash = self._store.get_id_hash(id)
371 hold_version = self._facade.get_package_by_hash(hash)
372 if (hold_version
373 and self._facade.is_package_installed(hold_version)):
374 versions_to_remove.add((hold_version.package, hold_version))
375
376 if not_installed:
377 response = {
378 "type": "operation-result",
379 "operation-id": message.get("operation-id"),
380 "status": FAILED,
381 "result-text": "Package holds not changed, since the" +
382 " following packages are not installed: %s" % (
383 ", ".join(sorted(not_installed))),
384 "result-code": 1}
385 return self._send_change_package_holds_response(response)
386
387 for package, hold_version in versions_to_create:
388 self._facade.set_package_hold(hold_version)
389 for package, hold_version in versions_to_remove:
390 self._facade.remove_package_hold(hold_version)
391
392 self._facade.reload_channels()
393
394 response = {"type": "operation-result",
395 "operation-id": message.get("operation-id"),
396 "status": SUCCEEDED,
397 "result-text": "Package holds successfully changed.",
398 "result-code": 0}
399
400 return self._send_change_package_holds_response(response)
303401
304 @staticmethod402 @staticmethod
305 def find_command():403 def find_command():
306404
=== modified file 'landscape/package/facade.py'
--- landscape/package/facade.py 2012-06-19 15:12:07 +0000
+++ landscape/package/facade.py 2014-11-19 18:22:39 +0000
@@ -1,3 +1,4 @@
1<<<<<<< TREE
1import hashlib2import hashlib
2import os3import os
3import subprocess4import subprocess
@@ -22,6 +23,47 @@
22from landscape.lib.fs import append_file, create_file, read_file23from landscape.lib.fs import append_file, create_file, read_file
23from landscape.constants import UBUNTU_PATH24from landscape.constants import UBUNTU_PATH
24from landscape.package.skeleton import build_skeleton_apt25from landscape.package.skeleton import build_skeleton_apt
26=======
27import hashlib
28import os
29import subprocess
30import tempfile
31from cStringIO import StringIO
32from operator import attrgetter
33
34has_smart = True
35try:
36 import smart
37 from smart.transaction import (
38 Transaction, PolicyInstall, PolicyUpgrade, PolicyRemove, Failed)
39 from smart.const import INSTALL, REMOVE, UPGRADE, ALWAYS, NEVER
40except ImportError:
41 has_smart = False
42
43# Importing apt throws a FutureWarning on hardy, that we don't want to
44# see.
45import warnings
46warnings.filterwarnings("ignore", module="apt", category=FutureWarning)
47del warnings
48
49import apt
50import apt_inst
51import apt_pkg
52
53has_new_enough_apt = True
54from aptsources.sourceslist import SourcesList
55try:
56 from apt.progress.text import AcquireProgress
57 from apt.progress.base import InstallProgress
58except ImportError:
59 AcquireProgress = object
60 InstallProgress = object
61 has_new_enough_apt = False
62
63from landscape.lib.fs import append_file, create_file, read_file
64from landscape.constants import UBUNTU_PATH
65from landscape.package.skeleton import build_skeleton, build_skeleton_apt
66>>>>>>> MERGE-SOURCE
2567
2668
27class TransactionError(Exception):69class TransactionError(Exception):
@@ -43,64 +85,708 @@
43 """Raised when channels fail to load."""85 """Raised when channels fail to load."""
4486
4587
46class LandscapeAcquireProgress(AcquireProgress):88<<<<<<< TREE
4789class LandscapeAcquireProgress(AcquireProgress):
48 def _winch(self, *dummy):90
49 """Override trying to get the column count of the buffer.91 def _winch(self, *dummy):
5092 """Override trying to get the column count of the buffer.
51 We always send the output to a file, not to a terminal, so the93
52 default width (80 columns) is fine for us.94 We always send the output to a file, not to a terminal, so the
5395 default width (80 columns) is fine for us.
54 Overriding this method means that we don't have to care about96
55 fcntl.ioctl API differences for different Python versions.97 Overriding this method means that we don't have to care about
56 """98 fcntl.ioctl API differences for different Python versions.
5799 """
58100
59class LandscapeInstallProgress(InstallProgress):101
60102class LandscapeInstallProgress(InstallProgress):
61 dpkg_exited = None103
62104 dpkg_exited = None
63 def wait_child(self):105
64 """Override to find out whether dpkg exited or not.106 def wait_child(self):
65107 """Override to find out whether dpkg exited or not.
66 The C{run()} method returns os.WEXITSTATUS(res) without checking108
67 os.WIFEXITED(res) first, so it can signal that everything is ok,109 The C{run()} method returns os.WEXITSTATUS(res) without checking
68 even though something causes dpkg not to exit cleanly.110 os.WIFEXITED(res) first, so it can signal that everything is ok,
69111 even though something causes dpkg not to exit cleanly.
70 Save whether dpkg exited cleanly into the C{dpkg_exited}112
71 attribute. If dpkg exited cleanly the exit code can be used to113 Save whether dpkg exited cleanly into the C{dpkg_exited}
72 determine whether there were any errors. If dpkg didn't exit114 attribute. If dpkg exited cleanly the exit code can be used to
73 cleanly it should mean that something went wrong.115 determine whether there were any errors. If dpkg didn't exit
74 """116 cleanly it should mean that something went wrong.
75 res = super(LandscapeInstallProgress, self).wait_child()117 """
76 self.dpkg_exited = os.WIFEXITED(res)118 res = super(LandscapeInstallProgress, self).wait_child()
77 return res119 self.dpkg_exited = os.WIFEXITED(res)
78120 return res
79121
80class AptFacade(object):122
81 """Wrapper for tasks using Apt.123class AptFacade(object):
82124 """Wrapper for tasks using Apt.
83 This object wraps Apt features, in a way that makes using and testing125
84 these features slightly more comfortable.126 This object wraps Apt features, in a way that makes using and testing
85127=======
86 @param root: The root dir of the Apt configuration files.128class LandscapeAcquireProgress(AcquireProgress):
87 @ivar refetch_package_index: Whether to refetch the package indexes129
88 when reloading the channels, or reuse the existing local130 def _winch(self, *dummy):
89 database.131 """Override trying to get the column count of the buffer.
90 """132
91133 We always send the output to a file, not to a terminal, so the
92 _dpkg_status = "/var/lib/dpkg/status"134 default width (80 columns) is fine for us.
93135
94 def __init__(self, root=None):136 Overriding this method means that we don't have to care about
95 self._root = root137 fcntl.ioctl API differences for different Python versions.
96 self._dpkg_args = []138 """
97 if self._root is not None:139
98 self._ensure_dir_structure()140
99 self._dpkg_args.extend(["--root", self._root])141class LandscapeInstallProgress(InstallProgress):
100 # don't use memonly=True here because of a python-apt bug on Natty when142
101 # sources.list contains invalid lines (LP: #886208)143 dpkg_exited = None
102 self._cache = apt.cache.Cache(rootdir=root)144
103 self._channels_loaded = False145 def wait_child(self):
146 """Override to find out whether dpkg exited or not.
147
148 The C{run()} method returns os.WEXITSTATUS(res) without checking
149 os.WIFEXITED(res) first, so it can signal that everything is ok,
150 even though something causes dpkg not to exit cleanly.
151
152 Save whether dpkg exited cleanly into the C{dpkg_exited}
153 attribute. If dpkg exited cleanly the exit code can be used to
154 determine whether there were any errors. If dpkg didn't exit
155 cleanly it should mean that something went wrong.
156 """
157 res = super(LandscapeInstallProgress, self).wait_child()
158 self.dpkg_exited = os.WIFEXITED(res)
159 return res
160
161
162class AptFacade(object):
163 """Wrapper for tasks using Apt.
164
165 This object wraps Apt features, in a way that makes using and testing
166 these features slightly more comfortable.
167
168 @param root: The root dir of the Apt configuration files.
169 @ivar refetch_package_index: Whether to refetch the package indexes
170 when reloading the channels, or reuse the existing local
171 database.
172 """
173
174 supports_package_holds = True
175 supports_package_locks = False
176 _dpkg_status = "/var/lib/dpkg/status"
177
178 def __init__(self, root=None):
179 self._root = root
180 self._dpkg_args = []
181 if self._root is not None:
182 self._ensure_dir_structure()
183 self._dpkg_args.extend(["--root", self._root])
184 # don't use memonly=True here because of a python-apt bug on Natty when
185 # sources.list contains invalid lines (LP: #886208)
186 self._cache = apt.cache.Cache(rootdir=root)
187 self._channels_loaded = False
188 self._pkg2hash = {}
189 self._hash2pkg = {}
190 self._version_installs = []
191 self._global_upgrade = False
192 self._version_removals = []
193 self.refetch_package_index = False
194
195 def _ensure_dir_structure(self):
196 self._ensure_sub_dir("etc/apt")
197 self._ensure_sub_dir("etc/apt/sources.list.d")
198 self._ensure_sub_dir("var/cache/apt/archives/partial")
199 self._ensure_sub_dir("var/lib/apt/lists/partial")
200 dpkg_dir = self._ensure_sub_dir("var/lib/dpkg")
201 self._ensure_sub_dir("var/lib/dpkg/info")
202 self._ensure_sub_dir("var/lib/dpkg/updates")
203 self._ensure_sub_dir("var/lib/dpkg/triggers")
204 create_file(os.path.join(dpkg_dir, "available"), "")
205 self._dpkg_status = os.path.join(dpkg_dir, "status")
206 if not os.path.exists(self._dpkg_status):
207 create_file(self._dpkg_status, "")
208
209 def _ensure_sub_dir(self, sub_dir):
210 """Ensure that a dir in the Apt root exists."""
211 full_path = os.path.join(self._root, sub_dir)
212 if not os.path.exists(full_path):
213 os.makedirs(full_path)
214 return full_path
215
216 def deinit(self):
217 """This method exists solely to be compatible with C{SmartFacade}."""
218
219 def get_packages(self):
220 """Get all the packages available in the channels."""
221 return self._hash2pkg.itervalues()
222
223 def get_locked_packages(self):
224 """Get all packages in the channels that are locked.
225
226 For Apt, it means all packages that are held.
227 """
228 return [
229 version for version in self.get_packages()
230 if (self.is_package_installed(version)
231 and self._is_package_held(version.package))]
232
233 def get_package_locks(self):
234 """Return all set package locks.
235
236 @return: A C{list} of ternary tuples, contaning the name, relation
237 and version details for each lock currently set on the system.
238
239 XXX: This method isn't implemented yet. It's here to make the
240 transition to Apt in the package reporter easier.
241 """
242 return []
243
244 def get_package_holds(self):
245 """Return the name of all the packages that are on hold."""
246 return [version.package.name for version in self.get_locked_packages()]
247
248 def _set_dpkg_selections(self, selection):
249 """Set the dpkg selection.
250
251 It basically does "echo $selection | dpkg --set-selections".
252 """
253 process = subprocess.Popen(
254 ["dpkg", "--set-selections"] + self._dpkg_args,
255 stdin=subprocess.PIPE)
256 process.communicate(selection)
257
258 def set_package_hold(self, version):
259 """Add a dpkg hold for a package.
260
261 @param version: The version of the package to hold.
262 """
263 self._set_dpkg_selections(version.package.name + " hold")
264
265 def remove_package_hold(self, version):
266 """Removes a dpkg hold for a package.
267
268 @param version: The version of the package to unhold.
269 """
270 if not self._is_package_held(version.package):
271 return
272 self._set_dpkg_selections(version.package.name + " install")
273
274 def reload_channels(self, force_reload_binaries=False):
275 """Reload the channels and update the cache.
276
277 @param force_reload_binaries: Whether to always reload
278 information about the binaries packages that are in the facade's
279 internal repo.
280 """
281 self._cache.open(None)
282 internal_sources_list = self._get_internal_sources_list()
283 if (self.refetch_package_index or
284 (force_reload_binaries and os.path.exists(internal_sources_list))):
285 # Try to update only the internal repos, if the python-apt
286 # version is new enough to accept a sources_list parameter.
287 new_apt_args = {}
288 if force_reload_binaries and not self.refetch_package_index:
289 new_apt_args["sources_list"] = internal_sources_list
290 try:
291 try:
292 self._cache.update(**new_apt_args)
293 except TypeError:
294 self._cache.update()
295 except apt.cache.FetchFailedException:
296 raise ChannelError(
297 "Apt failed to reload channels (%r)" % (
298 self.get_channels()))
299 self._cache.open(None)
300
301 self._pkg2hash.clear()
302 self._hash2pkg.clear()
303 for package in self._cache:
304 if not self._is_main_architecture(package):
305 continue
306 for version in package.versions:
307 hash = self.get_package_skeleton(
308 version, with_info=False).get_hash()
309 # Use a tuple including the package, since the Version
310 # objects of two different packages can have the same
311 # hash.
312 self._pkg2hash[(package, version)] = hash
313 self._hash2pkg[hash] = version
314 self._channels_loaded = True
315
316 def ensure_channels_reloaded(self):
317 """Reload the channels if they haven't been reloaded yet."""
318 if self._channels_loaded:
319 return
320 self.reload_channels()
321
322 def _get_internal_sources_list(self):
323 """Return the path to the source.list file for the facade channels."""
324 sources_dir = apt_pkg.config.find_dir("Dir::Etc::sourceparts")
325 return os.path.join(sources_dir, "_landscape-internal-facade.list")
326
327 def add_channel_apt_deb(self, url, codename, components=None):
328 """Add a deb URL which points to a repository.
329
330 @param url: The base URL of the repository.
331 @param codename: The dist in the repository.
332 @param components: The components to be included.
333 """
334 sources_file_path = self._get_internal_sources_list()
335 sources_line = "deb %s %s" % (url, codename)
336 if components:
337 sources_line += " %s" % " ".join(components)
338 if os.path.exists(sources_file_path):
339 current_content = read_file(sources_file_path).split("\n")
340 if sources_line in current_content:
341 return
342 sources_line += "\n"
343 append_file(sources_file_path, sources_line)
344
345 def add_channel_deb_dir(self, path):
346 """Add a directory with packages as a channel.
347
348 @param path: The path to the directory containing the packages.
349
350 A Packages file is created in the directory with information
351 about the deb files.
352 """
353 self._create_packages_file(path)
354 self.add_channel_apt_deb("file://%s" % path, "./", None)
355
356 def clear_channels(self):
357 """Clear the channels that have been added through the facade.
358
359 Channels that weren't added through the facade (i.e.
360 /etc/apt/sources.list and /etc/apt/sources.list.d) won't be
361 removed.
362 """
363 sources_file_path = self._get_internal_sources_list()
364 if os.path.exists(sources_file_path):
365 os.remove(sources_file_path)
366
367 def _create_packages_file(self, deb_dir):
368 """Create a Packages file in a directory with debs."""
369 packages_contents = "\n".join(
370 self.get_package_stanza(os.path.join(deb_dir, filename))
371 for filename in sorted(os.listdir(deb_dir)))
372 create_file(os.path.join(deb_dir, "Packages"), packages_contents)
373
374 def get_channels(self):
375 """Return a list of channels configured.
376
377 A channel is a deb line in sources.list or sources.list.d. It's
378 represented by a dict with baseurl, distribution, components,
379 and type keys.
380 """
381 sources_list = SourcesList()
382 return [{"baseurl": entry.uri, "distribution": entry.dist,
383 "components": " ".join(entry.comps), "type": entry.type}
384 for entry in sources_list if not entry.disabled]
385
386 def reset_channels(self):
387 """Remove all the configured channels."""
388 sources_list = SourcesList()
389 for entry in sources_list:
390 entry.set_enabled(False)
391 sources_list.save()
392
393 def get_package_stanza(self, deb_path):
394 """Return a stanza for the package to be included in a Packages file.
395
396 @param deb_path: The path to the deb package.
397 """
398 deb_file = open(deb_path)
399 deb = apt_inst.DebFile(deb_file)
400 control = deb.control.extractdata("control")
401 deb_file.close()
402 filename = os.path.basename(deb_path)
403 size = os.path.getsize(deb_path)
404 contents = read_file(deb_path)
405 md5 = hashlib.md5(contents).hexdigest()
406 sha1 = hashlib.sha1(contents).hexdigest()
407 sha256 = hashlib.sha256(contents).hexdigest()
408 # Use rewrite_section to ensure that the field order is correct.
409 return apt_pkg.rewrite_section(
410 apt_pkg.TagSection(control), apt_pkg.REWRITE_PACKAGE_ORDER,
411 [("Filename", filename), ("Size", str(size)),
412 ("MD5sum", md5), ("SHA1", sha1), ("SHA256", sha256)])
413
414 def get_arch(self):
415 """Return the architecture APT is configured to use."""
416 return apt_pkg.config.get("APT::Architecture")
417
418 def set_arch(self, architecture):
419 """Set the architecture that APT should use.
420
421 Setting multiple architectures isn't supported.
422 """
423 if architecture is None:
424 architecture = ""
425 # From oneiric and onwards Architectures is used to set which
426 # architectures can be installed, in case multiple architectures
427 # are supported. We force it to be single architecture, until we
428 # have a plan for supporting multiple architectures.
429 apt_pkg.config.clear("APT::Architectures")
430 apt_pkg.config.set("APT::Architectures::", architecture)
431 result = apt_pkg.config.set("APT::Architecture", architecture)
432 # Reload the cache, otherwise architecture change isn't reflected in
433 # package list
434 self._cache.open(None)
435 return result
436
437 def get_package_skeleton(self, pkg, with_info=True):
438 """Return a skeleton for the provided package.
439
440 The skeleton represents the basic structure of the package.
441
442 @param pkg: Package to build skeleton from.
443 @param with_info: If True, the skeleton will include information
444 useful for sending data to the server. Such information isn't
445 necessary if the skeleton will be used to build a hash.
446
447 @return: a L{PackageSkeleton} object.
448 """
449 return build_skeleton_apt(pkg, with_info=with_info, with_unicode=True)
450
451 def get_package_hash(self, version):
452 """Return a hash from the given package.
453
454 @param version: an L{apt.package.Version} object.
455 """
456 return self._pkg2hash.get((version.package, version))
457
458 def get_package_hashes(self):
459 """Get the hashes of all the packages available in the channels."""
460 return self._pkg2hash.values()
461
462 def get_package_by_hash(self, hash):
463 """Get the package having the provided hash.
464
465 @param hash: The hash the package should have.
466
467 @return: The L{apt.package.Package} that has the given hash.
468 """
469 return self._hash2pkg.get(hash)
470
471 def is_package_installed(self, version):
472 """Is the package version installed?"""
473 return version == version.package.installed
474
475 def is_package_available(self, version):
476 """Is the package available for installation?"""
477 return version.downloadable
478
479 def is_package_upgrade(self, version):
480 """Is the package an upgrade for another installed package?"""
481 if not version.package.is_upgradable or not version.package.installed:
482 return False
483 return version > version.package.installed
484
485 def _is_main_architecture(self, package):
486 """Is the package for the facade's main architecture?"""
487 # package.name includes the architecture, if it's for a foreign
488 # architectures. package.shortname never includes the
489 # architecture. package.shortname doesn't exist on releases that
490 # don't support multi-arch, though.
491 if not hasattr(package, "shortname"):
492 return True
493 return package.name == package.shortname
494
495 def _is_package_held(self, package):
496 """Is the package marked as held?"""
497 return package._pkg.selected_state == apt_pkg.SELSTATE_HOLD
498
499 def get_packages_by_name(self, name):
500 """Get all available packages matching the provided name.
501
502 @param name: The name the returned packages should have.
503 """
504 return [
505 version for version in self.get_packages()
506 if version.package.name == name]
507
508 def _get_broken_packages(self):
509 """Return the packages that are in a broken state."""
510 return set(
511 version.package for version in self.get_packages()
512 if version.package.is_inst_broken)
513
514 def _get_changed_versions(self, package):
515 """Return the versions that will be changed for the package.
516
517 Apt gives us that a package is going to be changed and have
518 variables set on the package to indicate what will change. We
519 need to convert that into a list of versions that will be either
520 installed or removed, which is what the server expects to get.
521 """
522 if package.marked_install:
523 return [package.candidate]
524 if package.marked_upgrade or package.marked_downgrade:
525 return [package.installed, package.candidate]
526 if package.marked_delete:
527 return [package.installed]
528 return None
529
530 def _check_changes(self, requested_changes):
531 """Check that the changes Apt will do have all been requested.
532
533 @raises DependencyError: If some change hasn't been explicitly
534 requested.
535 @return: C{True} if all the changes that Apt will perform have
536 been requested.
537 """
538 # Build tuples of (package, version) so that we can do
539 # comparison checks. Same versions of different packages compare
540 # as being the same, so we need to include the package as well.
541 all_changes = [
542 (version.package, version) for version in requested_changes]
543 versions_to_be_changed = set()
544 for package in self._cache.get_changes():
545 if not self._is_main_architecture(package):
546 continue
547 versions = self._get_changed_versions(package)
548 versions_to_be_changed.update(
549 (package, version) for version in versions)
550 dependencies = versions_to_be_changed.difference(all_changes)
551 if dependencies:
552 raise DependencyError(
553 [version for package, version in dependencies])
554 return len(versions_to_be_changed) > 0
555
556 def _get_unmet_relation_info(self, dep_relation):
557 """Return a string representation of a specific dependency relation."""
558 info = dep_relation.target_pkg.name
559 if dep_relation.target_ver:
560 info += " (%s %s)" % (
561 dep_relation.comp_type, dep_relation.target_ver)
562 reason = " but is not installable"
563 if dep_relation.target_pkg.name in self._cache:
564 dep_package = self._cache[dep_relation.target_pkg.name]
565 if dep_package.installed or dep_package.marked_install:
566 version = dep_package.candidate.version
567 if dep_package not in self._cache.get_changes():
568 version = dep_package.installed.version
569 reason = " but %s is to be installed" % version
570 info += reason
571 return info
572
573 def _is_dependency_satisfied(self, dependency, dep_type):
574 """Return whether a dependency is satisfied.
575
576 For positive dependencies (Pre-Depends, Depends) it means that
577 one of its targets is going to be installed. For negative
578 dependencies (Conflicts, Breaks), it means that none of its
579 targets are going to be installed.
580 """
581 is_positive = dep_type not in ["Breaks", "Conflicts"]
582 depcache = self._cache._depcache
583 for or_dep in dependency:
584 for target in or_dep.all_targets():
585 package = target.parent_pkg
586 if ((package.current_state == apt_pkg.CURSTATE_INSTALLED
587 or depcache.marked_install(package))
588 and not depcache.marked_delete(package)):
589 return is_positive
590 return not is_positive
591
592 def _get_unmet_dependency_info(self):
593 """Get information about unmet dependencies in the cache state.
594
595 Go through all the broken packages and say which dependencies
596 haven't been satisfied.
597
598 @return: A string with dependency information like what you get
599 from apt-get.
600 """
601
602 broken_packages = self._get_broken_packages()
603 if not broken_packages:
604 return ""
605 all_info = ["The following packages have unmet dependencies:"]
606 for package in sorted(broken_packages, key=attrgetter("name")):
607 for dep_type in ["PreDepends", "Depends", "Conflicts", "Breaks"]:
608 dependencies = package.candidate._cand.depends_list.get(
609 dep_type, [])
610 for dependency in dependencies:
611 if self._is_dependency_satisfied(dependency, dep_type):
612 continue
613 relation_infos = []
614 for dep_relation in dependency:
615 relation_infos.append(
616 self._get_unmet_relation_info(dep_relation))
617 info = " %s: %s: " % (package.name, dep_type)
618 or_divider = " or\n" + " " * len(info)
619 all_info.append(info + or_divider.join(relation_infos))
620 return "\n".join(all_info)
621
622 def perform_changes(self):
623 """Perform the pending package operations."""
624 # Try to enforce non-interactivity
625 os.environ["DEBIAN_FRONTEND"] = "noninteractive"
626 os.environ["APT_LISTCHANGES_FRONTEND"] = "none"
627 os.environ["APT_LISTBUGS_FRONTEND"] = "none"
628 # dpkg will fail if no path is set.
629 if "PATH" not in os.environ:
630 os.environ["PATH"] = UBUNTU_PATH
631 apt_pkg.config.clear("DPkg::options")
632 apt_pkg.config.set("DPkg::options::", "--force-confold")
633
634 held_package_names = set()
635 package_installs = set(
636 version.package for version in self._version_installs)
637 package_upgrades = set(
638 version.package for version in self._version_removals
639 if version.package in package_installs)
640 version_changes = self._version_installs[:]
641 version_changes.extend(self._version_removals)
642 if not version_changes and not self._global_upgrade:
643 return None
644 fixer = apt_pkg.ProblemResolver(self._cache._depcache)
645 already_broken_packages = self._get_broken_packages()
646 for version in self._version_installs:
647 # Set the candidate version, so that the version we want to
648 # install actually is the one getting installed.
649 version.package.candidate = version
650 version.package.mark_install(auto_fix=False)
651 # If we need to resolve dependencies, try avoiding having
652 # the package we asked to be installed from being removed.
653 # (This is what would have been done if auto_fix would have
654 # been True.
655 fixer.clear(version.package._pkg)
656 fixer.protect(version.package._pkg)
657 if self._global_upgrade:
658 self._cache.upgrade(dist_upgrade=True)
659 for version in self._version_removals:
660 if self._is_package_held(version.package):
661 held_package_names.add(version.package.name)
662 if version.package in package_upgrades:
663 # The server requests the old version to be removed for
664 # upgrades, since Smart worked that way. For Apt we have
665 # to take care not to mark upgraded packages for # removal.
666 continue
667 version.package.mark_delete(auto_fix=False)
668 # Configure the resolver in the same way
669 # mark_delete(auto_fix=True) would have done.
670 fixer.clear(version.package._pkg)
671 fixer.protect(version.package._pkg)
672 fixer.remove(version.package._pkg)
673 fixer.install_protect()
674
675 if held_package_names:
676 raise TransactionError(
677 "Can't perform the changes, since the following packages" +
678 " are held: %s" % ", ".join(sorted(held_package_names)))
679
680 now_broken_packages = self._get_broken_packages()
681 if now_broken_packages != already_broken_packages:
682 try:
683 fixer.resolve(True)
684 except SystemError, error:
685 raise TransactionError(
686 error.args[0] + "\n" + self._get_unmet_dependency_info())
687 if not self._check_changes(version_changes):
688 return None
689 fetch_output = StringIO()
690 # Redirect stdout and stderr to a file. We need to work with the
691 # file descriptors, rather than sys.stdout/stderr, since dpkg is
692 # run in a subprocess.
693 fd, install_output_path = tempfile.mkstemp()
694 old_stdout = os.dup(1)
695 old_stderr = os.dup(2)
696 os.dup2(fd, 1)
697 os.dup2(fd, 2)
698 install_progress = LandscapeInstallProgress()
699 try:
700 self._cache.commit(
701 fetch_progress=LandscapeAcquireProgress(fetch_output),
702 install_progress=install_progress)
703 if not install_progress.dpkg_exited:
704 raise SystemError("dpkg didn't exit cleanly.")
705 except SystemError, error:
706 result_text = (
707 fetch_output.getvalue() + read_file(install_output_path))
708 raise TransactionError(
709 error.args[0] + "\n\nPackage operation log:\n" + result_text)
710 else:
711 result_text = (
712 fetch_output.getvalue() + read_file(install_output_path))
713 finally:
714 # Restore stdout and stderr.
715 os.dup2(old_stdout, 1)
716 os.dup2(old_stderr, 2)
717 os.remove(install_output_path)
718 return result_text
719
720 def reset_marks(self):
721 """Clear the pending package operations."""
722 del self._version_installs[:]
723 del self._version_removals[:]
724 self._global_upgrade = False
725 self._cache.clear()
726
727 def mark_install(self, version):
728 """Mark the package for installation."""
729 self._version_installs.append(version)
730
731 def mark_global_upgrade(self):
732 """Upgrade all installed packages."""
733 self._global_upgrade = True
734
735 def mark_remove(self, version):
736 """Mark the package for removal."""
737 self._version_removals.append(version)
738
739
740class SmartFacade(object):
741 """Wrapper for tasks using Smart.
742
743 This object wraps Smart features, in a way that makes using and testing
744>>>>>>> MERGE-SOURCE
745 these features slightly more comfortable.
746<<<<<<< TREE
747
748 @param root: The root dir of the Apt configuration files.
749 @ivar refetch_package_index: Whether to refetch the package indexes
750 when reloading the channels, or reuse the existing local
751 database.
752=======
753
754 @param smart_init_kwargs: A dictionary that can be used to pass specific
755 keyword parameters to to L{smart.init}.
756>>>>>>> MERGE-SOURCE
757 """
758
759<<<<<<< TREE
760 _dpkg_status = "/var/lib/dpkg/status"
761
762 def __init__(self, root=None):
763 self._root = root
764 self._dpkg_args = []
765 if self._root is not None:
766 self._ensure_dir_structure()
767 self._dpkg_args.extend(["--root", self._root])
768 # don't use memonly=True here because of a python-apt bug on Natty when
769 # sources.list contains invalid lines (LP: #886208)
770 self._cache = apt.cache.Cache(rootdir=root)
771 self._channels_loaded = False
772=======
773 _deb_package_type = None
774 supports_package_holds = False
775 supports_package_locks = True
776
777 def __init__(self, smart_init_kwargs={}, sysconf_args=None):
778 if not has_smart:
779 raise RuntimeError(
780 "Smart needs to be installed if SmartFacade is used.")
781 self._smart_init_kwargs = smart_init_kwargs.copy()
782 self._smart_init_kwargs.setdefault("interface", "landscape")
783 self._sysconfig_args = sysconf_args or {}
784 self._reset()
785
786 def _reset(self):
787 # This attribute is initialized lazily in the _get_ctrl() method.
788 self._ctrl = None
789>>>>>>> MERGE-SOURCE
104 self._pkg2hash = {}790 self._pkg2hash = {}
105 self._hash2pkg = {}791 self._hash2pkg = {}
106 self._version_installs = []792 self._version_installs = []
@@ -224,6 +910,7 @@
224 return910 return
225 self.reload_channels()911 self.reload_channels()
226912
913<<<<<<< TREE
227 def _get_internal_sources_list(self):914 def _get_internal_sources_list(self):
228 """Return the path to the source.list file for the facade channels."""915 """Return the path to the source.list file for the facade channels."""
229 sources_dir = apt_pkg.config.find_dir("Dir::Etc::sourceparts")916 sources_dir = apt_pkg.config.find_dir("Dir::Etc::sourceparts")
@@ -338,6 +1025,39 @@
338 # package list1025 # package list
339 self._cache.open(None)1026 self._cache.open(None)
340 return result1027 return result
1028=======
1029 def reload_channels(self, force_reload_binaries=False):
1030 """
1031 Reload Smart channels, getting all the cache (packages) in memory.
1032
1033 @raise: L{ChannelError} if Smart fails to reload the channels.
1034 """
1035 ctrl = self._get_ctrl()
1036
1037 try:
1038 reload_result = ctrl.reloadChannels(caching=self._caching)
1039 except smart.Error:
1040 failed = True
1041 else:
1042 # Raise an error only if we are trying to download remote lists
1043 failed = not reload_result and self._caching == NEVER
1044 if failed:
1045 raise ChannelError("Smart failed to reload channels (%s)"
1046 % smart.sysconf.get("channels"))
1047
1048 self._hash2pkg.clear()
1049 self._pkg2hash.clear()
1050
1051 for pkg in self.get_packages():
1052 hash = self.get_package_skeleton(pkg, False).get_hash()
1053 self._hash2pkg[hash] = pkg
1054 self._pkg2hash[pkg] = hash
1055
1056 self.channels_reloaded()
1057
1058 def channels_reloaded(self):
1059 """Hook called after Smart channels are reloaded."""
1060>>>>>>> MERGE-SOURCE
3411061
342 def get_package_skeleton(self, pkg, with_info=True):1062 def get_package_skeleton(self, pkg, with_info=True):
343 """Return a skeleton for the provided package.1063 """Return a skeleton for the provided package.
@@ -710,7 +1430,14 @@
710 if len(results) > 0:1430 if len(results) > 0:
711 return " ".join(results)1431 return " ".join(results)
7121432
1433 def mark_global_upgrade(self):
1434 """Upgrade all installed packages."""
1435 for package in self.get_packages():
1436 if self.is_package_installed(package):
1437 self.mark_upgrade(package)
1438
713 def reset_marks(self):1439 def reset_marks(self):
1440<<<<<<< TREE
714 """Clear the pending package operations."""1441 """Clear the pending package operations."""
715 del self._version_installs[:]1442 del self._version_installs[:]
716 del self._version_removals[:]1443 del self._version_removals[:]
@@ -738,3 +1465,231 @@
738 def mark_remove_hold(self, version):1465 def mark_remove_hold(self, version):
739 """Mark the package to have its hold removed."""1466 """Mark the package to have its hold removed."""
740 self._version_hold_removals.append(version)1467 self._version_hold_removals.append(version)
1468=======
1469 self._marks.clear()
1470
1471 def perform_changes(self):
1472 ctrl = self._get_ctrl()
1473 cache = ctrl.getCache()
1474
1475 transaction = Transaction(cache)
1476
1477 policy = PolicyInstall
1478
1479 only_remove = True
1480 for pkg, oper in self._marks.items():
1481 if oper == UPGRADE:
1482 policy = PolicyUpgrade
1483 if oper != REMOVE:
1484 only_remove = False
1485 transaction.enqueue(pkg, oper)
1486
1487 if only_remove:
1488 policy = PolicyRemove
1489
1490 transaction.setPolicy(policy)
1491
1492 try:
1493 transaction.run()
1494 except Failed, e:
1495 raise TransactionError(e.args[0])
1496 changeset = transaction.getChangeSet()
1497
1498 if not changeset:
1499 return None # Nothing to do.
1500
1501 missing = []
1502 for pkg, op in changeset.items():
1503 if self._marks.get(pkg) != op:
1504 missing.append(pkg)
1505 if missing:
1506 raise DependencyError(missing)
1507
1508 try:
1509 self._ctrl.commitChangeSet(changeset)
1510 except smart.Error, e:
1511 raise TransactionError(e.args[0])
1512
1513 output = smart.iface.get_output_for_landscape()
1514 failed = smart.iface.has_failed_for_landscape()
1515
1516 smart.iface.reset_for_landscape()
1517
1518 if failed:
1519 raise SmartError(output)
1520 return output
1521
1522 def reload_cache(self):
1523 cache = self._get_ctrl().getCache()
1524 cache.reset()
1525 cache.load()
1526
1527 def get_arch(self):
1528 """
1529 Get the host dpkg architecture.
1530 """
1531 self._get_ctrl()
1532 from smart.backends.deb.loader import DEBARCH
1533 return DEBARCH
1534
1535 def set_arch(self, arch):
1536 """
1537 Set the host dpkg architecture.
1538
1539 To take effect it must be called before L{reload_channels}.
1540
1541 @param arch: the dpkg architecture to use (e.g. C{"i386"})
1542 """
1543 self._get_ctrl()
1544 smart.sysconf.set("deb-arch", arch)
1545
1546 # XXX workaround Smart setting DEBARCH statically in the
1547 # smart.backends.deb.base module
1548 import smart.backends.deb.loader as loader
1549 loader.DEBARCH = arch
1550
1551 def set_caching(self, mode):
1552 """
1553 Set Smart's caching mode.
1554
1555 @param mode: The caching mode to pass to Smart's C{reloadChannels}
1556 when calling L{reload_channels} (e.g C{smart.const.NEVER} or
1557 C{smart.const.ALWAYS}).
1558 """
1559 self._caching = mode
1560
1561 def reset_channels(self):
1562 """Remove all configured Smart channels."""
1563 self._get_ctrl()
1564 smart.sysconf.set("channels", {}, soft=True)
1565
1566 def add_channel(self, alias, channel):
1567 """
1568 Add a Smart channel.
1569
1570 This method can be called more than once to set multiple channels.
1571 To take effect it must be called before L{reload_channels}.
1572
1573 @param alias: A string identifying the channel to be added.
1574 @param channel: A C{dict} holding information about the channel to
1575 add (see the Smart API for details about valid keys and values).
1576 """
1577 channels = self.get_channels()
1578 channels.update({alias: channel})
1579 smart.sysconf.set("channels", channels, soft=True)
1580
1581 def add_channel_apt_deb(self, url, codename, components):
1582 """Add a Smart channel of type C{"apt-deb"}.
1583
1584 @see: L{add_channel}
1585 """
1586 alias = codename
1587 channel = {"baseurl": url, "distribution": codename,
1588 "components": components, "type": "apt-deb"}
1589 self.add_channel(alias, channel)
1590
1591 def add_channel_deb_dir(self, path):
1592 """Add a Smart channel of type C{"deb-dir"}.
1593
1594 @see: L{add_channel}
1595 """
1596 alias = path
1597 channel = {"path": path, "type": "deb-dir"}
1598 self.add_channel(alias, channel)
1599
1600 def clear_channels(self):
1601 """Clear channels.
1602
1603 This method exists to be compatible with AptFacade. Smart
1604 doesn't need to clear its channels.
1605 """
1606
1607 def get_channels(self):
1608 """
1609 @return: A C{dict} of all configured channels.
1610 """
1611 self._get_ctrl()
1612 return smart.sysconf.get("channels")
1613
1614 def get_package_locks(self):
1615 """Return all set package locks.
1616
1617 @return: A C{list} of ternary tuples, contaning the name, relation
1618 and version details for each lock currently set on the system.
1619 """
1620 self._get_ctrl()
1621 locks = []
1622 locks_by_name = smart.pkgconf.getFlagTargets("lock")
1623 for name in locks_by_name:
1624 for condition in locks_by_name[name]:
1625 relation = condition[0] or ""
1626 version = condition[1] or ""
1627 locks.append((name, relation, version))
1628 return locks
1629
1630 def _validate_lock_condition(self, relation, version):
1631 if relation and not version:
1632 raise RuntimeError("Package lock version not provided")
1633 if version and not relation:
1634 raise RuntimeError("Package lock relation not provided")
1635
1636 def set_package_lock(self, name, relation=None, version=None):
1637 """Set a new package lock.
1638
1639 Any package matching the given name and possibly the given version
1640 condition will be locked.
1641
1642 @param name: The name a package must match in order to be locked.
1643 @param relation: Optionally, the relation of the version condition the
1644 package must satisfy in order to be considered as locked.
1645 @param version: Optionally, the version associated with C{relation}.
1646
1647 @note: If used at all, the C{relation} and C{version} parameter must be
1648 both provided.
1649 """
1650 self._validate_lock_condition(relation, version)
1651 self._get_ctrl()
1652 smart.pkgconf.setFlag("lock", name, relation, version)
1653
1654 def remove_package_lock(self, name, relation=None, version=None):
1655 """Remove a package lock."""
1656 self._validate_lock_condition(relation, version)
1657 self._get_ctrl()
1658 smart.pkgconf.clearFlag("lock", name=name, relation=relation,
1659 version=version)
1660
1661 def save_config(self):
1662 """Flush the current smart configuration to disk."""
1663 control = self._get_ctrl()
1664 control.saveSysConf()
1665
1666 def is_package_installed(self, package):
1667 """Is the package installed?"""
1668 return package.installed
1669
1670 def is_package_available(self, package):
1671 """Is the package available for installation?"""
1672 for loader in package.loaders:
1673 # Is the package also in a non-installed
1674 # loader? IOW, "available".
1675 if not loader.getInstalled():
1676 return True
1677 return False
1678
1679 def is_package_upgrade(self, package):
1680 """Is the package an upgrade for another installed package?"""
1681 is_upgrade = False
1682 for upgrade in package.upgrades:
1683 for provides in upgrade.providedby:
1684 for provides_package in provides.packages:
1685 if provides_package.installed:
1686 is_upgrade = True
1687 break
1688 else:
1689 continue
1690 break
1691 else:
1692 continue
1693 break
1694 return is_upgrade
1695>>>>>>> MERGE-SOURCE
7411696
=== added file 'landscape/package/interface.py.OTHER'
--- landscape/package/interface.py.OTHER 1970-01-01 00:00:00 +0000
+++ landscape/package/interface.py.OTHER 2014-11-19 18:22:39 +0000
@@ -0,0 +1,84 @@
1import logging
2import types
3import sys
4
5try:
6 import smart.interfaces
7 from smart.interface import Interface
8 from smart.const import ERROR, WARNING, INFO, DEBUG
9except ImportError:
10 # Smart is optional if AptFacade is being used.
11 Interface = object
12
13
14class LandscapeInterface(Interface):
15
16 __output = ""
17 __failed = False
18
19 def reset_for_landscape(self):
20 """Reset output and failed flag."""
21 self.__failed = False
22 self.__output = u""
23
24 def get_output_for_landscape(self):
25 """showOutput() is cached, and returned by this method."""
26 return self.__output
27
28 def has_failed_for_landscape(self):
29 """Return true if any error() messages were logged."""
30 return self.__failed
31
32 def error(self, msg):
33 self.__failed = True
34 # Calling these logging.* functions here instead of message()
35 # below will output the message or not depending on the debug
36 # level set in landscape-client, rather than the one set in
37 # Smart's configuration.
38 logging.error("[Smart] %s", msg)
39 super(LandscapeInterface, self).error(msg)
40
41 def warning(self, msg):
42 logging.warning("[Smart] %s", msg)
43 super(LandscapeInterface, self).warning(msg)
44
45 def info(self, msg):
46 logging.info("[Smart] %s", msg)
47 super(LandscapeInterface, self).info(msg)
48
49 def debug(self, msg):
50 logging.debug("[Smart] %s", msg)
51 super(LandscapeInterface, self).debug(msg)
52
53 def message(self, level, msg):
54 prefix = {ERROR: "ERROR", WARNING: "WARNING",
55 INFO: "INFO", DEBUG: "DEBUG"}.get(level)
56 self.showOutput("%s: %s\n" % (prefix, msg))
57
58 def showOutput(self, output):
59 if not isinstance(output, unicode):
60 try:
61 output = output.decode("utf-8")
62 except UnicodeDecodeError:
63 output = output.decode("ascii", "replace")
64 self.__output += output
65
66
67class LandscapeInterfaceModule(types.ModuleType):
68
69 def __init__(self):
70 super(LandscapeInterfaceModule, self).__init__("landscape")
71
72 def create(self, ctrl, command=None, argv=None):
73 return LandscapeInterface(ctrl)
74
75
76def install_landscape_interface():
77 if "smart.interfaces.landscape" not in sys.modules:
78 # Plug the interface in a place Smart will recognize.
79 smart.interfaces.landscape = LandscapeInterfaceModule()
80 sys.modules["smart.interfaces.landscape"] = smart.interfaces.landscape
81
82
83def uninstall_landscape_interface():
84 sys.modules.pop("smart.interfaces.landscape", None)
085
=== modified file 'landscape/package/reporter.py'
--- landscape/package/reporter.py 2012-06-19 15:12:07 +0000
+++ landscape/package/reporter.py 2014-11-19 18:22:39 +0000
@@ -12,6 +12,7 @@
12from landscape.lib.fs import touch_file12from landscape.lib.fs import touch_file
13from landscape.lib import bpickle13from landscape.lib import bpickle
1414
15from landscape.package.facade import AptFacade
15from landscape.package.taskhandler import (16from landscape.package.taskhandler import (
16 PackageTaskHandlerConfiguration, PackageTaskHandler, run_task_handler)17 PackageTaskHandlerConfiguration, PackageTaskHandler, run_task_handler)
17from landscape.package.store import UnknownHashIDRequest, FakePackageStore18from landscape.package.store import UnknownHashIDRequest, FakePackageStore
@@ -47,15 +48,31 @@
4748
48 queue_name = "reporter"49 queue_name = "reporter"
4950
51<<<<<<< TREE
50 apt_update_interval = 6052 apt_update_interval = 60
51 apt_update_filename = "/usr/lib/landscape/apt-update"53 apt_update_filename = "/usr/lib/landscape/apt-update"
54=======
55 smart_update_interval = 60
56 smart_update_filename = "/usr/lib/landscape/smart-update"
57 apt_update_filename = "/usr/lib/landscape/apt-update"
58>>>>>>> MERGE-SOURCE
52 sources_list_filename = "/etc/apt/sources.list"59 sources_list_filename = "/etc/apt/sources.list"
53 sources_list_directory = "/etc/apt/sources.list.d"60 sources_list_directory = "/etc/apt/sources.list.d"
5461
55 def run(self):62 def run(self):
56 result = Deferred()63 result = Deferred()
5764
65<<<<<<< TREE
58 result.addCallback(lambda x: self.run_apt_update())66 result.addCallback(lambda x: self.run_apt_update())
67=======
68 if isinstance(self._facade, AptFacade):
69 # Update APT cache if APT facade is enabled.
70 result.addCallback(lambda x: self.run_apt_update())
71 else:
72 # Run smart-update before anything else, to make sure that
73 # the SmartFacade will load freshly updated channels
74 result.addCallback(lambda x: self.run_smart_update())
75>>>>>>> MERGE-SOURCE
5976
60 # If the appropriate hash=>id db is not there, fetch it77 # If the appropriate hash=>id db is not there, fetch it
61 result.addCallback(lambda x: self.fetch_hash_id_db())78 result.addCallback(lambda x: self.fetch_hash_id_db())
@@ -219,6 +236,86 @@
219 return result.addCallback(callback)236 return result.addCallback(callback)
220237
221 else:238 else:
239<<<<<<< TREE
240 logging.debug("'%s' didn't run, update interval has not passed" %
241 self.apt_update_filename)
242 return succeed(("", "", 0))
243=======
244 args = ("--after", str(self.smart_update_interval))
245 result = spawn_process(self.smart_update_filename, args=args)
246
247 def callback((out, err, code)):
248 # smart-update --after N will exit with error code 1 when it
249 # doesn't actually run the update code because to enough time
250 # has passed yet, but we don't actually consider it a failure.
251 smart_failed = False
252 if code != 0 and code != 1:
253 smart_failed = True
254 if code == 1 and err.strip() != "":
255 smart_failed = True
256 if smart_failed:
257 logging.warning("'%s' exited with status %d (%s)" % (
258 self.smart_update_filename, code, err))
259 logging.debug("'%s' exited with status %d (out='%s', err='%s'" % (
260 self.smart_update_filename, code, out, err))
261 touch_file(self._config.update_stamp_filename)
262 if not smart_failed and not self._facade.get_channels():
263 code = 1
264 err = "There are no APT sources configured in %s or %s." % (
265 self.sources_list_filename, self.sources_list_directory)
266 deferred = self._broker.call_if_accepted(
267 "package-reporter-result", self.send_result, code, err)
268 deferred.addCallback(lambda ignore: (out, err, code))
269 return deferred
270
271 result.addCallback(callback)
272 return result
273>>>>>>> MERGE-SOURCE
274
275 def _apt_update_timeout_expired(self, interval):
276 """Check if the apt-update timeout has passed."""
277 stamp = self._config.update_stamp_filename
278
279 if not os.path.exists(stamp):
280 return True
281 # check stamp file mtime
282 last_update = os.stat(stamp)[8]
283 now = int(time.time())
284 return (last_update + interval * 60) < now
285
286 def run_apt_update(self):
287 """Run apt-update and log a warning in case of non-zero exit code.
288
289 @return: a deferred returning (out, err, code)
290 """
291 if (self._config.force_smart_update or self._apt_sources_have_changed()
292 or self._apt_update_timeout_expired(self.smart_update_interval)):
293
294 result = spawn_process(self.apt_update_filename)
295
296 def callback((out, err, code)):
297 touch_file(self._config.update_stamp_filename)
298 logging.debug(
299 "'%s' exited with status %d (out='%s', err='%s')" % (
300 self.apt_update_filename, code, out, err))
301
302 if code != 0:
303 logging.warning("'%s' exited with status %d (%s)" % (
304 self.apt_update_filename, code, err))
305 elif not self._facade.get_channels():
306 code = 1
307 err = ("There are no APT sources configured in %s or %s." %
308 (self.sources_list_filename,
309 self.sources_list_directory))
310
311 deferred = self._broker.call_if_accepted(
312 "package-reporter-result", self.send_result, code, err)
313 deferred.addCallback(lambda ignore: (out, err, code))
314 return deferred
315
316 return result.addCallback(callback)
317
318 else:
222 logging.debug("'%s' didn't run, update interval has not passed" %319 logging.debug("'%s' didn't run, update interval has not passed" %
223 self.apt_update_filename)320 self.apt_update_filename)
224 return succeed(("", "", 0))321 return succeed(("", "", 0))
@@ -559,6 +656,116 @@
559656
560 return result657 return result
561658
659<<<<<<< TREE
660
661class FakeGlobalReporter(PackageReporter):
662 """
663 A standard reporter, which additionally stores messages sent into its
664 package store.
665 """
666
667 package_store_class = FakePackageStore
668
669 def send_message(self, message):
670 self._store.save_message(message)
671 return super(FakeGlobalReporter, self).send_message(message)
672
673
674class FakeReporter(PackageReporter):
675 """
676 A fake reporter which only sends messages previously stored by a
677 L{FakeGlobalReporter}.
678 """
679
680 package_store_class = FakePackageStore
681 global_store_filename = None
682
683 def run(self):
684 result = succeed(None)
685
686 # If the appropriate hash=>id db is not there, fetch it
687 result.addCallback(lambda x: self.fetch_hash_id_db())
688
689 result.addCallback(lambda x: self._store.clear_tasks())
690
691 # Finally, verify if we have anything new to send to the server.
692 result.addCallback(lambda x: self.send_pending_messages())
693=======
694 def detect_package_locks_changes(self):
695 """Detect changes in known package locks.
696
697 This method will verify if there are package locks that:
698
699 - are now set, and were not;
700 - were previously set but are not anymore;
701
702 In all cases, the server is notified of the new situation
703 with a "packages" message.
704
705 @return: A deferred resulting in C{True} if package lock changes were
706 detected with respect to the previous run, or C{False} otherwise.
707 """
708 old_package_locks = set(self._store.get_package_locks())
709 current_package_locks = set(self._facade.get_package_locks())
710
711 set_package_locks = current_package_locks - old_package_locks
712 unset_package_locks = old_package_locks - current_package_locks
713
714 message = {}
715 if set_package_locks:
716 message["created"] = sorted(set_package_locks)
717 if unset_package_locks:
718 message["deleted"] = sorted(unset_package_locks)
719
720 if not message:
721 return succeed(False)
722
723 message["type"] = "package-locks"
724 result = self.send_message(message)
725
726 logging.info("Queuing message with changes in known package locks:"
727 " %d created, %d deleted." %
728 (len(set_package_locks), len(unset_package_locks)))
729
730 def update_currently_known(result):
731 if set_package_locks:
732 self._store.add_package_locks(set_package_locks)
733 if unset_package_locks:
734 self._store.remove_package_locks(unset_package_locks)
735 return True
736
737 result.addCallback(update_currently_known)
738>>>>>>> MERGE-SOURCE
739
740 return result
741
742 def send_pending_messages(self):
743 """
744 As the last callback of L{PackageReporter}, sends messages stored.
745 """
746 if self.global_store_filename is None:
747 self.global_store_filename = os.environ["FAKE_PACKAGE_STORE"]
748 if not os.path.exists(self.global_store_filename):
749 return succeed(None)
750 message_sent = set(self._store.get_message_ids())
751 global_store = FakePackageStore(self.global_store_filename)
752 all_message_ids = set(global_store.get_message_ids())
753 not_sent = all_message_ids - message_sent
754 deferred = succeed(None)
755 got_type = set()
756 if not_sent:
757 messages = global_store.get_messages_by_ids(not_sent)
758 sent = []
759 for message_id, message in messages:
760 message = bpickle.loads(str(message))
761 if message["type"] not in got_type:
762 got_type.add(message["type"])
763 sent.append(message_id)
764 deferred.addCallback(
765 lambda x, message=message: self.send_message(message))
766 self._store.save_message_ids(sent)
767 return deferred
768
562769
563class FakeGlobalReporter(PackageReporter):770class FakeGlobalReporter(PackageReporter):
564 """771 """
565772
=== modified file 'landscape/package/skeleton.py'
--- landscape/package/skeleton.py 2012-06-19 15:12:07 +0000
+++ landscape/package/skeleton.py 2014-11-19 18:22:39 +0000
@@ -131,3 +131,94 @@
131 skeleton.summary = skeleton.summary.decode("utf-8")131 skeleton.summary = skeleton.summary.decode("utf-8")
132 skeleton.description = skeleton.description.decode("utf-8")132 skeleton.description = skeleton.description.decode("utf-8")
133 return skeleton133 return skeleton
134<<<<<<< TREE
135=======
136
137build_skeleton.inited = False
138
139
140def relation_to_string(relation_tuple):
141 """Convert an apt relation to a string representation.
142
143 @param relation_tuple: A tuple, (name, version, relation). version
144 and relation can be the empty string, if the relation is on a
145 name only.
146
147 Returns something like "name > 1.0"
148 """
149 name, version, relation_type = relation_tuple
150 relation_string = name
151 if relation_type:
152 relation_string += " %s %s" % (relation_type, version)
153 return relation_string
154
155
156def parse_record_field(record, record_field, relation_type,
157 or_relation_type=None):
158 """Parse an apt C{Record} field and return skeleton relations
159
160 @param record: An C{apt.package.Record} instance with package information.
161 @param record_field: The name of the record field to parse.
162 @param relation_type: The deb relation that can be passed to
163 C{skeleton.add_relation()}
164 @param or_relation_type: The deb relation that should be used if
165 there is more than one value in a relation.
166 """
167 relations = set()
168 values = apt_pkg.parse_depends(record.get(record_field, ""))
169 for value in values:
170 value_strings = [relation_to_string(relation) for relation in value]
171 value_relation_type = relation_type
172 if len(value_strings) > 1:
173 value_relation_type = or_relation_type
174 relation_string = " | ".join(value_strings)
175 relations.add((value_relation_type, relation_string))
176 return relations
177
178
179def build_skeleton_apt(version, with_info=False, with_unicode=False):
180 """Build a package skeleton from an apt package.
181
182 @param version: An instance of C{apt.package.Version}
183 @param with_info: Whether to extract extra information about the
184 package, like description, summary, size.
185 @param with_unicode: Whether the C{name} and C{version} of the
186 skeleton should be unicode strings.
187 """
188 name, version_string = version.package.name, version.version
189 if with_unicode:
190 name, version_string = unicode(name), unicode(version_string)
191 skeleton = PackageSkeleton(DEB_PACKAGE, name, version_string)
192 relations = set()
193 relations.update(parse_record_field(
194 version.record, "Provides", DEB_PROVIDES))
195 relations.add((
196 DEB_NAME_PROVIDES,
197 "%s = %s" % (version.package.name, version.version)))
198 relations.update(parse_record_field(
199 version.record, "Pre-Depends", DEB_REQUIRES, DEB_OR_REQUIRES))
200 relations.update(parse_record_field(
201 version.record, "Depends", DEB_REQUIRES, DEB_OR_REQUIRES))
202
203 relations.add((
204 DEB_UPGRADES, "%s < %s" % (version.package.name, version.version)))
205
206 relations.update(parse_record_field(
207 version.record, "Conflicts", DEB_CONFLICTS))
208 relations.update(parse_record_field(
209 version.record, "Breaks", DEB_CONFLICTS))
210 skeleton.relations = sorted(relations)
211
212 if with_info:
213 skeleton.section = version.section
214 skeleton.summary = version.summary
215 skeleton.description = version.description
216 skeleton.size = version.size
217 if version.installed_size > 0:
218 skeleton.installed_size = version.installed_size
219 if with_unicode:
220 skeleton.section = skeleton.section.decode("utf-8")
221 skeleton.summary = skeleton.summary.decode("utf-8")
222 skeleton.description = skeleton.description.decode("utf-8")
223 return skeleton
224>>>>>>> MERGE-SOURCE
134225
=== modified file 'landscape/package/store.py'
=== modified file 'landscape/package/taskhandler.py'
--- landscape/package/taskhandler.py 2012-06-19 15:12:07 +0000
+++ landscape/package/taskhandler.py 2014-11-19 18:22:39 +0000
@@ -258,11 +258,23 @@
258 # 0644 so...258 # 0644 so...
259 os.umask(022)259 os.umask(022)
260260
261<<<<<<< TREE
261 package_store = cls.package_store_class(config.store_filename)262 package_store = cls.package_store_class(config.store_filename)
262 # Delay importing of the facades so that we don't263 # Delay importing of the facades so that we don't
263 # import Apt unless we need to.264 # import Apt unless we need to.
264 from landscape.package.facade import AptFacade265 from landscape.package.facade import AptFacade
265 package_facade = AptFacade()266 package_facade = AptFacade()
267=======
268 package_store = cls.package_store_class(config.store_filename)
269 # Delay importing of the facades so that we don't
270 # import Smart unless we need to.
271 from landscape.package.facade import (
272 AptFacade, SmartFacade, has_new_enough_apt)
273 if has_new_enough_apt:
274 package_facade = AptFacade()
275 else:
276 package_facade = SmartFacade()
277>>>>>>> MERGE-SOURCE
266278
267 def finish():279 def finish():
268 connector.disconnect()280 connector.disconnect()
269281
=== modified file 'landscape/package/tests/helpers.py'
--- landscape/package/tests/helpers.py 2012-06-19 15:12:07 +0000
+++ landscape/package/tests/helpers.py 2014-11-19 18:22:39 +0000
@@ -1,121 +1,256 @@
1import base641import base64
2import os2import os
3import textwrap3<<<<<<< TREE
4import time4import textwrap
55import time
6import apt_inst6
7import apt_pkg7import apt_inst
88import apt_pkg
9from landscape.lib.fs import append_file, create_file9
10from landscape.package.facade import AptFacade10from landscape.lib.fs import append_file, create_file
1111from landscape.package.facade import AptFacade
1212
13class AptFacadeHelper(object):13
14 """Helper that sets up an AptFacade with a tempdir as its root."""14class AptFacadeHelper(object):
1515 """Helper that sets up an AptFacade with a tempdir as its root."""
16 def set_up(self, test_case):16
17 test_case.apt_root = test_case.makeDir()17 def set_up(self, test_case):
18 self.dpkg_status = os.path.join(18 test_case.apt_root = test_case.makeDir()
19 test_case.apt_root, "var", "lib", "dpkg", "status")19 self.dpkg_status = os.path.join(
20 test_case.Facade = AptFacade20 test_case.apt_root, "var", "lib", "dpkg", "status")
21 test_case.facade = AptFacade(root=test_case.apt_root)21 test_case.Facade = AptFacade
22 test_case.facade.refetch_package_index = True22 test_case.facade = AptFacade(root=test_case.apt_root)
23 test_case._add_system_package = self._add_system_package23 test_case.facade.refetch_package_index = True
24 test_case._install_deb_file = self._install_deb_file24 test_case._add_system_package = self._add_system_package
25 test_case._add_package_to_deb_dir = self._add_package_to_deb_dir25 test_case._install_deb_file = self._install_deb_file
26 test_case._touch_packages_file = self._touch_packages_file26 test_case._add_package_to_deb_dir = self._add_package_to_deb_dir
27 test_case._hash_packages_by_name = self._hash_packages_by_name27 test_case._touch_packages_file = self._touch_packages_file
2828 test_case._hash_packages_by_name = self._hash_packages_by_name
29 def _add_package(self, packages_file, name, architecture="all",29
30 version="1.0", control_fields=None):30 def _add_package(self, packages_file, name, architecture="all",
31 if control_fields is None:31 version="1.0", control_fields=None):
32 control_fields = {}32 if control_fields is None:
33 package_stanza = textwrap.dedent("""33 control_fields = {}
34 Package: %(name)s34 package_stanza = textwrap.dedent("""
35 Priority: optional35 Package: %(name)s
36 Section: misc36 Priority: optional
37 Installed-Size: 123437 Section: misc
38 Maintainer: Someone38 Installed-Size: 1234
39 Architecture: %(architecture)s39 Maintainer: Someone
40 Source: source40 Architecture: %(architecture)s
41 Version: %(version)s41 Source: source
42 Description: description42 Version: %(version)s
43 """ % {"name": name, "version": version,43 Description: description
44 "architecture": architecture})44 """ % {"name": name, "version": version,
45 package_stanza = apt_pkg.rewrite_section(45 "architecture": architecture})
46 apt_pkg.TagSection(package_stanza), apt_pkg.REWRITE_PACKAGE_ORDER,46 package_stanza = apt_pkg.rewrite_section(
47 control_fields.items())47 apt_pkg.TagSection(package_stanza), apt_pkg.REWRITE_PACKAGE_ORDER,
48 append_file(packages_file, "\n" + package_stanza + "\n")48 control_fields.items())
4949 append_file(packages_file, "\n" + package_stanza + "\n")
50 def _add_system_package(self, name, architecture="all", version="1.0",50
51 control_fields=None):51 def _add_system_package(self, name, architecture="all", version="1.0",
52 """Add a package to the dpkg status file."""52 control_fields=None):
53 system_control_fields = {"Status": "install ok installed"}53 """Add a package to the dpkg status file."""
54 if control_fields is not None:54 system_control_fields = {"Status": "install ok installed"}
55 system_control_fields.update(control_fields)55 if control_fields is not None:
56 self._add_package(56 system_control_fields.update(control_fields)
57 self.dpkg_status, name, architecture=architecture, version=version,57 self._add_package(
58 control_fields=system_control_fields)58 self.dpkg_status, name, architecture=architecture, version=version,
5959 control_fields=system_control_fields)
60 def _install_deb_file(self, path):60
61 """Fake the the given deb file is installed in the system."""61 def _install_deb_file(self, path):
62 deb_file = open(path)62 """Fake the the given deb file is installed in the system."""
63 deb = apt_inst.DebFile(deb_file)63 deb_file = open(path)
64 control = deb.control.extractdata("control")64 deb = apt_inst.DebFile(deb_file)
65 deb_file.close()65 control = deb.control.extractdata("control")
66 lines = control.splitlines()66 deb_file.close()
67 lines.insert(1, "Status: install ok installed")67 lines = control.splitlines()
68 status = "\n".join(lines)68 lines.insert(1, "Status: install ok installed")
69 append_file(self.dpkg_status, status + "\n\n")69 status = "\n".join(lines)
7070 append_file(self.dpkg_status, status + "\n\n")
71 def _add_package_to_deb_dir(self, path, name, architecture="all",71
72 version="1.0", control_fields=None):72 def _add_package_to_deb_dir(self, path, name, architecture="all",
73 """Add fake package information to a directory.73 version="1.0", control_fields=None):
7474 """Add fake package information to a directory.
75 There will only be basic information about the package75
76 available, so that get_packages() have something to return.76 There will only be basic information about the package
77 There won't be an actual package in the dir.77 available, so that get_packages() have something to return.
78 """78 There won't be an actual package in the dir.
79 if control_fields is None:79 """
80 control_fields = {}80 if control_fields is None:
81 self._add_package(81 control_fields = {}
82 os.path.join(path, "Packages"), name, architecture=architecture,82 self._add_package(
83 version=version, control_fields=control_fields)83 os.path.join(path, "Packages"), name, architecture=architecture,
8484 version=version, control_fields=control_fields)
85 def _touch_packages_file(self, deb_dir):85
86 """Make sure the Packages file get a newer mtime value.86 def _touch_packages_file(self, deb_dir):
8787 """Make sure the Packages file get a newer mtime value.
88 If we rely on simply writing to the file to update the mtime, we88
89 might end up with the same as before, since the resolution is89 If we rely on simply writing to the file to update the mtime, we
90 seconds, which causes apt to not reload the file.90 might end up with the same as before, since the resolution is
91 """91 seconds, which causes apt to not reload the file.
92 packages_path = os.path.join(deb_dir, "Packages")92 """
93 mtime = int(time.time() + 1)93 packages_path = os.path.join(deb_dir, "Packages")
94 os.utime(packages_path, (mtime, mtime))94 mtime = int(time.time() + 1)
9595 os.utime(packages_path, (mtime, mtime))
96 def _hash_packages_by_name(self, facade, store, package_name):96
97 """97 def _hash_packages_by_name(self, facade, store, package_name):
98 Ensure the named L{Package} is correctly recorded in the store so that98 """
99 we can really test the functions of the facade that depend on it.99 Ensure the named L{Package} is correctly recorded in the store so that
100 """100 we can really test the functions of the facade that depend on it.
101 hash_ids = {}101 """
102 for version in facade.get_packages_by_name(package_name):102 hash_ids = {}
103 skeleton = facade.get_package_skeleton(103 for version in facade.get_packages_by_name(package_name):
104 version, with_info=False)104 skeleton = facade.get_package_skeleton(
105 hash = skeleton.get_hash()105 version, with_info=False)
106 facade._pkg2hash[(version.package, version)] = hash106 hash = skeleton.get_hash()
107 hash_ids[hash] = version.package.id107 facade._pkg2hash[(version.package, version)] = hash
108 store.set_hash_ids(hash_ids)108 hash_ids[hash] = version.package.id
109109 store.set_hash_ids(hash_ids)
110110
111class SimpleRepositoryHelper(object):111
112 """Helper for adding a simple repository to the facade.112class SimpleRepositoryHelper(object):
113113 """Helper for adding a simple repository to the facade.
114 This helper requires that C{test_case.facade} has already been set114
115 up.115 This helper requires that C{test_case.facade} has already been set
116 """116 up.
117117 """
118 def set_up(self, test_case):118
119 def set_up(self, test_case):
120=======
121import textwrap
122import time
123
124try:
125 import smart
126except ImportError:
127 # Smart is optional if AptFacade is being used.
128 pass
129
130import apt_inst
131import apt_pkg
132
133from landscape.lib.fs import append_file, create_file
134from landscape.package.facade import AptFacade
135
136
137class AptFacadeHelper(object):
138 """Helper that sets up an AptFacade with a tempdir as its root."""
139
140 def set_up(self, test_case):
141 test_case.apt_root = test_case.makeDir()
142 self.dpkg_status = os.path.join(
143 test_case.apt_root, "var", "lib", "dpkg", "status")
144 test_case.Facade = AptFacade
145 test_case.facade = AptFacade(root=test_case.apt_root)
146 test_case.facade.refetch_package_index = True
147 test_case._add_system_package = self._add_system_package
148 test_case._install_deb_file = self._install_deb_file
149 test_case._add_package_to_deb_dir = self._add_package_to_deb_dir
150 test_case._touch_packages_file = self._touch_packages_file
151 test_case._hash_packages_by_name = self._hash_packages_by_name
152
153 def _add_package(self, packages_file, name, architecture="all",
154 version="1.0", control_fields=None):
155 if control_fields is None:
156 control_fields = {}
157 package_stanza = textwrap.dedent("""
158 Package: %(name)s
159 Priority: optional
160 Section: misc
161 Installed-Size: 1234
162 Maintainer: Someone
163 Architecture: %(architecture)s
164 Source: source
165 Version: %(version)s
166 Description: description
167 """ % {"name": name, "version": version,
168 "architecture": architecture})
169 package_stanza = apt_pkg.rewrite_section(
170 apt_pkg.TagSection(package_stanza), apt_pkg.REWRITE_PACKAGE_ORDER,
171 control_fields.items())
172 append_file(packages_file, "\n" + package_stanza + "\n")
173
174 def _add_system_package(self, name, architecture="all", version="1.0",
175 control_fields=None):
176 """Add a package to the dpkg status file."""
177 system_control_fields = {"Status": "install ok installed"}
178 if control_fields is not None:
179 system_control_fields.update(control_fields)
180 self._add_package(
181 self.dpkg_status, name, architecture=architecture, version=version,
182 control_fields=system_control_fields)
183
184 def _install_deb_file(self, path):
185 """Fake the the given deb file is installed in the system."""
186 deb_file = open(path)
187 deb = apt_inst.DebFile(deb_file)
188 control = deb.control.extractdata("control")
189 deb_file.close()
190 lines = control.splitlines()
191 lines.insert(1, "Status: install ok installed")
192 status = "\n".join(lines)
193 append_file(self.dpkg_status, status + "\n\n")
194
195 def _add_package_to_deb_dir(self, path, name, architecture="all",
196 version="1.0", control_fields=None):
197 """Add fake package information to a directory.
198
199 There will only be basic information about the package
200 available, so that get_packages() have something to return.
201 There won't be an actual package in the dir.
202 """
203 if control_fields is None:
204 control_fields = {}
205 self._add_package(
206 os.path.join(path, "Packages"), name, architecture=architecture,
207 version=version, control_fields=control_fields)
208
209 def _touch_packages_file(self, deb_dir):
210 """Make sure the Packages file get a newer mtime value.
211
212 If we rely on simply writing to the file to update the mtime, we
213 might end up with the same as before, since the resolution is
214 seconds, which causes apt to not reload the file.
215 """
216 packages_path = os.path.join(deb_dir, "Packages")
217 mtime = int(time.time() + 1)
218 os.utime(packages_path, (mtime, mtime))
219
220 def _hash_packages_by_name(self, facade, store, package_name):
221 """
222 Ensure the named L{Package} is correctly recorded in the store so that
223 we can really test the functions of the facade that depend on it.
224 """
225 hash_ids = {}
226 for version in facade.get_packages_by_name(package_name):
227 skeleton = facade.get_package_skeleton(
228 version, with_info=False)
229 hash = skeleton.get_hash()
230 facade._pkg2hash[(version.package, version)] = hash
231 hash_ids[hash] = version.package.id
232 store.set_hash_ids(hash_ids)
233
234
235class SimpleRepositoryHelper(object):
236 """Helper for adding a simple repository to the facade.
237
238 This helper requires that C{test_case.facade} has already been set
239 up.
240 """
241
242 def set_up(self, test_case):
243 test_case.repository_dir = test_case.makeDir()
244 create_simple_repository(test_case.repository_dir)
245 test_case.facade.add_channel_deb_dir(test_case.repository_dir)
246
247
248class SmartHelper(object):
249
250 def set_up(self, test_case):
251 test_case.smart_dir = test_case.makeDir()
252 test_case.smart_config = test_case.makeFile("")
253>>>>>>> MERGE-SOURCE
119 test_case.repository_dir = test_case.makeDir()254 test_case.repository_dir = test_case.makeDir()
120 create_simple_repository(test_case.repository_dir)255 create_simple_repository(test_case.repository_dir)
121 test_case.facade.add_channel_deb_dir(test_case.repository_dir)256 test_case.facade.add_channel_deb_dir(test_case.repository_dir)
122257
=== modified file 'landscape/package/tests/test_changer.py'
--- landscape/package/tests/test_changer.py 2012-06-19 15:12:07 +0000
+++ landscape/package/tests/test_changer.py 2014-11-19 18:22:39 +0000
@@ -6,20 +6,35 @@
66
7from twisted.internet.defer import Deferred7from twisted.internet.defer import Deferred
88
9from landscape.lib.fs import create_file, read_file, touch_file9<<<<<<< TREE
10from landscape.lib.fs import create_file, read_file, touch_file
11=======
12try:
13 from smart.cache import Provides
14except ImportError:
15 # Smart is optional if AptFacade is being used.
16 pass
17
18from landscape.lib.fs import create_file, read_file, touch_file
19>>>>>>> MERGE-SOURCE
10from landscape.package.changer import (20from landscape.package.changer import (
11 PackageChanger, main, find_changer_command, UNKNOWN_PACKAGE_DATA_TIMEOUT,21 PackageChanger, main, find_changer_command, UNKNOWN_PACKAGE_DATA_TIMEOUT,
12 SUCCESS_RESULT, DEPENDENCY_ERROR_RESULT, POLICY_ALLOW_INSTALLS,22 SUCCESS_RESULT, DEPENDENCY_ERROR_RESULT, POLICY_ALLOW_INSTALLS,
13 POLICY_ALLOW_ALL_CHANGES, ERROR_RESULT)23 POLICY_ALLOW_ALL_CHANGES, ERROR_RESULT)
14from landscape.package.store import PackageStore24from landscape.package.store import PackageStore
15from landscape.package.facade import (25from landscape.package.facade import (
26<<<<<<< TREE
16 DependencyError, TransactionError)27 DependencyError, TransactionError)
28=======
29 DependencyError, TransactionError, SmartError, has_new_enough_apt)
30>>>>>>> MERGE-SOURCE
17from landscape.package.changer import (31from landscape.package.changer import (
18 PackageChangerConfiguration, ChangePackagesResult)32 PackageChangerConfiguration, ChangePackagesResult)
19from landscape.tests.mocker import ANY33from landscape.tests.mocker import ANY
20from landscape.tests.helpers import (34from landscape.tests.helpers import (
21 LandscapeTest, BrokerServiceHelper)35 LandscapeTest, BrokerServiceHelper)
22from landscape.package.tests.helpers import (36from landscape.package.tests.helpers import (
37<<<<<<< TREE
23 HASH1, HASH2, HASH3, PKGDEB1, PKGDEB2,38 HASH1, HASH2, HASH3, PKGDEB1, PKGDEB2,
24 AptFacadeHelper, SimpleRepositoryHelper)39 AptFacadeHelper, SimpleRepositoryHelper)
25from landscape.manager.manager import FAILED40from landscape.manager.manager import FAILED
@@ -47,7 +62,25 @@
4762
48 result = super(AptPackageChangerTest, self).setUp()63 result = super(AptPackageChangerTest, self).setUp()
49 return result.addCallback(set_up)64 return result.addCallback(set_up)
5065=======
66 SmartFacadeHelper, HASH1, HASH2, HASH3, PKGDEB1, PKGDEB2, PKGNAME2,
67 AptFacadeHelper, SimpleRepositoryHelper)
68from landscape.manager.manager import FAILED, SUCCEEDED
69
70
71class PackageChangerTestMixin(object):
72
73 def disable_clear_channels(self):
74 """Disable clear_channels(), so that it doesn't remove test setup.
75
76 This is useful for change-packages tests, which will call
77 facade.clear_channels(). Normally that's safe, but since we used
78 the facade to set up channels, we don't want them to be removed.
79 """
80 self.facade.clear_channels = lambda: None
81>>>>>>> MERGE-SOURCE
82
83<<<<<<< TREE
51 def set_pkg1_installed(self):84 def set_pkg1_installed(self):
52 """Return the hash of a package that is installed."""85 """Return the hash of a package that is installed."""
53 self._add_system_package("foo")86 self._add_system_package("foo")
@@ -135,6 +168,19 @@
135168
136 self.addCleanup(reset_perform_changes, self.Facade)169 self.addCleanup(reset_perform_changes, self.Facade)
137 self.Facade.perform_changes = func170 self.Facade.perform_changes = func
171=======
172 def get_pending_messages(self):
173 return self.broker_service.message_store.get_pending_messages()
174
175 def replace_perform_changes(self, func):
176 old_perform_changes = self.Facade.perform_changes
177
178 def reset_perform_changes(Facade):
179 Facade.perform_changes = old_perform_changes
180
181 self.addCleanup(reset_perform_changes, self.Facade)
182 self.Facade.perform_changes = func
183>>>>>>> MERGE-SOURCE
138184
139 def test_unknown_package_id_for_dependency(self):185 def test_unknown_package_id_for_dependency(self):
140 hash1, hash2 = self.set_pkg1_and_pkg2_satisfied()186 hash1, hash2 = self.set_pkg1_and_pkg2_satisfied()
@@ -511,6 +557,7 @@
511 {"type": "change-packages", "upgrade-all": True,557 {"type": "change-packages", "upgrade-all": True,
512 "operation-id": 124})558 "operation-id": 124})
513559
560<<<<<<< TREE
514 result = self.changer.handle_tasks()561 result = self.changer.handle_tasks()
515562
516 def got_result(result):563 def got_result(result):
@@ -554,6 +601,51 @@
554 self.assertEqual(124, message2["operation-id"])601 self.assertEqual(124, message2["operation-id"])
555 self.assertEqual("change-packages-result", message2["type"])602 self.assertEqual("change-packages-result", message2["type"])
556 self.assertEqual(ERROR_RESULT, message2["result-code"])603 self.assertEqual(ERROR_RESULT, message2["result-code"])
604=======
605 result = self.changer.handle_tasks()
606
607 def got_result(result):
608 message = self.get_pending_messages()[1]
609 self.assertEqual(124, message["operation-id"])
610 self.assertEqual("change-packages-result", message["type"])
611 self.assertNotEqual(0, message["result-code"])
612
613 return result.addCallback(got_result)
614
615 def test_tasks_are_isolated_cache(self):
616 """
617 The package (apt/smart) cache should be reset between task runs.
618 In this test, we try to run two different operations, first
619 installing package 2, then removing package 1. Both tasks will
620 fail for lack of superuser privileges. If the package cache
621 isn't reset between tasks, the second operation will fail with a
622 dependency error, since it will be marked for installation, but
623 we haven't explicitly marked it so.
624 """
625 self.log_helper.ignore_errors(".*dpkg")
626
627 installable_hash = self.set_pkg2_satisfied()
628 installed_hash = self.set_pkg1_installed()
629 self.store.set_hash_ids({installed_hash: 1, installable_hash: 2})
630
631 self.store.add_task("changer",
632 {"type": "change-packages", "install": [2],
633 "operation-id": 123})
634 self.store.add_task("changer",
635 {"type": "change-packages", "remove": [1],
636 "operation-id": 124})
637
638 result = self.changer.handle_tasks()
639
640 def got_result(result):
641 message1, message2 = self.get_pending_messages()
642 self.assertEqual(123, message1["operation-id"])
643 self.assertEqual("change-packages-result", message1["type"])
644 self.assertEqual(ERROR_RESULT, message1["result-code"])
645 self.assertEqual(124, message2["operation-id"])
646 self.assertEqual("change-packages-result", message2["type"])
647 self.assertEqual(ERROR_RESULT, message2["result-code"])
648>>>>>>> MERGE-SOURCE
557649
558 return result.addCallback(got_result)650 return result.addCallback(got_result)
559651
@@ -873,27 +965,72 @@
873965
874 def raise_error(self):966 def raise_error(self):
875 raise TransactionError(u"áéíóú")967 raise TransactionError(u"áéíóú")
876 self.replace_perform_changes(raise_error)968<<<<<<< TREE
877 self.disable_clear_channels()969 self.replace_perform_changes(raise_error)
878970 self.disable_clear_channels()
879 result = self.changer.handle_tasks()971
880972 result = self.changer.handle_tasks()
881 def got_result(result):973
882 self.assertMessages(self.get_pending_messages(),974 def got_result(result):
883 [{"operation-id": 123,975 self.assertMessages(self.get_pending_messages(),
884 "result-code": 100,976 [{"operation-id": 123,
885 "result-text": u"áéíóú",977 "result-code": 100,
886 "type": "change-packages-result"}])978 "result-text": u"áéíóú",
887 return result.addCallback(got_result)979 "type": "change-packages-result"}])
888980 return result.addCallback(got_result)
889 def test_update_stamp_exists(self):981
890 """982 def test_update_stamp_exists(self):
891 L{PackageChanger.update_exists} returns C{True} if the983 """
892 update-stamp file is there, C{False} otherwise.984 L{PackageChanger.update_exists} returns C{True} if the
893 """985 update-stamp file is there, C{False} otherwise.
894 self.assertTrue(self.changer.update_stamp_exists())986 """
895 os.remove(self.config.update_stamp_filename)987 self.assertTrue(self.changer.update_stamp_exists())
896 self.assertFalse(self.changer.update_stamp_exists())988 os.remove(self.config.update_stamp_filename)
989 self.assertFalse(self.changer.update_stamp_exists())
990=======
991 self.replace_perform_changes(raise_error)
992 self.disable_clear_channels()
993
994 result = self.changer.handle_tasks()
995
996 def got_result(result):
997 self.assertMessages(self.get_pending_messages(),
998 [{"operation-id": 123,
999 "result-code": 100,
1000 "result-text": u"áéíóú",
1001 "type": "change-packages-result"}])
1002 return result.addCallback(got_result)
1003
1004 def test_smart_error_with_unicode_data(self):
1005 self.store.set_hash_ids({HASH1: 1})
1006 self.store.add_task("changer",
1007 {"type": "change-packages", "install": [1],
1008 "operation-id": 123})
1009
1010 def raise_error(self):
1011 raise SmartError(u"áéíóú")
1012 self.replace_perform_changes(raise_error)
1013 self.disable_clear_channels()
1014
1015 result = self.changer.handle_tasks()
1016
1017 def got_result(result):
1018 self.assertMessages(self.get_pending_messages(),
1019 [{"operation-id": 123,
1020 "result-code": 100,
1021 "result-text": u"áéíóú",
1022 "type": "change-packages-result"}])
1023 return result.addCallback(got_result)
1024
1025 def test_update_stamp_exists(self):
1026 """
1027 L{PackageChanger.update_exists} returns C{True} if the
1028 update-stamp file is there, C{False} otherwise.
1029 """
1030 self.assertTrue(self.changer.update_stamp_exists())
1031 os.remove(self.config.update_stamp_filename)
1032 self.assertFalse(self.changer.update_stamp_exists())
1033>>>>>>> MERGE-SOURCE
8971034
898 def test_binaries_path(self):1035 def test_binaries_path(self):
899 self.assertEqual(1036 self.assertEqual(
@@ -946,6 +1083,7 @@
946 self.changer.init_channels([])1083 self.changer.init_channels([])
947 self.assertFalse(os.path.exists(existing_deb_path))1084 self.assertFalse(os.path.exists(existing_deb_path))
9481085
1086<<<<<<< TREE
9491087
950 def test_binaries_available_in_cache(self):1088 def test_binaries_available_in_cache(self):
951 """1089 """
@@ -974,6 +1112,101 @@
974 return result.addCallback(got_result)1112 return result.addCallback(got_result)
9751113
976 def test_change_package_holds(self):1114 def test_change_package_holds(self):
1115=======
1116
1117class SmartPackageChangerTest(LandscapeTest, PackageChangerTestMixin):
1118
1119 helpers = [SmartFacadeHelper, BrokerServiceHelper]
1120
1121 def setUp(self):
1122
1123 def set_up(ignored):
1124
1125 self.store = PackageStore(self.makeFile())
1126 self.config = PackageChangerConfiguration()
1127 self.config.data_path = self.makeDir()
1128 os.mkdir(self.config.package_directory)
1129 os.mkdir(self.config.binaries_path)
1130 touch_file(self.config.update_stamp_filename)
1131 self.changer = PackageChanger(
1132 self.store, self.facade, self.remote, self.config)
1133 service = self.broker_service
1134 service.message_store.set_accepted_types(["change-packages-result",
1135 "operation-result"])
1136
1137 result = super(SmartPackageChangerTest, self).setUp()
1138 return result.addCallback(set_up)
1139
1140 def set_pkg1_installed(self):
1141 previous = self.Facade.channels_reloaded
1142
1143 def callback(self):
1144 previous(self)
1145 self.get_packages_by_name("name1")[0].installed = True
1146 self.Facade.channels_reloaded = callback
1147 return HASH1
1148
1149 def set_pkg2_upgrades_pkg1(self):
1150 previous = self.Facade.channels_reloaded
1151
1152 def callback(self):
1153 from smart.backends.deb.base import DebUpgrades
1154 previous(self)
1155 [pkg2] = self.get_packages_by_name("name2")
1156 pkg2.upgrades += (DebUpgrades("name1", "=", "version1-release1"),)
1157 self.reload_cache() # Relink relations.
1158 self.Facade.channels_reloaded = callback
1159 self.set_pkg2_satisfied()
1160 self.set_pkg1_installed()
1161 return HASH1, HASH2
1162
1163 def set_pkg2_satisfied(self):
1164 previous = self.Facade.channels_reloaded
1165
1166 def callback(self):
1167 previous(self)
1168 [pkg2] = self.get_packages_by_name("name2")
1169 pkg2.requires = ()
1170 self.reload_cache() # Relink relations.
1171 self.Facade.channels_reloaded = callback
1172 return HASH2
1173
1174 def set_pkg1_and_pkg2_satisfied(self):
1175 previous = self.Facade.channels_reloaded
1176
1177 def callback(self):
1178 previous(self)
1179
1180 provide1 = Provides("prerequirename1", "prerequireversion1")
1181 provide2 = Provides("requirename1", "requireversion1")
1182 [pkg2] = self.get_packages_by_name("name2")
1183 pkg2.provides += (provide1, provide2)
1184
1185 provide1 = Provides("prerequirename2", "prerequireversion2")
1186 provide2 = Provides("requirename2", "requireversion2")
1187 [pkg1] = self.get_packages_by_name("name1")
1188 pkg1.provides += (provide1, provide2)
1189
1190 # Ask Smart to reprocess relationships.
1191 self.reload_cache()
1192 self.Facade.channels_reloaded = callback
1193 return HASH1, HASH2
1194
1195 def remove_pkg2(self):
1196 os.remove(os.path.join(self.repository_dir, PKGNAME2))
1197
1198 def get_transaction_error_message(self):
1199 return "requirename1 = requireversion1"
1200
1201 def get_binaries_channels(self, binaries_path):
1202 return {binaries_path: {"type": "deb-dir",
1203 "path": binaries_path}}
1204
1205 def get_package_name(self, package):
1206 return package.name
1207
1208 def test_change_package_locks(self):
1209>>>>>>> MERGE-SOURCE
977 """1210 """
978 The L{PackageChanger.handle_tasks} method appropriately creates and1211 The L{PackageChanger.handle_tasks} method appropriately creates and
979 deletes package holds as requested by the C{change-packages}1212 deletes package holds as requested by the C{change-packages}
@@ -1331,42 +1564,738 @@
1331 "operation-id": 123})1564 "operation-id": 123})
13321565
1333 def assert_result(result):1566 def assert_result(result):
1334 self.assertMessages(1567<<<<<<< TREE
1335 self.get_pending_messages(),1568 self.assertMessages(
1336 [{"type": "operation-result",1569 self.get_pending_messages(),
1337 "operation-id": 123,1570 [{"type": "operation-result",
1338 "status": FAILED,1571 "operation-id": 123,
1339 "result-text": "This client doesn't support package locks.",1572 "status": FAILED,
1340 "result-code": 1}])1573 "result-text": "This client doesn't support package locks.",
13411574 "result-code": 1}])
1342 result = self.changer.handle_tasks()1575
1343 return result.addCallback(assert_result)1576 result = self.changer.handle_tasks()
13441577 return result.addCallback(assert_result)
1345 def test_change_packages_with_binaries_removes_binaries(self):1578
1346 """1579 def test_change_packages_with_binaries_removes_binaries(self):
1347 After the C{change-packages} handler has installed the binaries,1580 """
1348 the binaries and the internal facade deb source is removed.1581 After the C{change-packages} handler has installed the binaries,
1349 """1582 the binaries and the internal facade deb source is removed.
1350 self.store.add_task("changer",1583 """
1351 {"type": "change-packages", "install": [2],1584 self.store.add_task("changer",
1352 "binaries": [(HASH2, 2, PKGDEB2)],1585 {"type": "change-packages", "install": [2],
1353 "operation-id": 123})1586 "binaries": [(HASH2, 2, PKGDEB2)],
13541587 "operation-id": 123})
1355 def return_good_result(self):1588
1356 return "Yeah, I did whatever you've asked for!"1589 def return_good_result(self):
1357 self.replace_perform_changes(return_good_result)1590 return "Yeah, I did whatever you've asked for!"
13581591 self.replace_perform_changes(return_good_result)
1359 result = self.changer.handle_tasks()1592
13601593 result = self.changer.handle_tasks()
1361 def got_result(result):1594
1362 self.assertMessages(self.get_pending_messages(),1595 def got_result(result):
1363 [{"operation-id": 123,1596 self.assertMessages(self.get_pending_messages(),
1364 "result-code": 1,1597 [{"operation-id": 123,
1365 "result-text": "Yeah, I did whatever you've "1598 "result-code": 1,
1366 "asked for!",1599 "result-text": "Yeah, I did whatever you've "
1367 "type": "change-packages-result"}])1600 "asked for!",
1368 self.assertEqual([], os.listdir(self.config.binaries_path))1601 "type": "change-packages-result"}])
1369 self.assertFalse(1602 self.assertEqual([], os.listdir(self.config.binaries_path))
1370 os.path.exists(self.facade._get_internal_sources_list()))1603 self.assertFalse(
13711604 os.path.exists(self.facade._get_internal_sources_list()))
1372 return result.addCallback(got_result)1605
1606 return result.addCallback(got_result)
1607=======
1608 self.facade.deinit()
1609 self.assertEqual(self.facade.get_package_locks(),
1610 [("foo", ">=", "1.0")])
1611 self.assertIn("Queuing message with change package locks results "
1612 "to exchange urgently.", self.logfile.getvalue())
1613 self.assertMessages(self.get_pending_messages(),
1614 [{"type": "operation-result",
1615 "operation-id": 123,
1616 "status": SUCCEEDED,
1617 "result-text": "Package locks successfully"
1618 " changed.",
1619 "result-code": 0}])
1620
1621 result = self.changer.handle_tasks()
1622 return result.addCallback(assert_result)
1623
1624 def test_change_package_locks_create_with_already_existing(self):
1625 """
1626 The L{PackageChanger.handle_tasks} method gracefully handles requests
1627 for creating package locks that already exist.
1628 """
1629 self.facade.set_package_lock("foo")
1630 self.store.add_task("changer", {"type": "change-package-locks",
1631 "create": [("foo", None, None)],
1632 "operation-id": 123})
1633
1634 def assert_result(result):
1635 self.facade.deinit()
1636 self.assertEqual(self.facade.get_package_locks(),
1637 [("foo", "", "")])
1638 self.assertMessages(self.get_pending_messages(),
1639 [{"type": "operation-result",
1640 "operation-id": 123,
1641 "status": SUCCEEDED,
1642 "result-text": "Package locks successfully"
1643 " changed.",
1644 "result-code": 0}])
1645
1646 result = self.changer.handle_tasks()
1647 return result.addCallback(assert_result)
1648
1649 def test_change_package_locks_delete_without_already_existing(self):
1650 """
1651 The L{PackageChanger.handle_tasks} method gracefully handles requests
1652 for deleting package locks that don't exist.
1653 """
1654 self.store.add_task("changer", {"type": "change-package-locks",
1655 "delete": [("foo", ">=", "1.0")],
1656 "operation-id": 123})
1657
1658 def assert_result(result):
1659 self.facade.deinit()
1660 self.assertEqual(self.facade.get_package_locks(), [])
1661 self.assertMessages(self.get_pending_messages(),
1662 [{"type": "operation-result",
1663 "operation-id": 123,
1664 "status": SUCCEEDED,
1665 "result-text": "Package locks successfully"
1666 " changed.",
1667 "result-code": 0}])
1668
1669 result = self.changer.handle_tasks()
1670 return result.addCallback(assert_result)
1671
1672 def test_dpkg_error(self):
1673 """
1674 Verify that errors emitted by dpkg are correctly reported to
1675 the server as problems.
1676
1677 This test is to make sure that Smart reports the problem
1678 correctly. It doesn't make sense for AptFacade, since there we
1679 don't call dpkg.
1680 """
1681 self.log_helper.ignore_errors(".*dpkg")
1682
1683 installed_hash = self.set_pkg1_installed()
1684 self.store.set_hash_ids({installed_hash: 1})
1685 self.store.add_task("changer",
1686 {"type": "change-packages", "remove": [1],
1687 "operation-id": 123})
1688
1689 result = self.changer.handle_tasks()
1690
1691 def got_result(result):
1692 messages = self.get_pending_messages()
1693 self.assertEqual(len(messages), 1, "Too many messages")
1694 message = messages[0]
1695 self.assertEqual(message["operation-id"], 123)
1696 self.assertEqual(message["result-code"], 100)
1697 self.assertEqual(message["type"], "change-packages-result")
1698 text = message["result-text"]
1699 # We can't test the actual content of the message because the dpkg
1700 # error can be localized
1701 self.assertIn("\n[remove] name1_version1-release1\ndpkg: ", text)
1702 self.assertIn("ERROR", text)
1703 self.assertIn("(2)", text)
1704 return result.addCallback(got_result)
1705
1706 def test_change_package_holds(self):
1707 """
1708 If C{SmartFacade} is used, the L{PackageChanger.handle_tasks}
1709 method fails the activity, since it can't add or remove dpkg holds.
1710 """
1711 self.facade.reload_channels()
1712 self.store.add_task("changer", {"type": "change-package-holds",
1713 "create": [1],
1714 "delete": [2],
1715 "operation-id": 123})
1716
1717 def assert_result(result):
1718 self.assertIn("Queuing message with change package holds results "
1719 "to exchange urgently.", self.logfile.getvalue())
1720 self.assertMessages(
1721 self.get_pending_messages(),
1722 [{"type": "operation-result",
1723 "operation-id": 123,
1724 "status": FAILED,
1725 "result-text": "This client doesn't support package holds.",
1726 "result-code": 1}])
1727
1728 result = self.changer.handle_tasks()
1729 return result.addCallback(assert_result)
1730
1731 def test_global_upgrade(self):
1732 """
1733 Besides asking for individual changes, the server may also request
1734 the client to perform a global upgrade. This would be the equivalent
1735 of a "smart upgrade" command being executed in the command line.
1736
1737 This test should be run for both C{AptFacade} and
1738 C{SmartFacade}, but due to the smart test setting up that two
1739 packages with different names upgrade each other, the message
1740 doesn't correctly report that the old version should be
1741 uninstalled. The test is still useful, since it shows that the
1742 message contains the changes that smart says are needed.
1743
1744 Making the test totally correct is a lot of work, that is not
1745 worth doing, since we're removing smart soon.
1746 """
1747 hash1, hash2 = self.set_pkg2_upgrades_pkg1()
1748 self.store.set_hash_ids({hash1: 1, hash2: 2})
1749
1750 self.store.add_task("changer",
1751 {"type": "change-packages", "upgrade-all": True,
1752 "operation-id": 123})
1753
1754 result = self.changer.handle_tasks()
1755
1756 def got_result(result):
1757 self.assertMessages(self.get_pending_messages(),
1758 [{"operation-id": 123,
1759 "must-install": [2],
1760 "result-code": 101,
1761 "type": "change-packages-result"}])
1762
1763 return result.addCallback(got_result)
1764
1765
1766class AptPackageChangerTest(LandscapeTest, PackageChangerTestMixin):
1767
1768 if not has_new_enough_apt:
1769 skip = "Can't use AptFacade on hardy"
1770
1771 helpers = [AptFacadeHelper, SimpleRepositoryHelper, BrokerServiceHelper]
1772
1773 def setUp(self):
1774
1775 def set_up(ignored):
1776
1777 self.store = PackageStore(self.makeFile())
1778 self.config = PackageChangerConfiguration()
1779 self.config.data_path = self.makeDir()
1780 os.mkdir(self.config.package_directory)
1781 os.mkdir(self.config.binaries_path)
1782 touch_file(self.config.update_stamp_filename)
1783 self.changer = PackageChanger(
1784 self.store, self.facade, self.remote, self.config)
1785 service = self.broker_service
1786 service.message_store.set_accepted_types(["change-packages-result",
1787 "operation-result"])
1788
1789 result = super(AptPackageChangerTest, self).setUp()
1790 return result.addCallback(set_up)
1791
1792 def set_pkg1_installed(self):
1793 """Return the hash of a package that is installed."""
1794 self._add_system_package("foo")
1795 self.facade.reload_channels()
1796 [foo] = self.facade.get_packages_by_name("foo")
1797 return self.facade.get_package_hash(foo)
1798
1799 def set_pkg2_satisfied(self):
1800 """Return the hash of a package that can be installed."""
1801 self._add_package_to_deb_dir(self.repository_dir, "bar")
1802 self.facade.reload_channels()
1803 [bar] = self.facade.get_packages_by_name("bar")
1804 return self.facade.get_package_hash(bar)
1805
1806 def set_pkg1_and_pkg2_satisfied(self):
1807 """Make a package depend on another package.
1808
1809 Return the hashes of the two packages.
1810 """
1811 self._add_package_to_deb_dir(
1812 self.repository_dir, "foo", control_fields={"Depends": "bar"})
1813 self._add_package_to_deb_dir(self.repository_dir, "bar")
1814 self.facade.reload_channels()
1815 [foo] = self.facade.get_packages_by_name("foo")
1816 [bar] = self.facade.get_packages_by_name("bar")
1817 return (
1818 self.facade.get_package_hash(foo),
1819 self.facade.get_package_hash(bar))
1820
1821 def set_pkg2_upgrades_pkg1(self):
1822 """Make it so that one package upgrades another.
1823
1824 Return the hashes of the two packages.
1825 """
1826 self._add_system_package("foo", version="1.0")
1827 self._add_package_to_deb_dir(self.repository_dir, "foo", version="2.0")
1828 self.facade.reload_channels()
1829 foo_1, foo_2 = sorted(self.facade.get_packages_by_name("foo"))
1830 return (
1831 self.facade.get_package_hash(foo_1),
1832 self.facade.get_package_hash(foo_2))
1833
1834 def remove_pkg2(self):
1835 """Remove package name2 from its repository."""
1836 packages_file = os.path.join(self.repository_dir, "Packages")
1837 packages_contents = read_file(packages_file)
1838 packages_contents = "\n\n".join(
1839 [stanza for stanza in packages_contents.split("\n\n")
1840 if "Package: name2" not in stanza])
1841 create_file(packages_file, packages_contents)
1842
1843 def get_transaction_error_message(self):
1844 """Return part of the apt transaction error message."""
1845 return "Unable to correct problems"
1846
1847 def get_binaries_channels(self, binaries_path):
1848 """Return the channels that will be used for the binaries."""
1849 return [{"baseurl": "file://%s" % binaries_path,
1850 "components": "",
1851 "distribution": "./",
1852 "type": "deb"}]
1853
1854 def get_package_name(self, version):
1855 """Return the name of the package."""
1856 return version.package.name
1857
1858 def test_binaries_available_in_cache(self):
1859 """
1860 If binaries are included in the changes-packages message, those
1861 will be added to the facade's cache.
1862 """
1863 # Make sure to turn off automatic rereading of Packages file,
1864 # like it is by default.
1865 self.facade.refetch_package_index = False
1866 self.assertEqual(None, self.facade.get_package_by_hash(HASH2))
1867 self.store.add_task("changer",
1868 {"type": "change-packages", "install": [2],
1869 "binaries": [(HASH2, 2, PKGDEB2)],
1870 "operation-id": 123})
1871
1872 def return_good_result(self):
1873 return "Yeah, I did whatever you've asked for!"
1874 self.replace_perform_changes(return_good_result)
1875
1876 result = self.changer.handle_tasks()
1877
1878 def got_result(result):
1879 self.assertNotEqual(None, self.facade.get_package_by_hash(HASH2))
1880 self.assertFalse(self.facade.refetch_package_index)
1881
1882 return result.addCallback(got_result)
1883
1884 def test_change_package_holds(self):
1885 """
1886 The L{PackageChanger.handle_tasks} method appropriately creates and
1887 deletes package holds as requested by the C{change-package-holds}
1888 message.
1889 """
1890 self._add_system_package("foo")
1891 self._add_system_package("bar")
1892 self.facade.reload_channels()
1893 self._hash_packages_by_name(self.facade, self.store, "foo")
1894 self._hash_packages_by_name(self.facade, self.store, "bar")
1895 [foo] = self.facade.get_packages_by_name("foo")
1896 [bar] = self.facade.get_packages_by_name("bar")
1897 self.facade.set_package_hold(bar)
1898 # Make sure that the mtime of the dpkg status file is old when
1899 # apt loads it, so that it will be reloaded when asserting the
1900 # test result.
1901 old_mtime = time.time() - 10
1902 os.utime(self.facade._dpkg_status, (old_mtime, old_mtime))
1903 self.facade.reload_channels()
1904 self.store.add_task("changer", {"type": "change-package-holds",
1905 "create": [foo.package.id],
1906 "delete": [bar.package.id],
1907 "operation-id": 123})
1908
1909 def assert_result(result):
1910 self.facade.reload_channels()
1911 self.assertEqual(["foo"], self.facade.get_package_holds())
1912 self.assertIn("Queuing message with change package holds results "
1913 "to exchange urgently.", self.logfile.getvalue())
1914 self.assertMessages(
1915 self.get_pending_messages(),
1916 [{"type": "operation-result",
1917 "operation-id": 123,
1918 "status": SUCCEEDED,
1919 "result-text": "Package holds successfully changed.",
1920 "result-code": 0}])
1921
1922 result = self.changer.handle_tasks()
1923 return result.addCallback(assert_result)
1924
1925 def test_create_package_holds_with_identical_version(self):
1926 """
1927 The L{PackageChanger.handle_tasks} method appropriately creates
1928 holds as requested by the C{change-package-holds} message even
1929 when versions from two different packages are the same.
1930 """
1931 self._add_system_package("foo", version="1.1")
1932 self._add_system_package("bar", version="1.1")
1933 self.facade.reload_channels()
1934 self._hash_packages_by_name(self.facade, self.store, "foo")
1935 self._hash_packages_by_name(self.facade, self.store, "bar")
1936 [foo] = self.facade.get_packages_by_name("foo")
1937 [bar] = self.facade.get_packages_by_name("bar")
1938 self.facade.reload_channels()
1939 self.store.add_task("changer", {"type": "change-package-holds",
1940 "create": [foo.package.id,
1941 bar.package.id],
1942 "operation-id": 123})
1943
1944 def assert_result(result):
1945 self.assertEqual(["foo", "bar"], self.facade.get_package_holds())
1946
1947 result = self.changer.handle_tasks()
1948 return result.addCallback(assert_result)
1949
1950 def test_delete_package_holds_with_identical_version(self):
1951 """
1952 The L{PackageChanger.handle_tasks} method appropriately deletes
1953 holds as requested by the C{change-package-holds} message even
1954 when versions from two different packages are the same.
1955 """
1956 self._add_system_package("foo", version="1.1")
1957 self._add_system_package("bar", version="1.1")
1958 self.facade.reload_channels()
1959 self._hash_packages_by_name(self.facade, self.store, "foo")
1960 self._hash_packages_by_name(self.facade, self.store, "bar")
1961 [foo] = self.facade.get_packages_by_name("foo")
1962 [bar] = self.facade.get_packages_by_name("bar")
1963 self.facade.set_package_hold(foo)
1964 self.facade.set_package_hold(bar)
1965 self.facade.reload_channels()
1966 self.store.add_task("changer", {"type": "change-package-holds",
1967 "delete": [foo.package.id,
1968 bar.package.id],
1969 "operation-id": 123})
1970
1971 def assert_result(result):
1972 self.assertEqual([], self.facade.get_package_holds())
1973
1974 result = self.changer.handle_tasks()
1975 return result.addCallback(assert_result)
1976
1977 def test_change_package_holds_create_already_held(self):
1978 """
1979 If the C{change-package-holds} message requests to add holds for
1980 packages that are already held, the activity succeeds, since the
1981 end result is that the requested package holds are there.
1982 """
1983 self._add_system_package("foo")
1984 self.facade.reload_channels()
1985 self._hash_packages_by_name(self.facade, self.store, "foo")
1986 [foo] = self.facade.get_packages_by_name("foo")
1987 self.facade.set_package_hold(foo)
1988 self.facade.reload_channels()
1989 self.store.add_task("changer", {"type": "change-package-holds",
1990 "create": [foo.package.id],
1991 "operation-id": 123})
1992
1993 def assert_result(result):
1994 self.facade.reload_channels()
1995 self.assertEqual(["foo"], self.facade.get_package_holds())
1996 self.assertIn("Queuing message with change package holds results "
1997 "to exchange urgently.", self.logfile.getvalue())
1998 self.assertMessages(
1999 self.get_pending_messages(),
2000 [{"type": "operation-result",
2001 "operation-id": 123,
2002 "status": SUCCEEDED,
2003 "result-text": "Package holds successfully changed.",
2004 "result-code": 0}])
2005
2006 result = self.changer.handle_tasks()
2007 return result.addCallback(assert_result)
2008
2009 def test_change_package_holds_create_other_version_installed(self):
2010 """
2011 If the C{change-package-holds} message requests to add holds for
2012 packages that have a different version installed than the one
2013 being requested to hold, the activity fails.
2014
2015 The whole activity is failed, meaning that other valid hold
2016 requests won't get processed.
2017 """
2018 self._add_system_package("foo", version="1.0")
2019 self._add_package_to_deb_dir(
2020 self.repository_dir, "foo", version="2.0")
2021 self._add_system_package("bar", version="1.0")
2022 self._add_package_to_deb_dir(
2023 self.repository_dir, "bar", version="2.0")
2024 self.facade.reload_channels()
2025 [foo1, foo2] = sorted(self.facade.get_packages_by_name("foo"))
2026 [bar1, bar2] = sorted(self.facade.get_packages_by_name("bar"))
2027 self.store.set_hash_ids({self.facade.get_package_hash(foo1): 1,
2028 self.facade.get_package_hash(foo2): 2,
2029 self.facade.get_package_hash(bar1): 3,
2030 self.facade.get_package_hash(bar2): 4})
2031 self.facade.reload_channels()
2032 self.store.add_task("changer", {"type": "change-package-holds",
2033 "create": [2, 3],
2034 "operation-id": 123})
2035
2036 def assert_result(result):
2037 self.facade.reload_channels()
2038 self.assertEqual([], self.facade.get_package_holds())
2039 self.assertIn("Queuing message with change package holds results "
2040 "to exchange urgently.", self.logfile.getvalue())
2041 self.assertMessages(
2042 self.get_pending_messages(),
2043 [{"type": "operation-result",
2044 "operation-id": 123,
2045 "status": FAILED,
2046 "result-text": "Package holds not changed, since the" +
2047 " following packages are not installed: 2",
2048 "result-code": 1}])
2049
2050 result = self.changer.handle_tasks()
2051 return result.addCallback(assert_result)
2052
2053 def test_change_package_holds_create_not_installed(self):
2054 """
2055 If the C{change-package-holds} message requests to add holds for
2056 packages that aren't installed, the whole activity is failed. If
2057 multiple holds are specified, those won't be added. There's no
2058 difference between a package that is available in some
2059 repository and a package that the facade doesn't know about at
2060 all.
2061 """
2062 self._add_system_package("foo")
2063 self._add_package_to_deb_dir(self.repository_dir, "bar")
2064 self._add_package_to_deb_dir(self.repository_dir, "baz")
2065 self.facade.reload_channels()
2066 self._hash_packages_by_name(self.facade, self.store, "foo")
2067 self._hash_packages_by_name(self.facade, self.store, "bar")
2068 self._hash_packages_by_name(self.facade, self.store, "baz")
2069 [foo] = self.facade.get_packages_by_name("foo")
2070 [bar] = self.facade.get_packages_by_name("bar")
2071 [baz] = self.facade.get_packages_by_name("baz")
2072 self.store.add_task("changer", {"type": "change-package-holds",
2073 "create": [foo.package.id,
2074 bar.package.id,
2075 baz.package.id],
2076 "operation-id": 123})
2077
2078 def assert_result(result):
2079 self.facade.reload_channels()
2080 self.assertEqual([], self.facade.get_package_holds())
2081 self.assertIn("Queuing message with change package holds results "
2082 "to exchange urgently.", self.logfile.getvalue())
2083 self.assertMessages(
2084 self.get_pending_messages(),
2085 [{"type": "operation-result",
2086 "operation-id": 123,
2087 "status": FAILED,
2088 "result-text": "Package holds not changed, since the "
2089 "following packages are not installed: "
2090 "%s, %s" % tuple(sorted([bar.package.id,
2091 baz.package.id])),
2092 "result-code": 1}])
2093
2094 result = self.changer.handle_tasks()
2095 return result.addCallback(assert_result)
2096
2097 def test_change_package_holds_create_unknown_hash(self):
2098 """
2099 If the C{change-package-holds} message requests to add holds for
2100 packages that the client doesn't know about, it's being treated
2101 as the packages not being installed.
2102 """
2103 self.facade.reload_channels()
2104 self.store.add_task("changer", {"type": "change-package-holds",
2105 "create": [1],
2106 "operation-id": 123})
2107
2108 def assert_result(result):
2109 self.facade.reload_channels()
2110 self.assertEqual([], self.facade.get_package_holds())
2111 self.assertIn("Queuing message with change package holds results "
2112 "to exchange urgently.", self.logfile.getvalue())
2113 self.assertMessages(
2114 self.get_pending_messages(),
2115 [{"type": "operation-result",
2116 "operation-id": 123,
2117 "status": FAILED,
2118 "result-text": "Package holds not changed, since the" +
2119 " following packages are not installed: 1",
2120 "result-code": 1}])
2121
2122 result = self.changer.handle_tasks()
2123 return result.addCallback(assert_result)
2124
2125 def test_change_package_holds_delete_not_held(self):
2126 """
2127 If the C{change-package-holds} message requests to remove holds
2128 for packages that aren't held, the activity succeeds if the
2129 right version is installed, since the end result is that the
2130 hold is removed.
2131 """
2132 self._add_package_to_deb_dir(self.repository_dir, "foo")
2133 self.facade.reload_channels()
2134 self._hash_packages_by_name(self.facade, self.store, "foo")
2135 [foo] = self.facade.get_packages_by_name("foo")
2136 self.store.add_task("changer", {"type": "change-package-holds",
2137 "delete": [foo.package.id],
2138 "operation-id": 123})
2139
2140 def assert_result(result):
2141 self.facade.reload_channels()
2142 self.assertEqual([], self.facade.get_package_holds())
2143 self.assertIn("Queuing message with change package holds results "
2144 "to exchange urgently.", self.logfile.getvalue())
2145 self.assertMessages(
2146 self.get_pending_messages(),
2147 [{"type": "operation-result",
2148 "operation-id": 123,
2149 "status": SUCCEEDED,
2150 "result-text": "Package holds successfully changed.",
2151 "result-code": 0}])
2152
2153 result = self.changer.handle_tasks()
2154 return result.addCallback(assert_result)
2155
2156 def test_change_package_holds_delete_different_version_held(self):
2157 """
2158 If the C{change-package-holds} message requests to remove holds
2159 for packages that aren't held, the activity succeeds if the
2160 right version is installed, since the end result is that the
2161 hold is removed.
2162 """
2163 self._add_system_package("foo", version="1.0")
2164 self._add_package_to_deb_dir(
2165 self.repository_dir, "foo", version="2.0")
2166 self.facade.reload_channels()
2167 [foo1, foo2] = sorted(self.facade.get_packages_by_name("foo"))
2168 self.facade.set_package_hold(foo1)
2169 self.store.set_hash_ids({self.facade.get_package_hash(foo1): 1,
2170 self.facade.get_package_hash(foo2): 2})
2171 self.facade.reload_channels()
2172 self.store.add_task("changer", {"type": "change-package-holds",
2173 "delete": [2],
2174 "operation-id": 123})
2175
2176 def assert_result(result):
2177 self.facade.reload_channels()
2178 self.assertEqual(["foo"], self.facade.get_package_holds())
2179 self.assertIn("Queuing message with change package holds results "
2180 "to exchange urgently.", self.logfile.getvalue())
2181 self.assertMessages(
2182 self.get_pending_messages(),
2183 [{"type": "operation-result",
2184 "operation-id": 123,
2185 "status": SUCCEEDED,
2186 "result-text": "Package holds successfully changed.",
2187 "result-code": 0}])
2188
2189 result = self.changer.handle_tasks()
2190 return result.addCallback(assert_result)
2191
2192 def test_change_package_holds_delete_unknown_hash(self):
2193 """
2194 If the C{change-package-holds} message requests to remove holds
2195 for packages that aren't known by the client, the activity
2196 succeeds, since the end result is that the package isn't
2197 held at that version.
2198 """
2199 self.store.add_task("changer", {"type": "change-package-holds",
2200 "delete": [1],
2201 "operation-id": 123})
2202
2203 def assert_result(result):
2204 self.facade.reload_channels()
2205 self.assertEqual([], self.facade.get_package_holds())
2206 self.assertIn("Queuing message with change package holds results "
2207 "to exchange urgently.", self.logfile.getvalue())
2208 self.assertMessages(
2209 self.get_pending_messages(),
2210 [{"type": "operation-result",
2211 "operation-id": 123,
2212 "status": SUCCEEDED,
2213 "result-text": "Package holds successfully changed.",
2214 "result-code": 0}])
2215
2216 result = self.changer.handle_tasks()
2217 return result.addCallback(assert_result)
2218
2219 def test_change_package_holds_delete_not_installed(self):
2220 """
2221 If the C{change-package-holds} message requests to remove holds
2222 for packages that aren't installed, the activity succeeds, since
2223 the end result is still that the package isn't held at the
2224 requested version.
2225 """
2226 self._add_system_package("foo")
2227 self.facade.reload_channels()
2228 self._hash_packages_by_name(self.facade, self.store, "foo")
2229 [foo] = self.facade.get_packages_by_name("foo")
2230 self.store.add_task("changer", {"type": "change-package-holds",
2231 "delete": [foo.package.id],
2232 "operation-id": 123})
2233
2234 def assert_result(result):
2235 self.facade.reload_channels()
2236 self.assertEqual([], self.facade.get_package_holds())
2237 self.assertIn("Queuing message with change package holds results "
2238 "to exchange urgently.", self.logfile.getvalue())
2239 self.assertMessages(
2240 self.get_pending_messages(),
2241 [{"type": "operation-result",
2242 "operation-id": 123,
2243 "status": SUCCEEDED,
2244 "result-text": "Package holds successfully changed.",
2245 "result-code": 0}])
2246
2247 result = self.changer.handle_tasks()
2248 return result.addCallback(assert_result)
2249
2250 def test_change_package_locks(self):
2251 """
2252 If C{AptFacade} is used, the L{PackageChanger.handle_tasks}
2253 method fails the activity, since it can't add or remove locks because
2254 apt doesn't support this.
2255 """
2256 self.store.add_task("changer", {"type": "change-package-locks",
2257 "create": [("foo", ">=", "1.0")],
2258 "delete": [("bar", None, None)],
2259 "operation-id": 123})
2260
2261 def assert_result(result):
2262 self.assertMessages(
2263 self.get_pending_messages(),
2264 [{"type": "operation-result",
2265 "operation-id": 123,
2266 "status": FAILED,
2267 "result-text": "This client doesn't support package locks.",
2268 "result-code": 1}])
2269
2270 result = self.changer.handle_tasks()
2271 return result.addCallback(assert_result)
2272
2273 def test_change_packages_with_binaries_removes_binaries(self):
2274 """
2275 After the C{change-packages} handler has installed the binaries,
2276 the binaries and the internal facade deb source is removed.
2277 """
2278 self.store.add_task("changer",
2279 {"type": "change-packages", "install": [2],
2280 "binaries": [(HASH2, 2, PKGDEB2)],
2281 "operation-id": 123})
2282
2283 def return_good_result(self):
2284 return "Yeah, I did whatever you've asked for!"
2285 self.replace_perform_changes(return_good_result)
2286
2287 result = self.changer.handle_tasks()
2288
2289 def got_result(result):
2290 self.assertMessages(self.get_pending_messages(),
2291 [{"operation-id": 123,
2292 "result-code": 1,
2293 "result-text": "Yeah, I did whatever you've "
2294 "asked for!",
2295 "type": "change-packages-result"}])
2296 self.assertEqual([], os.listdir(self.config.binaries_path))
2297 self.assertFalse(
2298 os.path.exists(self.facade._get_internal_sources_list()))
2299
2300 return result.addCallback(got_result)
2301>>>>>>> MERGE-SOURCE
13732302
=== modified file 'landscape/package/tests/test_facade.py'
--- landscape/package/tests/test_facade.py 2012-06-19 15:12:07 +0000
+++ landscape/package/tests/test_facade.py 2014-11-19 18:22:39 +0000
@@ -1,19 +1,53 @@
1import os1import os
2import textwrap2<<<<<<< TREE
3import tempfile3import textwrap
44import tempfile
5import apt_pkg5
6from apt.package import Package6import apt_pkg
7from aptsources.sourceslist import SourcesList7from apt.package import Package
88from aptsources.sourceslist import SourcesList
9from landscape.constants import UBUNTU_PATH9
10from landscape.lib.fs import read_file, create_file10from landscape.constants import UBUNTU_PATH
11from landscape.lib.fs import read_file, create_file
12=======
13import re
14import sys
15import textwrap
16import tempfile
17
18try:
19 import smart
20 from smart.control import Control
21 from smart.cache import Provides
22 from smart.const import NEVER, ALWAYS
23except ImportError:
24 # Smart is optional if AptFacade is being used.
25 pass
26
27import apt_pkg
28from apt.package import Package
29from aptsources.sourceslist import SourcesList
30
31from twisted.internet import reactor
32from twisted.internet.defer import Deferred
33from twisted.internet.utils import getProcessOutputAndValue
34
35from landscape.constants import UBUNTU_PATH
36from landscape.lib.fs import read_file, create_file
37from landscape.package import facade as facade_module
38>>>>>>> MERGE-SOURCE
11from landscape.package.facade import (39from landscape.package.facade import (
40<<<<<<< TREE
12 TransactionError, DependencyError, ChannelError, AptFacade)41 TransactionError, DependencyError, ChannelError, AptFacade)
42=======
43 TransactionError, DependencyError, ChannelError, SmartError, AptFacade,
44 has_new_enough_apt)
45>>>>>>> MERGE-SOURCE
1346
14from landscape.tests.mocker import ANY47from landscape.tests.mocker import ANY
15from landscape.tests.helpers import LandscapeTest, EnvironSaverHelper48from landscape.tests.helpers import LandscapeTest, EnvironSaverHelper
16from landscape.package.tests.helpers import (49from landscape.package.tests.helpers import (
50<<<<<<< TREE
17 HASH1, HASH2, HASH3, PKGNAME1, PKGNAME2, PKGNAME3,51 HASH1, HASH2, HASH3, PKGNAME1, PKGNAME2, PKGNAME3,
18 PKGDEB1, PKGNAME_MINIMAL, PKGDEB_MINIMAL,52 PKGDEB1, PKGNAME_MINIMAL, PKGDEB_MINIMAL,
19 create_deb, AptFacadeHelper,53 create_deb, AptFacadeHelper,
@@ -622,42 +656,2524 @@
622 ["amd64-package"],656 ["amd64-package"],
623 sorted(version.package.name657 sorted(version.package.name
624 for version in self.facade.get_packages()))658 for version in self.facade.get_packages()))
625659=======
626 def test_get_package_skeleton(self):660 SmartFacadeHelper, HASH1, HASH2, HASH3, PKGNAME1, PKGNAME2, PKGNAME3,
627 """661 PKGNAME4, PKGDEB4, PKGDEB1, PKGNAME_MINIMAL, PKGDEB_MINIMAL,
628 C{get_package_skeleton} returns a C{PackageSkeleton} for a662 create_full_repository, create_deb, AptFacadeHelper,
629 package. By default extra information is included, but it's663 create_simple_repository)
630 possible to specify that only basic information should be664
631 included.665
632666class FakeOwner(object):
633 The information about the package are unicode strings.667 """Fake Owner object that apt.progress.text.AcquireProgress expects."""
634 """668
635 deb_dir = self.makeDir()669 def __init__(self, filesize, error_text=""):
636 create_simple_repository(deb_dir)670 self.id = None
637 self.facade.add_channel_deb_dir(deb_dir)671 self.filesize = filesize
638 self.facade.reload_channels()672 self.complete = False
639 [pkg1] = self.facade.get_packages_by_name("name1")673 self.status = None
640 [pkg2] = self.facade.get_packages_by_name("name2")674 self.STAT_DONE = object()
641 skeleton1 = self.facade.get_package_skeleton(pkg1)675 self.error_text = error_text
642 self.assertTrue(isinstance(skeleton1.summary, unicode))676
643 self.assertEqual("Summary1", skeleton1.summary)677
644 skeleton2 = self.facade.get_package_skeleton(pkg2, with_info=False)678class FakeFetchItem(object):
645 self.assertIs(None, skeleton2.summary)679 """Fake Item object that apt.progress.text.AcquireProgress expects."""
646 self.assertEqual(HASH1, skeleton1.get_hash())680
647 self.assertEqual(HASH2, skeleton2.get_hash())681 def __init__(self, owner, description):
648682 self.owner = owner
649 def test_get_package_hash(self):683 self.description = description
650 """684
651 C{get_package_hash} returns the hash for a given package.685
652 """686class AptFacadeTest(LandscapeTest):
653 deb_dir = self.makeDir()687
654 create_simple_repository(deb_dir)688 if not has_new_enough_apt:
655 self.facade.add_channel_deb_dir(deb_dir)689 skip = "Can't use AptFacade on hardy"
656 self.facade.reload_channels()690
657 [pkg] = self.facade.get_packages_by_name("name1")691 helpers = [AptFacadeHelper, EnvironSaverHelper]
658 self.assertEqual(HASH1, self.facade.get_package_hash(pkg))692
659 [pkg] = self.facade.get_packages_by_name("name2")693 def version_sortkey(self, version):
660 self.assertEqual(HASH2, self.facade.get_package_hash(pkg))694 """Return a key by which a Version object can be sorted."""
695 return (version.package, version)
696
697 def patch_cache_commit(self, commit_function=None):
698 """Patch the apt cache's commit function as to not call dpkg.
699
700 @param commit_function: A function accepting two parameters,
701 fetch_progress and install_progress.
702 """
703
704 def commit(fetch_progress, install_progress):
705 install_progress.dpkg_exited = True
706 if commit_function:
707 commit_function(fetch_progress, install_progress)
708
709 self.facade._cache.commit = commit
710
711 def test_default_root(self):
712 """
713 C{AptFacade} can be created by not providing a root directory,
714 which means that the currently configured root (most likely /)
715 will be used.
716 """
717 original_dpkg_root = apt_pkg.config.get("Dir")
718 facade = AptFacade()
719 self.assertEqual(original_dpkg_root, apt_pkg.config.get("Dir"))
720 # Make sure that at least reloading the channels work.
721 facade.reload_channels()
722
723 def test_custom_root_create_required_files(self):
724 """
725 If a custom root is passed to the constructor, the directory and
726 files that apt expects to be there will be created.
727 """
728 root = self.makeDir()
729 AptFacade(root=root)
730 self.assertTrue(os.path.exists(os.path.join(root, "etc", "apt")))
731 self.assertTrue(
732 os.path.exists(os.path.join(root, "etc", "apt", "sources.list.d")))
733 self.assertTrue(os.path.exists(
734 os.path.join(root, "var", "cache", "apt", "archives", "partial")))
735 self.assertTrue(os.path.exists(
736 os.path.join(root, "var", "lib", "apt", "lists", "partial")))
737 self.assertTrue(
738 os.path.exists(os.path.join(root, "var", "lib", "dpkg", "status")))
739
740 def test_no_system_packages(self):
741 """
742 If the dpkg status file is empty, not packages are reported by
743 C{get_packages()}.
744 """
745 self.facade.reload_channels()
746 self.assertEqual([], list(self.facade.get_packages()))
747
748 def test_get_packages_single_version(self):
749 """
750 If the dpkg status file contains some packages, those packages
751 are reported by C{get_packages()}.
752 """
753 self._add_system_package("foo")
754 self._add_system_package("bar")
755 self.facade.reload_channels()
756 self.assertEqual(
757 ["bar", "foo"],
758 sorted(version.package.name
759 for version in self.facade.get_packages()))
760
761 def test_get_packages_multiple_version(self):
762 """
763 If there are multiple versions of a package, C{get_packages()}
764 returns one object per version.
765 """
766 deb_dir = self.makeDir()
767 self._add_system_package("foo", version="1.0")
768 self._add_package_to_deb_dir(deb_dir, "foo", version="1.5")
769 self.facade.add_channel_apt_deb("file://%s" % deb_dir, "./")
770 self.facade.reload_channels()
771 self.assertEqual(
772 [("foo", "1.0"), ("foo", "1.5")],
773 sorted((version.package.name, version.version)
774 for version in self.facade.get_packages()))
775
776 def test_get_packages_multiple_architectures(self):
777 """
778 If there are multiple architectures for a package, only the native
779 architecture is reported by C{get_packages()}.
780 """
781 apt_pkg.config.clear("APT::Architectures")
782 apt_pkg.config.set("APT::Architecture", "amd64")
783 apt_pkg.config.set("APT::Architectures::", "amd64")
784 apt_pkg.config.set("APT::Architectures::", "i386")
785 facade = AptFacade(apt_pkg.config.get("Dir"))
786
787 self._add_system_package("foo", version="1.0", architecture="amd64")
788 self._add_system_package("bar", version="1.1", architecture="i386")
789 facade.reload_channels()
790 self.assertEqual([("foo", "1.0")],
791 [(version.package.name, version.version)
792 for version in facade.get_packages()])
793
794 def test_add_channel_apt_deb_without_components(self):
795 """
796 C{add_channel_apt_deb()} adds a new deb URL to a file in
797 sources.list.d.
798
799 If no components are given, nothing is written after the dist.
800 """
801 self.facade.add_channel_apt_deb("http://example.com/ubuntu", "lucid")
802 list_filename = (
803 self.apt_root +
804 "/etc/apt/sources.list.d/_landscape-internal-facade.list")
805 sources_contents = read_file(list_filename)
806 self.assertEqual(
807 "deb http://example.com/ubuntu lucid\n",
808 sources_contents)
809
810 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: