debian/0000755000000000000000000000000013404451207007166 5ustar debian/README.source0000644000000000000000000000104413404451062011343 0ustar When making a revision to this package, please update the *last* digit in the Debian version number. e.g. 1.0.29-0ubuntu0.9.04.0 should become 1.0.29-0ubuntu0.9.04.1 In addition, when you build a package for a new client version, it would be appreciated if you also updated the UPSTREAM_VERSION variable in landscape/__init__.py to include the entire new client version number. This helps us keep track of exact version of clients in use. There's no need to update the DEBIAN_REVISION variable, as it gets automatically set at build time. debian/landscape-client.postrm0000644000000000000000000000241513404451062013643 0ustar #!/bin/sh set -e # summary of how this script can be called: # * `remove' # * `purge' # * `upgrade' # * `failed-upgrade' # * `abort-install' # * `abort-install' # * `abort-upgrade' # * `disappear' # # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package case "$1" in purge) LOG_DIR=/var/log/landscape DATA_DIR=/var/lib/landscape rm -f "/etc/default/landscape-client" rm -f "${LOG_DIR}/manager.log"* rm -f "${LOG_DIR}/monitor.log"* rm -f "${LOG_DIR}/broker.log"* rm -f "${LOG_DIR}/watchdog.log"* rm -f "${LOG_DIR}/package-reporter.log"* rm -f "${LOG_DIR}/package-changer.log"* rm -rf "${DATA_DIR}/client" rm -rf "${DATA_DIR}/.gnupg" ;; remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) ;; *) echo "postrm called with unknown argument \`$1'" >&2 exit 1 ;; esac # dh_installdeb will replace this with shell code automatically # generated by other debhelper scripts. #DEBHELPER# exit 0 debian/watch0000644000000000000000000000024113404451062010213 0ustar version=3 https://launchpad.net/landscape-client/+download https://launchpad.net/landscape-client/[^/]+/[^/]+/\+download/landscape-client-(.*)\.tar\.(?:bz2|gz) debian/landscape-client-ui.install0000644000000000000000000000113713404451062014400 0ustar usr/bin/landscape-client-settings-mechanism usr/bin/landscape-client-registration-mechanism usr/bin/landscape-client-settings-ui usr/share/dbus-1/system-services/com.canonical.LandscapeClientSettings.service usr/share/dbus-1/system-services/com.canonical.LandscapeClientRegistration.service usr/share/polkit-1/actions/com.canonical.LandscapeClientSettings.policy etc/dbus-1/system.d/com.canonical.LandscapeClientSettings.conf etc/dbus-1/system.d/com.canonical.LandscapeClientRegistration.conf etc/dbus-1/system.d/landscape.conf usr/share/glib-2.0/schemas/com.canonical.landscape-client-settings.gschema.xml debian/source.lintian-overrides0000644000000000000000000000112213404451062014041 0ustar # we use dh_python or dh_python2 depending on the ubuntu release # the package is being built on, this is detected dynamically # in the rules file landscape-client source: dh_python-is-obsolete # the package has to build on lucid, where the standards version # is 3.8.2 landscape-client source: ancient-standards-version # it's a bug that should be fixed in quantal landscape-client source: unknown-field-in-dsc original-maintainer # this is only used in a very specific client upgrade from # the dbus to the amp version landscape-client: start-stop-daemon-in-maintainer-script postinst:130 debian/compat0000644000000000000000000000000213404451062010363 0ustar 7 debian/landscape-client-ui-install.install0000644000000000000000000000026713404451062016047 0ustar usr/bin/landscape-client-ui-install usr/share/applications/landscape-client-settings.desktop usr/share/icons/hicolor/scalable/apps/preferences-management-service.svg usr/share/locale debian/landscape-common.install0000644000000000000000000000013013404451062013767 0ustar usr/lib/python* usr/bin/landscape-sysinfo usr/share/landscape/landscape-sysinfo.wrapper debian/landscape-client.init0000644000000000000000000000502613404451062013263 0ustar #!/bin/sh ### BEGIN INIT INFO # Provides: landscape-client # Required-Start: $local_fs $remote_fs # Required-Stop: $local_fs $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Landscape client daemons # Description: The Landscape client daemons are needed so the # Landscape server can manage a computer. ### END INIT INFO LANDSCAPE_DEFAULTS=/etc/default/landscape-client NAME=landscape-client DAEMON=/usr/bin/$NAME PIDDIR=/var/run/landscape SOCKETDIR=/var/lib/landscape/client/sockets PIDFILE=$PIDDIR/$NAME.pid RUN=0 # overridden in /etc/default/landscape-client CLOUD=0 # overridden in /etc/default/landscape-client DAEMON_GROUP=landscape export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" [ -f $DAEMON ] || exit 0 . /lib/lsb/init-functions [ -f $LANDSCAPE_DEFAULTS ] && . $LANDSCAPE_DEFAULTS check_config () { # This $RUN check should match the semantics of # l.sysvconfig.SysVConfig.is_configured_to_run. if [ $RUN -eq 0 ]; then echo "$NAME is not configured, please run landscape-config." exit 0 fi } case "$1" in start) check_config log_daemon_msg "Starting the $NAME daemon" install --owner=landscape --directory $PIDDIR # Cleanup leftover sockets if there's no other landscape process # running. This shouldn't be usually necessary, but it can # happen in case the client crashed badly and the socket points # to a process with the same PID. if [ -d "${SOCKETDIR:?}" ]; then if ! pidofproc -p "$PIDFILE" "$DAEMON" > /dev/null; then find "${SOCKETDIR:?}" -maxdepth 1 -type s -print0 | xargs -r0 rm -f find "${SOCKETDIR:?}" -maxdepth 1 -type l -print0 | xargs -r0 rm -f fi fi FULL_COMMAND="start-stop-daemon --start --quiet --oknodo --startas $DAEMON --pidfile $PIDFILE -g $DAEMON_GROUP -- --daemon --pid-file $PIDFILE" if [ x"$DAEMON_USER" != x ]; then sudo -u $DAEMON_USER $FULL_COMMAND else $FULL_COMMAND fi log_end_msg $? ;; stop) log_daemon_msg "Stopping $NAME daemon" start-stop-daemon --retry 30 --stop --quiet --pidfile $PIDFILE log_end_msg $? ;; status) # We want to maintain backward compatibility with Dapper, # so we're not going to use status_of_proc() pidofproc -p $PIDFILE $DAEMON >/dev/null status=$? if [ $status -eq 0 ]; then log_success_msg "$NAME is running" else log_failure_msg "$NAME is not running" fi exit $status ;; restart|force-reload) $0 stop && $0 start ;; *) echo "Usage: $0 {start|stop|status|restart|force-reload}" exit 1 ;; esac exit 0 debian/landscape-client.config0000755000000000000000000000340713404451062013571 0ustar #!/bin/sh PACKAGE=landscape-client CONFIGFILE=/etc/landscape/client.conf set -e . /usr/share/debconf/confmodule var_in_file() { var="$1" file="$2" line=$(grep "^$var\s*=\s*" "$file" 2>/dev/null || true) echo "$line" } get_var_from_file() { var="$1" file="$2" val=$(grep "^$var\s*=\s*" "$file" 2>/dev/null | tail -n1 | sed "s/^.*=\s*//") echo "$val" } update_var() { var="$1" file="$2" line=$(var_in_file $var $file) if [ -n "$line" ]; then val=$(get_var_from_file $var $file) # Store value from config file in debconf. db_set $PACKAGE/$var $val fi } # Load config file, if it exists. if [ -e $CONFIGFILE ]; then # Replace old registration_password to registration_key sed -i -r 's/^registration_password[[:blank:]]*=/registration_key =/' $CONFIGFILE # Config file is "ini" type, not shell, so we cannot source it # If a setting is defined in the config file, update it in debconf # db. update_var "computer_title" "$CONFIGFILE" update_var "account_name" "$CONFIGFILE" update_var "registration_key" "$CONFIGFILE" update_var "url" "$CONFIGFILE" update_var "exchange_interval" "$CONFIGFILE" update_var "urgent_exchange_interval" "$CONFIGFILE" update_var "ping_url" "$CONFIGFILE" update_var "ping_interval" "$CONFIGFILE" update_var "http_proxy" "$CONFIGFILE" update_var "https_proxy" "$CONFIGFILE" update_var "tags" "$CONFIGFILE" fi # Ask questions. # Do debconf configuration db_get $PACKAGE/register_system if [ "$RET" = true ]; then priority=high else priority=medium fi db_input "$priority" $PACKAGE/computer_title || true db_input "$priority" $PACKAGE/account_name || true db_input "$priority" $PACKAGE/registration_key || true db_go || true debian/patches/0000755000000000000000000000000013404451062010614 5ustar debian/patches/ubuntu-mate-revno-823.diff0000644000000000000000000000152713404451062015362 0ustar Description: Landscape Client UI should show in MATE. Merge show-in-mate [f=] [r=tealeg,tribaal] [a=Ubuntu MATE Developers] Let the landscape-client-ui show up in Ubuntu MATE Author: Martin Wimpress Origin: upstream, http://bazaar.launchpad.net/~landscape/landscape-client/trunk/revision/823 Bug: https://bugs.launchpad.net/bugs/1653929 Bug-Ubuntu: https://bugs.launchpad.net/bugs/1653929 --- a/applications/landscape-client-settings.desktop.in +++ b/applications/landscape-client-settings.desktop.in @@ -7,7 +7,7 @@ Type=Application StartupNotify=true Categories=GNOME;GTK;Settings;X-GNOME-SystemSettings;X-GNOME-Settings-Panel;X-Unity-Settings-Panel; -OnlyShowIn=GNOME;Unity; +OnlyShowIn=GNOME;Unity;MATE; X-Unity-Settings-Panel=landscape X-GNOME-Settings-Panel=landscape X-Ubuntu-Gettext-Domain=landscape-client debian/patches/bug-1532887-revno-825.diff0000644000000000000000000001447013404451062014533 0ustar Description: Frequent resyncs after users tab fix. Merge revert-forced-users-update [f=1532887] [r=ack,chad.smith] [a=Fernando Correa Neto] Reverts the change that introduced forced user database updates in the client. Author: Fernando Correa Neto Origin: upstream, http://bazaar.launchpad.net/~landscape/landscape-client/trunk/revision/825 Bug: https://bugs.launchpad.net/bugs/1532887 Bug-Ubuntu: https://bugs.launchpad.net/bugs/1532887 --- a/landscape/monitor/tests/test_usermonitor.py +++ b/landscape/monitor/tests/test_usermonitor.py @@ -206,28 +206,6 @@ result.addCallback(got_result) return result - def test_run_passes_force_reset_as_true(self): - """ - The run method will pass True for the underlying _run_detect_changes - method to force a database reset. - """ - original_run_detect_changes = self.plugin._run_detect_changes - - def fake_run_detect_changes(operation_id=None, force_reset=False): - return force_reset - - self.plugin._run_detect_changes = fake_run_detect_changes - - def got_result(result): - self.assertTrue(result) - - self.broker_service.message_store.set_accepted_types(["users"]) - self.monitor.add(self.plugin) - result = self.plugin.run() - result.addCallback(got_result) - self.plugin._run_detect_changes = original_run_detect_changes - return result - def test_run_interval(self): """ L{UserMonitor.register} calls the C{register} method on it's --- a/landscape/monitor/usermonitor.py +++ b/landscape/monitor/usermonitor.py @@ -27,7 +27,7 @@ def register(self, registry): super(UserMonitor, self).register(registry) - self.call_on_accepted("users", self._run_detect_changes, None, True) + self.call_on_accepted("users", self._run_detect_changes, None) self._publisher = ComponentPublisher(self, self.registry.reactor, self.registry.config) @@ -49,11 +49,9 @@ return self.registry.broker.call_if_accepted( "users", self._run_detect_changes, operation_id) - def run(self, operation_id=None): - return self.registry.broker.call_if_accepted( - "users", self._run_detect_changes, operation_id, True) + run = detect_changes - def _run_detect_changes(self, operation_id=None, force_reset=False): + def _run_detect_changes(self, operation_id=None): """ If changes are detected an C{urgent-exchange} is fired to send updates to the server immediately. @@ -68,7 +66,7 @@ # We'll skip checking the locked users if we're in monitor-only mode. if getattr(self.registry.config, "monitor_only", False): result = maybeDeferred(self._detect_changes, - [], operation_id, force_reset) + [], operation_id) else: def get_locked_usernames(user_manager): @@ -81,13 +79,11 @@ result = user_manager_connector.connect() result.addCallback(get_locked_usernames) result.addCallback(disconnect) - result.addCallback(self._detect_changes, operation_id, force_reset) - result.addErrback(lambda f: self._detect_changes([], operation_id, - force_reset)) + result.addCallback(self._detect_changes, operation_id) + result.addErrback(lambda f: self._detect_changes([], operation_id)) return result - def _detect_changes(self, locked_users, operation_id=None, - force_reset=False): + def _detect_changes(self, locked_users, operation_id=None): def update_snapshot(result): changes.snapshot() @@ -99,7 +95,7 @@ self._provider.locked_users = locked_users changes = UserChanges(self._persist, self._provider) - message = changes.create_diff(force_reset=force_reset) + message = changes.create_diff() if message: message["type"] = "users" --- a/landscape/user/changes.py +++ b/landscape/user/changes.py @@ -20,17 +20,10 @@ # that from the necessary places. self._refresh() - def _refresh(self, force_reset=False): - """Load the previous snapshot and update current data. - - @param force_reset: Whether the user and group databases should be - reset. - """ + def _refresh(self): + """Load the previous snapshot and update current data.""" self._old_users = self._persist.get("users", {}) self._old_groups = self._persist.get("groups", {}) - if force_reset: - self.clear() - self._new_users = self._create_index( "username", self._provider.get_users()) self._new_groups = self._create_index( @@ -59,13 +52,13 @@ index[data[key]] = data return index - def create_diff(self, force_reset=False): + def create_diff(self): """Returns the changes since the last snapshot. - + See landscape.message_schemas.USERS schema for a description of the dictionary returned by this method. """ - self._refresh(force_reset=force_reset) + self._refresh() changes = {} changes.update(self._detect_user_changes()) changes.update(self._detect_group_changes()) --- a/landscape/user/tests/test_changes.py +++ b/landscape/user/tests/test_changes.py @@ -105,21 +105,6 @@ changes.snapshot() self.assertEqual(changes.create_diff(), {}) - def test_create_diff_with_force_reset(self): - """ - UserChanges.create_diff accepts a force_reset parameter that can be - used to force a database reset. - """ - users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/sh")] - groups = [("webdev", "x", 1000, ["jdoe"])] - provider = FakeUserProvider(users=users, groups=groups) - FakeUserInfo(provider=provider) - - changes = UserChanges(self.persist, provider) - changes.create_diff() - changes.snapshot() - self.assertEqual({}, changes.create_diff(force_reset=True)) - def test_add_user(self): """ L{UserChanges.create_diff} should report new users created debian/patches/series0000644000000000000000000000133313404451062012031 0ustar ignore-backports-1668583.diff bug-1409700-revno-807.diff bug-1398090-revno-808.diff bug-1409700-revno-812.diff bug-1428826-revno-813.diff bug-1429888-revno-814.diff bug-1434546-revno-815.diff ubuntu-mate-revno-823.diff bug-1508110-revno-824.diff bug-1532887-revno-825.diff bug-1508110-revno-826.diff bug-1508110-revno-829.diff package-reporter-proxy-1531150.diff iso-configuration-1699789.diff autoremovable-report-1208393.diff config-no-reregister-1618483.diff set-vm-info-to-kvm-for-aws-C5-instances.patch set-vm-info-to-kvm-for-digitalocean-instances.patch detect-cloudstack-kvm-1754073.patch 1699179-release-upgrade-check.diff nutanix-kvm.patch release-upgrade-success.patch post-upgrade-reboot.patch 1616116-resync-loop.patch debian/patches/1616116-resync-loop.patch0000644000000000000000000001324013404451062014732 0ustar Description: Clear hash id database on package resync. This should avoid loop when inconsistencies are present in the database through either corruption or server-restore. Author: Simon Poirier Origin: upstream, https://github.com/CanonicalLtd/landscape-client/commit/666cd32dbdedcc29c90154015bbe2ea0e52924e8 Bug-Ubuntu: https://bugs.launchpad.net/bugs/1616116 Last-Update: 2018-11-23 Index: sru/landscape/package/reporter.py =================================================================== --- sru.orig/landscape/package/reporter.py 2018-11-29 18:07:37.002436855 -0500 +++ sru/landscape/package/reporter.py 2018-11-29 18:07:36.998436837 -0500 @@ -385,7 +385,10 @@ return result + @inlineCallbacks def _handle_resynchronize(self): + self._store.clear_hash_ids() + yield self._remove_hash_id_db() self._store.clear_available() self._store.clear_available_upgrades() self._store.clear_installed() @@ -393,8 +396,6 @@ self._store.clear_hash_id_requests() self._store.clear_autoremovable() - return succeed(None) - def _handle_unknown_packages(self, hashes): self._facade.ensure_channels_reloaded() @@ -430,6 +431,18 @@ return result + def _remove_hash_id_db(self): + + def _remove_it(hash_id_db_filename): + if hash_id_db_filename and os.path.exists(hash_id_db_filename): + logging.warning( + "Removing cached hash=>id database %s", + hash_id_db_filename) + os.remove(hash_id_db_filename) + result = self._determine_hash_id_db_filename() + result.addCallback(_remove_it) + return result + def remove_expired_hash_id_requests(self): now = time.time() timeout = now - HASH_ID_REQUEST_TIMEOUT Index: sru/landscape/package/tests/test_reporter.py =================================================================== --- sru.orig/landscape/package/tests/test_reporter.py 2018-11-29 18:07:37.002436855 -0500 +++ sru/landscape/package/tests/test_reporter.py 2018-11-29 18:07:36.998436837 -0500 @@ -1252,10 +1252,11 @@ finally: sys.argv = saved_argv + @inlineCallbacks def test_resynchronize(self): """ When a resynchronize task arrives, the reporter should clear - out all the data in the package store, except the hash ids. + out all the data in the package store, including the hash ids. This is done in the reporter so that we know it happens when no other reporter is possibly running at the same time. @@ -1268,6 +1269,16 @@ self.facade.reload_channels() message_store = self.broker_service.message_store message_store.set_accepted_types(["package-locks"]) + + # create hash_id file + message_store.set_server_uuid("uuid") + self.facade.set_arch("arch") + hash_id_file = os.path.join( + self.config.hash_id_directory, "uuid_codename_arch") + os.makedirs(self.config.hash_id_directory) + with open(hash_id_file, "w"): + pass + self.store.set_hash_ids({foo_hash: 3, HASH2: 4}) self.store.add_available([1]) self.store.add_available_upgrades([2]) @@ -1291,30 +1302,33 @@ self.store.add_task("reporter", {"type": "resynchronize"}) deferred = self.reporter.run() - - def check_result(result): - # The hashes should not go away. - hash1 = self.store.get_hash_id(foo_hash) - hash2 = self.store.get_hash_id(HASH2) - self.assertEqual([hash1, hash2], [3, 4]) - - # But the other data should. - self.assertEqual(self.store.get_available_upgrades(), []) - - # After running the resychronize task, detect_packages_changes is - # called, and the existing known hashes are made available. - self.assertEqual(self.store.get_available(), [4]) - self.assertEqual(self.store.get_installed(), [3]) - self.assertEqual(self.store.get_locked(), [3]) - - # The two original hash id requests are gone, and a new hash id - # request should be detected for HASH3. - [request] = self.store.iter_hash_id_requests() - self.assertEqual(request.hashes, [HASH3, HASH1]) - - deferred.addCallback(check_result) self.reactor.advance(0) - return deferred + with mock.patch( + "landscape.package.taskhandler.parse_lsb_release", + side_effect=lambda _: {"code-name": "codename"} + ): + yield deferred + + # The hashes should go away to avoid loops + # when server gets restored to an early state. + hash1 = self.store.get_hash_id(foo_hash) + hash2 = self.store.get_hash_id(HASH2) + self.assertNotEqual(hash1, 3) + self.assertNotEqual(hash2, 4) + self.assertFalse(os.path.exists(hash_id_file)) + self.assertEqual(self.store.get_available_upgrades(), []) + + # After running the resychronize task, the hash db is empty, + # thus detect_packages_changes will generate an empty set until + # next run when hash-id db is populated again. + self.assertEqual(self.store.get_available(), []) + self.assertEqual(self.store.get_installed(), []) + self.assertEqual(self.store.get_locked(), []) + + # The two original hash id requests are gone, and a new hash id + # request should be detected for HASH3. + [request] = self.store.iter_hash_id_requests() + self.assertEqual(request.hashes, [HASH3, HASH2, foo_hash, HASH1]) def test_run_apt_update(self): """ debian/patches/ignore-backports-1668583.diff0000644000000000000000000001531013404451062015561 0ustar Index: landscape-client-14.12/landscape/package/reporter.py =================================================================== --- landscape-client-14.12.orig/landscape/package/reporter.py +++ landscape-client-14.12/landscape/package/reporter.py @@ -12,6 +12,7 @@ from landscape.lib.sequenceranges import from landscape.lib.twisted_util import gather_results, spawn_process from landscape.lib.fetch import fetch_async from landscape.lib.fs import touch_file +from landscape.lib.lsb_release import parse_lsb_release, LSB_RELEASE_FILENAME from landscape.lib import bpickle from landscape.package.taskhandler import ( @@ -507,8 +508,26 @@ class PackageReporter(PackageTaskHandler current_available = set() current_upgrades = set() current_locked = set() + lsb = parse_lsb_release(LSB_RELEASE_FILENAME) + backports_archive = "{}-backports".format(lsb["code-name"]) for package in self._facade.get_packages(): + # Don't include package versions from the official backports + # archive. The backports archive is enabled by default since + # xenial with a pinning policy of 100. Ideally we would + # support pinning, but we don't yet. In the mean time, we + # ignore backports, so that packages don't get automatically + # upgraded to the backports version. + backport_origins = [ + origin for origin in package.origins + if origin.archive == backports_archive] + if backport_origins and ( + len(backport_origins) == len(package.origins)): + # Ignore the version if it's only in the official + # backports archive. If it's somewhere else as well, + # e.g. a PPA, we assume it was added manually and the + # user wants to get updates from it. + continue hash = self._facade.get_package_hash(package) id = self._store.get_hash_id(hash) if id is not None: Index: landscape-client-14.12/landscape/package/tests/test_reporter.py =================================================================== --- landscape-client-14.12.orig/landscape/package/tests/test_reporter.py +++ landscape-client-14.12/landscape/package/tests/test_reporter.py @@ -2,6 +2,7 @@ import sys import os import time import apt_pkg +import shutil from twisted.internet.defer import Deferred, succeed, inlineCallbacks from twisted.internet import reactor @@ -9,6 +10,7 @@ from twisted.internet import reactor from landscape.lib.fs import create_file, touch_file from landscape.lib.fetch import fetch_async, FetchError +from landscape.lib.lsb_release import parse_lsb_release, LSB_RELEASE_FILENAME from landscape.lib import bpickle from landscape.package.store import ( PackageStore, UnknownHashIDRequest, FakePackageStore) @@ -939,6 +941,92 @@ class PackageReporterAptTest(LandscapeTe result = self.reporter.detect_packages_changes() return result.addCallback(got_result) + + def test_detect_packages_changes_with_backports(self): + """ + Package versions coming from backports aren't considered to be + available. + + This is because we don't support pinning, and the backports + archive is enabled by default since xenial. + """ + message_store = self.broker_service.message_store + message_store.set_accepted_types(["packages"]) + + lsb = parse_lsb_release(LSB_RELEASE_FILENAME) + release_path = os.path.join(self.repository_dir, "Release") + with open(release_path, "w") as release: + release.write("Suite: {}-backports".format(lsb["code-name"])) + + self.store.set_hash_ids({HASH1: 1, HASH2: 2, HASH3: 3}) + + def got_result(result): + self.assertMessages(message_store.get_pending_messages(), []) + + self.assertEqual(sorted(self.store.get_available()), []) + + result = self.reporter.detect_packages_changes() + return result.addCallback(got_result) + + def test_detect_packages_changes_with_backports_others(self): + """ + Packages coming from backport archives that aren't named like + the official backports archive are considered to be available. + """ + message_store = self.broker_service.message_store + message_store.set_accepted_types(["packages"]) + + release_path = os.path.join(self.repository_dir, "Release") + with open(release_path, "w") as release: + release.write("Suite: my-personal-backports") + + self.store.set_hash_ids({HASH1: 1, HASH2: 2, HASH3: 3}) + + def got_result(result): + self.assertMessages(message_store.get_pending_messages(), + [{"type": "packages", "available": [(1, 3)]}]) + + self.assertEqual(sorted(self.store.get_available()), [1, 2, 3]) + + result = self.reporter.detect_packages_changes() + return result.addCallback(got_result) + + def test_detect_packages_changes_with_backports_both(self): + """ + If a package is both in the official backports archive and in + some other archive (e.g. a PPA), the package is considered to be + available. + + The reason for this is that if you have enabled a PPA, you most + likely want to get updates from it. + """ + message_store = self.broker_service.message_store + message_store.set_accepted_types(["packages"]) + + temp_dir = self.makeDir() + other_backport_dir = os.path.join(temp_dir, "my-personal-backports") + shutil.copytree(self.repository_dir, other_backport_dir) + os.remove(os.path.join(other_backport_dir, "Packages")) + self.facade.add_channel_deb_dir(other_backport_dir) + + lsb = parse_lsb_release(LSB_RELEASE_FILENAME) + official_release_path = os.path.join(self.repository_dir, "Release") + with open(official_release_path, "w") as release: + release.write("Suite: {}-backports".format(lsb["code-name"])) + unofficial_release_path = os.path.join(other_backport_dir, "Release") + with open(unofficial_release_path, "w") as release: + release.write("Suite: my-personal-backports") + + self.store.set_hash_ids({HASH1: 1, HASH2: 2, HASH3: 3}) + + def got_result(result): + self.assertMessages(message_store.get_pending_messages(), + [{"type": "packages", "available": [(1, 3)]}]) + + self.assertEqual(sorted(self.store.get_available()), [1, 2, 3]) + + result = self.reporter.detect_packages_changes() + return result.addCallback(got_result) @inlineCallbacks def test_detect_packages_after_tasks(self): debian/patches/release-upgrade-success.patch0000644000000000000000000000231713404451062016353 0ustar Description: Enable landscape-client to survive trusty upgrade At present, landscape-client fails to report the completion (or failure) of a release upgrade from trusty to xenial due to the delayed import of twisted.internet.unix (part of LP: #1670291) Author: Simon Poirier Origin: https://github.com/CanonicalLtd/landscape-client/commit/d2bd25caafd0406ef762c718cac66a7f6ad94596 Bug-Ubuntu: https://bugs.launchpad.net/bugs/1670291 Last-Update: 2018-11-23 Index: sru/landscape/package/releaseupgrader.py =================================================================== --- sru.orig/landscape/package/releaseupgrader.py 2018-11-23 10:29:20.563300247 -0500 +++ sru/landscape/package/releaseupgrader.py 2018-11-23 10:29:20.559300250 -0500 @@ -165,6 +165,9 @@ @param current_code_name: The code-name of the current release. """ + # This bizarre (and apparently unused) import is a workaround for + # LP: #1670291 -- see comments in that ticket for an explanation + import twisted.internet.unix # noqa: F401 upgrade_tool_directory = self._config.upgrade_tool_directory # On some releases the upgrade-tool doesn't support the allow third debian/patches/autoremovable-report-1208393.diff0000644000000000000000000003225413404451062016461 0ustar Description: Package autoremove reporting - Add autoremove state to store schema - autoremove package report Author: Simon Poirier Origin: upstream, https://github.com/CanonicalLtd/landscape-client/commit/4807ca51a92814acaa51156d7172a274309f2934 Bug-Ubuntu: https://launchpad.net/bugs/1208393 Last-Update: 2017-11-10 --- This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ --- a/landscape/package/facade.py +++ b/landscape/package/facade.py @@ -422,6 +422,10 @@ return False return version > version.package.installed + def is_package_autoremovable(self, version): + """Was the package auto-installed, but isn't required anymore?""" + return version.package.is_auto_removable + def _is_main_architecture(self, package): """Is the package for the facade's main architecture?""" # package.name includes the architecture, if it's for a foreign --- a/landscape/package/reporter.py +++ b/landscape/package/reporter.py @@ -341,6 +341,7 @@ self._store.clear_installed() self._store.clear_locked() self._store.clear_hash_id_requests() + self._store.clear_autoremovable() return succeed(None) @@ -542,11 +543,13 @@ old_available = set(self._store.get_available()) old_upgrades = set(self._store.get_available_upgrades()) old_locked = set(self._store.get_locked()) + old_autoremovable = set(self._store.get_autoremovable()) current_installed = set() current_available = set() current_upgrades = set() current_locked = set() + current_autoremovable = set() lsb = parse_lsb_release(LSB_RELEASE_FILENAME) backports_archive = "{}-backports".format(lsb["code-name"]) @@ -574,6 +577,8 @@ current_installed.add(id) if self._facade.is_package_available(package): current_available.add(id) + if self._facade.is_package_autoremovable(package): + current_autoremovable.add(id) else: current_available.add(id) @@ -591,11 +596,13 @@ new_available = current_available - old_available new_upgrades = current_upgrades - old_upgrades new_locked = current_locked - old_locked + new_autoremovable = current_autoremovable - old_autoremovable not_installed = old_installed - current_installed not_available = old_available - current_available not_upgrades = old_upgrades - current_upgrades not_locked = old_locked - current_locked + not_autoremovable = old_autoremovable - current_autoremovable message = {} if new_installed: @@ -611,6 +618,13 @@ message["locked"] = \ list(sequence_to_ranges(sorted(new_locked))) + if new_autoremovable: + message["autoremovable"] = list( + sequence_to_ranges(sorted(new_autoremovable))) + if not_autoremovable: + message["not-autoremovable"] = list( + sequence_to_ranges(sorted(not_autoremovable))) + if not_installed: message["not-installed"] = \ list(sequence_to_ranges(sorted(not_installed))) @@ -632,12 +646,15 @@ logging.info("Queuing message with changes in known packages: " "%d installed, %d available, %d available upgrades, " - "%d locked, %d not installed, %d not available, " - "%d not available upgrades, %d not locked." + "%d locked, %d autoremovable, %d not installed, " + "%d not available, %d not available upgrades, " + "%d not locked, %d not autoremovable. " % (len(new_installed), len(new_available), len(new_upgrades), len(new_locked), + len(new_autoremovable), len(not_installed), len(not_available), - len(not_upgrades), len(not_locked))) + len(not_upgrades), len(not_locked), + len(not_autoremovable))) def update_currently_known(result): if new_installed: @@ -648,6 +665,8 @@ self._store.add_available(new_available) if new_locked: self._store.add_locked(new_locked) + if new_autoremovable: + self._store.add_autoremovable(new_autoremovable) if not_available: self._store.remove_available(not_available) if new_upgrades: @@ -656,6 +675,8 @@ self._store.remove_available_upgrades(not_upgrades) if not_locked: self._store.remove_locked(not_locked) + if not_autoremovable: + self._store.remove_autoremovable(not_autoremovable) # Something has changed wrt the former run, let's update the # timestamp and return True. stamp_file = self._config.detect_package_changes_stamp --- a/landscape/package/store.py +++ b/landscape/package/store.py @@ -208,6 +208,25 @@ return [row[0] for row in cursor.fetchall()] @with_cursor + def add_autoremovable(self, cursor, ids): + for id in ids: + cursor.execute("REPLACE INTO autoremovable VALUES (?)", (id,)) + + @with_cursor + def remove_autoremovable(self, cursor, ids): + id_list = ",".join(str(int(id)) for id in ids) + cursor.execute("DELETE FROM autoremovable WHERE id IN (%s)" % id_list) + + @with_cursor + def clear_autoremovable(self, cursor): + cursor.execute("DELETE FROM autoremovable") + + @with_cursor + def get_autoremovable(self, cursor): + cursor.execute("SELECT id FROM autoremovable") + return [row[0] for row in cursor.fetchall()] + + @with_cursor def add_installed(self, cursor, ids): for id in ids: cursor.execute("REPLACE INTO installed VALUES (?)", (id,)) @@ -424,6 +443,8 @@ # try block. cursor = db.cursor() try: + cursor.execute("CREATE TABLE autoremovable" + " (id INTEGER PRIMARY KEY)") cursor.execute("CREATE TABLE locked" " (id INTEGER PRIMARY KEY)") cursor.execute("CREATE TABLE available" --- a/landscape/package/tests/test_facade.py +++ b/landscape/package/tests/test_facade.py @@ -817,6 +817,30 @@ self.assertEqual("version2-release2", version2.version) self.assertFalse(self.facade.is_package_installed(version2)) + def test_is_package_autoremovable(self): + """ + Check that auto packages without dependencies on them are correctly + detected as being autoremovable. + """ + self._add_system_package("dep") + self._add_system_package("newdep") + self._add_system_package("foo", control_fields={"Depends": "newdep"}) + self.facade.reload_channels() + dep, = sorted(self.facade.get_packages_by_name("dep")) + dep.package.mark_auto(True) + # dep should not be explicitely installed + dep.package.mark_install(False) + newdep, = sorted(self.facade.get_packages_by_name("newdep")) + newdep, = sorted(self.facade.get_packages_by_name("newdep")) + newdep.package.mark_auto(True) + self.assertTrue(dep.package.is_installed) + self.assertTrue(dep.package.is_auto_installed) + self.assertTrue(newdep.package.is_installed) + self.assertTrue(dep.package.is_auto_installed) + + self.assertTrue(self.facade.is_package_autoremovable(dep)) + self.assertFalse(self.facade.is_package_autoremovable(newdep)) + def test_is_package_available_in_channel_not_installed(self): """ A package is considered available if the package is in a --- a/landscape/package/tests/test_reporter.py +++ b/landscape/package/tests/test_reporter.py @@ -84,6 +84,15 @@ """Make it so that package "name1" is considered installed.""" self._install_deb_file(os.path.join(self.repository_dir, PKGNAME1)) + def set_pkg1_autoremovable(self): + """Make it so package "name1" is considered auto removable.""" + self.set_pkg1_installed() + self.facade.reload_channels() + name1 = sorted(self.facade.get_packages_by_name("name1"))[0] + # Since no other package depends on this, all that's needed + # to have it autoremovable is to mark it as installed+auto. + name1.package.mark_auto(True) + def _make_fake_apt_update(self, out="output", err="error", code=0): """Create a fake apt-update executable""" self.reporter.apt_update_filename = self.makeFile( @@ -971,6 +980,53 @@ result = self.reporter.detect_packages_changes() return result.addCallback(got_result) + def test_detect_packages_changes_with_autoremovable(self): + message_store = self.broker_service.message_store + message_store.set_accepted_types(["packages"]) + + self.store.set_hash_ids({HASH1: 1, HASH2: 2, HASH3: 3}) + self.store.add_available([1, 2, 3]) + self.store.add_installed([1]) + self.set_pkg1_autoremovable() + + result = self.successResultOf(self.reporter.detect_packages_changes()) + self.assertTrue(result) + + expected = [{"type": "packages", "autoremovable": [1]}] + self.assertMessages(message_store.get_pending_messages(), expected) + self.assertEqual([1], self.store.get_autoremovable()) + + def test_detect_packages_changes_with_not_autoremovable(self): + message_store = self.broker_service.message_store + message_store.set_accepted_types(["packages"]) + + self.store.set_hash_ids({HASH1: 1, HASH2: 2, HASH3: 3}) + self.store.add_available([1, 2, 3]) + # We don't care about checking other state changes in this test. + # In reality the package would also be installed, available, etc. + self.store.add_autoremovable([1, 2]) + + result = self.successResultOf(self.reporter.detect_packages_changes()) + self.assertTrue(result) + + expected = [{"type": "packages", "not-autoremovable": [1, 2]}] + self.assertMessages(message_store.get_pending_messages(), expected) + self.assertEqual([], self.store.get_autoremovable()) + + def test_detect_packages_changes_with_known_autoremovable(self): + message_store = self.broker_service.message_store + message_store.set_accepted_types(["packages"]) + + self.store.set_hash_ids({HASH1: 1, HASH2: 2, HASH3: 3}) + self.store.add_available([1, 2, 3]) + self.store.add_installed([1]) + self.store.add_autoremovable([1]) + self.set_pkg1_autoremovable() + + result = self.successResultOf(self.reporter.detect_packages_changes()) + self.assertFalse(result) + self.assertEqual([1], self.store.get_autoremovable()) + def test_detect_packages_changes_with_backports(self): """ Package versions coming from backports aren't considered to be --- a/landscape/package/tests/test_store.py +++ b/landscape/package/tests/test_store.py @@ -284,6 +284,23 @@ self.store1.clear_available_upgrades() self.assertEqual(self.store2.get_available_upgrades(), []) + def test_add_and_get_autoremovable(self): + self.store1.add_autoremovable([1, 2]) + value = self.store1.get_autoremovable() + self.assertEqual([1, 2], value) + + def test_clear_autoremovable(self): + self.store1.add_autoremovable([1, 2]) + self.store1.clear_autoremovable() + value = self.store1.get_autoremovable() + self.assertEqual([], value) + + def test_remove_autoremovable(self): + self.store1.add_autoremovable([1, 2, 3, 4]) + self.store1.remove_autoremovable([2, 4, 5]) + value = self.store1.get_autoremovable() + self.assertEqual([1, 3], value) + def test_add_and_get_installed_packages(self): self.store1.add_installed([1, 2]) self.assertEqual(self.store2.get_installed(), [1, 2]) @@ -350,6 +367,9 @@ cursor.execute("pragma table_info(locked)") result = cursor.fetchall() self.assertTrue(len(result) > 0) + cursor.execute("pragma table_info(autoremovable)") + result = cursor.fetchall() + self.assertTrue(len(result) > 0) def test_add_and_get_locked(self): """ --- a/landscape/message_schemas.py +++ b/landscape/message_schemas.py @@ -350,13 +350,15 @@ "available": package_ids_or_ranges, "available-upgrades": package_ids_or_ranges, "locked": package_ids_or_ranges, + "autoremovable": package_ids_or_ranges, + "not-autoremovable": package_ids_or_ranges, "not-installed": package_ids_or_ranges, "not-available": package_ids_or_ranges, "not-available-upgrades": package_ids_or_ranges, "not-locked": package_ids_or_ranges}, optional=["installed", "available", "available-upgrades", "locked", "not-available", "not-installed", "not-available-upgrades", - "not-locked"]) + "not-locked", "autoremovable", "not-autoremovable"]) package_locks = List(Tuple(Unicode(), Unicode(), Unicode())) PACKAGE_LOCKS = Message( debian/patches/bug-1508110-revno-826.diff0000644000000000000000000003105413404451062014507 0ustar Description: Users tab doesn't work as expected. Merge wait-for-valid-session-id-on-resynchronize [f=1508110] [r=chad.smith,free.ekanayaka,benji] [a=Fernando Correa Neto] Changes the user monitor plugin to wait on a valid session id. Author: Fernando Correa Neto Origin: upstream, http://bazaar.launchpad.net/~landscape/landscape-client/trunk/revision/826 Bug: https://bugs.launchpad.net/bugs/1508110 Bug-Ubuntu: https://bugs.launchpad.net/bugs/1508110 --- a/landscape/broker/client.py +++ b/landscape/broker/client.py @@ -1,6 +1,6 @@ from logging import info, exception -from twisted.internet.defer import maybeDeferred +from twisted.internet.defer import maybeDeferred, succeed from landscape.log import format_object from landscape.lib.twisted_util import gather_results @@ -67,7 +67,7 @@ """ if not (scopes is None or self.scope in scopes): # This resynchronize event is out of scope for us. Do nothing - return + return succeed(None) # Because the broker will drop session IDs already associated to scope # of the resynchronisation, it isn't safe to send messages until the --- a/landscape/broker/tests/test_client.py +++ b/landscape/broker/tests/test_client.py @@ -47,6 +47,17 @@ self.client.add(plugin) self.assertEqual(test_session_id, plugin._session_id) + def test_resynchronizing_out_of_scope(self): + """ + When a 'reysnchronize' event happens and the plugin scope is not part + of the scopes that were passed, BrokerClientPlugin succeeds. + """ + plugin = BrokerClientPlugin() + plugin.scope = "foo" + self.client.add(plugin) + deferred = self.client_reactor.fire("resynchronize", scopes=["bar"])[0] + self.assertIsNone(self.successResultOf(deferred)) + def test_resynchronizing_refreshes_session_id(self): """ When a 'reysnchronize' event fires a new session ID is acquired as the --- a/landscape/monitor/tests/test_usermonitor.py +++ b/landscape/monitor/tests/test_usermonitor.py @@ -23,13 +23,13 @@ def got_result(result): self.assertMessages( self.broker_service.message_store.get_pending_messages(), - [{"create-group-members": {u"webdev":[u"jdoe"]}, + [{"create-group-members": {u"webdev": [u"jdoe"]}, "create-groups": [{"gid": 1000, "name": u"webdev"}], "create-users": [{"enabled": True, "home-phone": None, "location": None, "name": u"JD", "primary-gid": 1000, "uid": 1000, "username": u"jdoe", "work-phone": None}], - "type": "users"}]) + "type": "users"}]) plugin.stop() users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/sh")] @@ -91,20 +91,20 @@ self.assertTrue(persist.get("groups")) self.assertMessages( self.broker_service.message_store.get_pending_messages(), - [{"create-group-members": {u"webdev":[u"jdoe"]}, + [{"create-group-members": {u"webdev": [u"jdoe"]}, "create-groups": [{"gid": 1000, "name": u"webdev"}], "create-users": [{"enabled": True, "home-phone": None, "location": None, "name": u"JD", "primary-gid": 1000, "uid": 1000, "username": u"jdoe", "work-phone": None}], - "type": "users"}]) + "type": "users"}]) self.broker_service.message_store.delete_all_messages() deferred = self.monitor.reactor.fire( "resynchronize", scopes=["users"])[0] self.successResultOf(deferred) self.assertMessages( self.broker_service.message_store.get_pending_messages(), - [{"create-group-members": {u"webdev":[u"jdoe"]}, + [{"create-group-members": {u"webdev": [u"jdoe"]}, "create-groups": [{"gid": 1000, "name": u"webdev"}], "create-users": [{"enabled": True, "home-phone": None, "location": None, "name": u"JD", @@ -113,6 +113,29 @@ "work-phone": None}], "type": "users"}]) + def test_new_message_after_resynchronize_event(self): + """ + When a 'resynchronize' reactor event is fired, a new session is + created and the UserMonitor creates a new message. + """ + self.provider.users = [("jdoe", "x", 1000, 1000, "JD,,,,", + "/home/jdoe", "/bin/sh")] + self.provider.groups = [("webdev", "x", 1000, ["jdoe"])] + self.broker_service.message_store.set_accepted_types(["users"]) + self.monitor.add(self.plugin) + self.plugin.client.broker.message_store.drop_session_ids() + deferred = self.reactor.fire("resynchronize")[0] + self.successResultOf(deferred) + self.assertMessages( + self.broker_service.message_store.get_pending_messages(), + [{"create-group-members": {u"webdev": [u"jdoe"]}, + "create-groups": [{"gid": 1000, "name": u"webdev"}], + "create-users": [{"enabled": True, "home-phone": None, + "location": None, "name": u"JD", + "primary-gid": 1000, "uid": 1000, + "username": u"jdoe", "work-phone": None}], + "type": "users"}]) + def test_wb_resynchronize_event_with_global_scope(self): """ When a C{resynchronize} event, with global scope, occurs we act exactly @@ -129,19 +152,19 @@ self.assertTrue(persist.get("groups")) self.assertMessages( self.broker_service.message_store.get_pending_messages(), - [{"create-group-members": {u"webdev":[u"jdoe"]}, + [{"create-group-members": {u"webdev": [u"jdoe"]}, "create-groups": [{"gid": 1000, "name": u"webdev"}], "create-users": [{"enabled": True, "home-phone": None, "location": None, "name": u"JD", "primary-gid": 1000, "uid": 1000, "username": u"jdoe", "work-phone": None}], - "type": "users"}]) + "type": "users"}]) self.broker_service.message_store.delete_all_messages() deferred = self.monitor.reactor.fire("resynchronize")[0] self.successResultOf(deferred) self.assertMessages( self.broker_service.message_store.get_pending_messages(), - [{"create-group-members": {u"webdev":[u"jdoe"]}, + [{"create-group-members": {u"webdev": [u"jdoe"]}, "create-groups": [{"gid": 1000, "name": u"webdev"}], "create-users": [{"enabled": True, "home-phone": None, "location": None, "name": u"JD", @@ -166,7 +189,7 @@ self.assertTrue(persist.get("groups")) self.assertMessages( self.broker_service.message_store.get_pending_messages(), - [{"create-group-members": {u"webdev":[u"jdoe"]}, + [{"create-group-members": {u"webdev": [u"jdoe"]}, "create-groups": [{"gid": 1000, "name": u"webdev"}], "create-users": [{"enabled": True, "home-phone": None, "location": None, "name": u"JD", @@ -189,13 +212,13 @@ def got_result(result): self.assertMessages( self.broker_service.message_store.get_pending_messages(), - [{"create-group-members": {u"webdev":[u"jdoe"]}, + [{"create-group-members": {u"webdev": [u"jdoe"]}, "create-groups": [{"gid": 1000, "name": u"webdev"}], "create-users": [{"enabled": True, "home-phone": None, "location": None, "name": u"JD", "primary-gid": 1000, "uid": 1000, "username": u"jdoe", "work-phone": None}], - "type": "users"}]) + "type": "users"}]) self.provider.users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/sh")] @@ -230,14 +253,14 @@ def got_result(result): self.assertMessages( self.broker_service.message_store.get_pending_messages(), - [{"create-group-members": {u"webdev":[u"jdoe"]}, + [{"create-group-members": {u"webdev": [u"jdoe"]}, "create-groups": [{"gid": 1000, "name": u"webdev"}], "create-users": [{"enabled": True, "home-phone": None, "location": None, "name": u"JD", "primary-gid": 1000, "uid": 1000, "username": u"jdoe", "work-phone": None}], - "operation-id": 1001, - "type": "users"}]) + "operation-id": 1001, + "type": "users"}]) self.provider.users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/sh")] @@ -253,12 +276,12 @@ def got_result(result): self.assertMessages( self.broker_service.message_store.get_pending_messages(), - [{"create-group-members": {u"webdev":[u"jdoe"]}, + [{"create-group-members": {u"webdev": [u"jdoe"]}, "create-groups": [{"gid": 1000, "name": u"webdev"}], "create-users": [{"enabled": True, "home-phone": None, "location": None, "name": u"JD", "primary-gid": 1000, "uid": 1000, - "username": u"jdoe", "work-phone": None}], + "username": u"jdoe", "work-phone": None}], "type": "users"}]) self.broker_service.message_store.set_accepted_types(["users"]) @@ -283,7 +306,7 @@ def got_result(result): self.assertMessages( self.broker_service.message_store.get_pending_messages(), - [{"create-group-members": {u"webdev":[u"jdoe"]}, + [{"create-group-members": {u"webdev": [u"jdoe"]}, "create-groups": [{"gid": 1000, "name": u"webdev"}], "create-users": [{"enabled": True, "home-phone": None, "location": None, "name": u"JD", @@ -332,8 +355,9 @@ def got_result(result): mstore = self.broker_service.message_store - self.assertMessages(mstore.get_pending_messages(), - [{"create-group-members": {u"webdev":[u"jdoe"]}, + self.assertMessages( + mstore.get_pending_messages(), + [{"create-group-members": {u"webdev": [u"jdoe"]}, "create-groups": [{"gid": 1000, "name": u"webdev"}], "create-users": [{"enabled": True, "home-phone": None, "location": None, "name": u"JD", @@ -376,7 +400,7 @@ self.mocker.replay() self.provider.users = [("jdoe", "x", 1000, 1000, "JD,,,,", - "/home/jdoe", "/bin/sh")] + "/home/jdoe", "/bin/sh")] self.provider.groups = [("webdev", "x", 1000, ["jdoe"])] self.monitor.add(self.plugin) connector = RemoteUserMonitorConnector(self.reactor, self.config) --- a/landscape/monitor/usermonitor.py +++ b/landscape/monitor/usermonitor.py @@ -39,10 +39,14 @@ self._publisher.stop() self._publisher = None - def _reset(self): + def _resynchronize(self, scopes=None): """Reset user and group data.""" - super(UserMonitor, self)._reset() - return self._run_detect_changes() + deferred = super(UserMonitor, self)._resynchronize(scopes=scopes) + # Wait for the superclass' asynchronous _resynchronize method to + # complete, so we have a new session ID at hand and we can craft a + # valid message (l.broker.client.BrokerClientPlugin._resynchronize). + deferred.addCallback(lambda _: self._run_detect_changes()) + return deferred @remote def detect_changes(self, operation_id=None): debian/patches/config-no-reregister-1618483.diff0000644000000000000000000001626513404451062016344 0ustar Description: Default to not register again if already registered When running the landscape-config script, change the default answer to the registration question to "No" if this client is already registered. Author: Alberto Donato Origin: upstream, http://bazaar.launchpad.net/~landscape/landscape-client/trunk/revision/939 Bug: https://launchpad.net/bugs/1618483 Last-Update: 2017-11-10 --- This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ --- a/landscape/configuration.py +++ b/landscape/configuration.py @@ -22,8 +22,10 @@ from landscape.lib.twisted_util import gather_results from landscape.lib.fetch import fetch, FetchError from landscape.lib.bootstrap import BootstrapList, BootstrapDirectory +from landscape.lib.persist import Persist from landscape.reactor import LandscapeReactor -from landscape.broker.registration import InvalidCredentialsError +from landscape.broker.registration import Identity, InvalidCredentialsError +from landscape.broker.service import BrokerService from landscape.broker.config import BrokerConfiguration from landscape.broker.amp import RemoteBrokerConnector @@ -731,6 +733,15 @@ return 2 # An error happened +def is_registered(config): + """Return whether the client is already registered.""" + persist_filename = os.path.join( + config.data_path, "{}.bpickle".format(BrokerService.service_name)) + persist = Persist(filename=persist_filename) + identity = Identity(config, persist) + return bool(identity.secure_id) + + def main(args, print=print): """Interact with the user and the server to set up client configuration.""" @@ -769,9 +780,13 @@ report_registration_outcome(result, print=print) sys.exit(determine_exit_code(result)) else: - answer = raw_input("\nRequest a new registration for " - "this computer now? (Y/n): ") - if not answer.upper().startswith("N"): + script = LandscapeSetupScript(config) + default_answer = not is_registered(config) + answer = script.prompt_yes_no( + "\nRequest a new registration for this computer now?", + default=default_answer) + + if answer: result = register(config, reactor) report_registration_outcome(result, print=print) sys.exit(determine_exit_code(result)) --- a/landscape/tests/test_configuration.py +++ b/landscape/tests/test_configuration.py @@ -7,10 +7,12 @@ import sys import unittest +import mock from twisted.internet.defer import succeed, fail, Deferred from landscape.broker.registration import InvalidCredentialsError -from landscape.broker.tests.helpers import RemoteBrokerHelper +from landscape.broker.tests.helpers import ( + RemoteBrokerHelper, BrokerConfigurationHelper) from landscape.configuration import ( print_text, LandscapeSetupScript, LandscapeSetupConfiguration, register, setup, main, setup_init_script_and_start_client, @@ -18,15 +20,17 @@ ImportOptionError, store_public_key_data, bootstrap_tree, got_connection, success, failure, exchange_failure, handle_registration_errors, done, got_error, report_registration_outcome, - determine_exit_code) + determine_exit_code, is_registered) from landscape.lib.amp import MethodCallError from landscape.lib.fetch import HTTPCodeError, PyCurlError +from landscape.lib.persist import Persist from landscape.sysvconfig import SysVConfig, ProcessError from landscape.tests.helpers import FakeBrokerServiceHelper from landscape.tests.helpers import LandscapeTest, EnvironSaverHelper from landscape.tests.mocker import ANY, MATCH, CONTAINS, expect + class LandscapeConfigurationTest(LandscapeTest): def get_config(self, args, data_path=None): @@ -1167,9 +1171,9 @@ setup_mock = self.mocker.replace(setup) setup_mock(ANY) - raw_input_mock = self.mocker.replace(raw_input) + raw_input_mock = self.mocker.replace(raw_input, passthrough=False) raw_input_mock("\nRequest a new registration for " - "this computer now? (Y/n): ") + "this computer now? [Y/n]") self.mocker.result("n") # This must not be called. @@ -1215,7 +1219,7 @@ raw_input_mock = self.mocker.replace(raw_input) raw_input_mock("\nRequest a new registration for " - "this computer now? (Y/n): ") + "this computer now? [Y/n]") self.mocker.result("y") # The register() function will be called. @@ -1246,7 +1250,7 @@ raw_input_mock = self.mocker.replace(raw_input) raw_input_mock("\nRequest a new registration for " - "this computer now? (Y/n): ") + "this computer now? [Y/n]") self.mocker.result("y") # The register() function will be called. @@ -1337,13 +1341,15 @@ printed) def make_working_config(self): + data_path = self.makeFile() return self.makeFile("[client]\n" "computer_title = Old Title\n" "account_name = Old Name\n" "registration_key = Old Password\n" "http_proxy = http://old.proxy\n" "https_proxy = https://old.proxy\n" - "url = http://url\n") + "data_path = {}\n" + "url = http://url\n".format(data_path)) def test_register(self): sysvconfig_mock = self.mocker.patch(SysVConfig) @@ -1357,7 +1363,7 @@ raw_input_mock = self.mocker.replace(raw_input) raw_input_mock("\nRequest a new registration for " - "this computer now? (Y/n): ") + "this computer now? [Y/n]") self.mocker.result("") raw_input_mock("\nThe Landscape client must be started " @@ -1424,7 +1430,7 @@ setup_mock(ANY) raw_input_mock = self.mocker.replace(raw_input) raw_input_mock("\nRequest a new registration for " - "this computer now? (Y/n): ") + "this computer now? [Y/n]") self.mocker.result("") register_mock = self.mocker.replace(register, passthrough=False) @@ -2322,3 +2328,27 @@ for code in failure_codes: result = determine_exit_code(code) self.assertEqual(2, result) + + +class IsRegisteredTest(LandscapeTest): + + helpers = [BrokerConfigurationHelper] + + def setUp(self): + super(IsRegisteredTest, self).setUp() + persist_file = os.path.join(self.config.data_path, "broker.bpickle") + self.persist = Persist(filename=persist_file) + + def test_is_registered_false(self): + """ + If the client hasn't previouly registered, is_registered returns False. + """ + self.assertFalse(is_registered(self.config)) + + def test_is_registered_true(self): + """ + If the client has previouly registered, is_registered returns True. + """ + self.persist.set("registration.secure-id", "super-secure") + self.persist.save() + self.assertTrue(is_registered(self.config)) debian/patches/set-vm-info-to-kvm-for-aws-C5-instances.patch0000644000000000000000000000411513404451062021023 0ustar Description: New Amazon AWS C5 instances are not recognised as kvm. The new Amazon AWS C5 instance type is not a recognised VM type in Landscape and thus won't allow use of a Virtual Guest asset to register with. The C5 instance type is a new type that uses a KVM-family hypervisor instead of Xen (displaying "Amazon EC2" in sys_vendor which landscape-client doesn't recognise, thus cannot associate/map to kvm). Author: Simon Poirier Origin: upstream, https://github.com/CanonicalLtd/landscape-client/commit/10bf98b860d2efed98f47ab510d8784df5da346c Bug: https://bugs.launchpad.net/bugs/1742531 Bug-Ubuntu: https://bugs.launchpad.net/bugs/1742531 Index: landscape-client-14.12/landscape/lib/tests/test_vm_info.py =================================================================== --- landscape-client-14.12.orig/landscape/lib/tests/test_vm_info.py +++ landscape-client-14.12/landscape/lib/tests/test_vm_info.py @@ -77,6 +77,14 @@ class GetVMInfoTest(LandscapeTest): self.assertEqual("", get_vm_info(root_path=self.root_path)) + def test_get_vm_info_with_ec2_sys_vendor(self): + """ + get_vm_info should return "kvm" when sys_vendor is "Amazon EC2", + which is the case for C5 instances which are based on KVM. + """ + self.make_sys_vendor("Amazon EC2") + self.assertEqual("kvm", get_vm_info(root_path=self.root_path)) + def test_get_vm_info_with_bochs_sys_vendor(self): """ L{get_vm_info} should return "kvm" when we detect the sys_vendor is Index: landscape-client-14.12/landscape/lib/vm_info.py =================================================================== --- landscape-client-14.12.orig/landscape/lib/vm_info.py +++ landscape-client-14.12/landscape/lib/vm_info.py @@ -62,6 +62,7 @@ def _get_vm_by_vendor(sys_vendor_path): vendor = read_file(sys_vendor_path).lower() # Use lower-key string for vendors, since we do case-insentive match. content_vendors_map = ( + ("amazon ec2", "kvm"), ("bochs", "kvm"), ("google", "gce"), ("innotek", "virtualbox"), debian/patches/package-reporter-proxy-1531150.diff0000644000000000000000000002541013404451062016677 0ustar Description: Add proxy handling to package reporter Author: Simon Poirier Origin: upstream, http://bazaar.launchpad.net/~landscape/landscape-client/trunk/revision/919 Bug: https://launchpad.net/bugs/1531150 Last-Update: 2017-11-10 --- This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ --- a/apt-update/apt-update.c +++ b/apt-update/apt-update.c @@ -19,7 +19,7 @@ int main(int argc, char *argv[], char *envp[]) { char *apt_argv[] = {"/usr/bin/apt-get", "-q", "update", NULL}; - char *apt_envp[] = {"PATH=/bin:/usr/bin", NULL, NULL}; + char *apt_envp[] = {"PATH=/bin:/usr/bin", NULL, NULL, NULL, NULL}; // Set the HOME environment variable struct passwd *pwd = getpwuid(geteuid()); @@ -33,6 +33,24 @@ exit(1); } + // Pass proxy environment variables + int proxy_arg = 2; + char *http_proxy = getenv("http_proxy"); + if (http_proxy) { + if (asprintf(&apt_envp[proxy_arg], "http_proxy=%s", http_proxy) == -1) { + perror("error: Unable to set http_proxy environment variable"); + exit(1); + } + proxy_arg++; + } + char *https_proxy = getenv("https_proxy"); + if (https_proxy) { + if (asprintf(&apt_envp[proxy_arg], "https_proxy=%s", https_proxy) == -1) { + perror("error: Unable to set https_proxy environment variable"); + exit(1); + } + } + // Drop any supplementary group if (setgroups(0, NULL) == -1) { perror("error: Unable to set supplementary groups IDs"); --- a/landscape/lib/fetch.py +++ b/landscape/lib/fetch.py @@ -45,7 +45,7 @@ def fetch(url, post=False, data="", headers={}, cainfo=None, curl=None, connect_timeout=30, total_timeout=600, insecure=False, follow=True, - user_agent=None): + user_agent=None, proxy=None): """Retrieve a URL and return the content. @param url: The url to be fetched. @@ -61,6 +61,7 @@ during autodiscovery) @param follow: If True, follow HTTP redirects (default True). @param user_agent: The user-agent to set in the request. + @param proxy: The proxy url to use for the request. """ import pycurl output = StringIO(data) @@ -94,6 +95,9 @@ if user_agent is not None: curl.setopt(pycurl.USERAGENT, user_agent) + if proxy is not None: + curl.setopt(pycurl.PROXY, proxy) + curl.setopt(pycurl.MAXREDIRS, 5) curl.setopt(pycurl.CONNECTTIMEOUT, connect_timeout) curl.setopt(pycurl.LOW_SPEED_LIMIT, 1) --- a/landscape/lib/tests/test_fetch.py +++ b/landscape/lib/tests/test_fetch.py @@ -286,6 +286,14 @@ self.assertEqual(result, "result") self.assertEqual("user-agent", curl.options[pycurl.USERAGENT]) + def test_pycurl_proxy(self): + """If provided, the proxy is set in the request.""" + curl = CurlStub("result") + proxy = "http://my.little.proxy" + result = fetch("http://example.com", curl=curl, proxy=proxy) + self.assertEqual("result", result) + self.assertEqual(proxy, curl.options[pycurl.PROXY]) + def test_create_curl(self): curls = [] --- a/landscape/package/reporter.py +++ b/landscape/package/reporter.py @@ -38,6 +38,10 @@ parser.add_option("--force-apt-update", default=False, action="store_true", help="Force running apt-update.") + parser.add_option("--http-proxy", metavar="URL", + help="The URL of the HTTP proxy, if one is needed.") + parser.add_option("--https-proxy", metavar="URL", + help="The URL of the HTTPS proxy, if one is needed.") return parser @@ -133,8 +137,14 @@ logging.warning("Couldn't download hash=>id database: %s" % str(exception)) + if url.startswith("https"): + proxy = self._config.get("https_proxy") + else: + proxy = self._config.get("http_proxy") + result = fetch_async(url, - cainfo=self._config.get("ssl_public_key")) + cainfo=self._config.get("ssl_public_key"), + proxy=proxy) result.addCallback(fetch_ok) result.addErrback(fetch_error) @@ -262,7 +272,12 @@ Run apt-update using the passed in deferred, which allows for callers to inspect the result code. """ - result = spawn_process(self.apt_update_filename) + env = {} + if self._config.http_proxy: + env["http_proxy"] = self._config.http_proxy + if self._config.https_proxy: + env["https_proxy"] = self._config.https_proxy + result = spawn_process(self.apt_update_filename, env=env) def callback((out, err, code), deferred): return deferred.callback((out, err, code)) --- a/landscape/package/tests/test_reporter.py +++ b/landscape/package/tests/test_reporter.py @@ -3,11 +3,11 @@ import time import apt_pkg import shutil +import mock from twisted.internet.defer import Deferred, succeed, inlineCallbacks from twisted.internet import reactor - from landscape.lib.fs import create_file, touch_file from landscape.lib.fetch import fetch_async, FetchError from landscape.lib.lsb_release import parse_lsb_release, LSB_RELEASE_FILENAME @@ -286,7 +286,7 @@ hash_id_db_url = self.config.package_hash_id_url + "uuid_codename_arch" fetch_async_mock = self.mocker.replace("landscape.lib." "fetch.fetch_async") - fetch_async_mock(hash_id_db_url, cainfo=None) + fetch_async_mock(hash_id_db_url, cainfo=None, patch=None) fetch_async_result = Deferred() fetch_async_result.callback("hash-ids") self.mocker.result(fetch_async_result) @@ -311,6 +311,33 @@ return result + @mock.patch("landscape.package.reporter.fetch_async", + return_value=succeed("hash-ids")) + @mock.patch("logging.info", return_value=None) + def test_fetch_hash_id_db_with_proxy(self, logging_mock, mock_fetch_async): + """fetching hash-id-db uses proxy settings""" + # Assume package_hash_id_url is set + self.config.data_path = self.makeDir() + self.config.package_hash_id_url = "https://fake.url/path/" + os.makedirs(os.path.join(self.config.data_path, "package", "hash-id")) + + # Fake uuid, codename and arch + message_store = self.broker_service.message_store + message_store.set_server_uuid("uuid") + self.reporter.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE) + self.facade.set_arch("arch") + + # Let's say fetch_async is successful + hash_id_db_url = self.config.package_hash_id_url + "uuid_codename_arch" + + # set proxy settings + self.config.https_proxy = "http://helloproxy:8000" + + result = self.reporter.fetch_hash_id_db() + mock_fetch_async.assert_called_once_with( + hash_id_db_url, cainfo=None, proxy="http://helloproxy:8000") + return result + def test_fetch_hash_id_db_does_not_download_twice(self): # Let's say that the hash=>id database is already there @@ -330,7 +357,7 @@ # Intercept any call to fetch_async fetch_async_mock = self.mocker.replace("landscape.lib." "fetch.fetch_async") - fetch_async_mock(ANY) + fetch_async_mock(ANY, proxy=None) # Go! self.mocker.replay() @@ -430,7 +457,7 @@ "uuid_codename_arch" fetch_async_mock = self.mocker.replace("landscape.lib." "fetch.fetch_async") - fetch_async_mock(hash_id_db_url, cainfo=None) + fetch_async_mock(hash_id_db_url, cainfo=None, proxy=None) fetch_async_result = Deferred() fetch_async_result.callback("hash-ids") self.mocker.result(fetch_async_result) @@ -462,7 +489,7 @@ hash_id_db_url = self.config.package_hash_id_url + "uuid_codename_arch" fetch_async_mock = self.mocker.replace("landscape.lib." "fetch.fetch_async") - fetch_async_mock(hash_id_db_url, cainfo=None) + fetch_async_mock(hash_id_db_url, cainfo=None, proxy=None) fetch_async_result = Deferred() fetch_async_result.errback(FetchError("fetch error")) self.mocker.result(fetch_async_result) @@ -538,7 +565,7 @@ "uuid_codename_arch" fetch_async_mock = self.mocker.replace("landscape.lib." "fetch.fetch_async") - fetch_async_mock(hash_id_db_url, cainfo=self.config.ssl_public_key) + fetch_async_mock(hash_id_db_url, cainfo=self.config.ssl_public_key, proxy=None) fetch_async_result = Deferred() fetch_async_result.callback("hash-ids") self.mocker.result(fetch_async_result) @@ -1621,6 +1648,44 @@ reactor.callWhenRunning(do_test) return deferred + @mock.patch("landscape.package.reporter.spawn_process", + return_value=succeed(("", "", 0))) + def test_run_apt_update_honors_http_proxy(self, mock_spawn_process): + """ + The PackageReporter.run_apt_update method honors the http_proxy + config when calling the apt-update wrapper. + """ + self.config.http_proxy = "http://proxy_server:8080" + self.reporter.sources_list_filename = "/I/Dont/Exist" + + update_result = self.reporter.run_apt_update() + # run_apt_update uses reactor.call_later so advance a bit + self.reactor.advance(0) + self.successResultOf(update_result) + + mock_spawn_process.assert_called_once_with( + self.reporter.apt_update_filename, + env={"http_proxy": "http://proxy_server:8080"}) + + @mock.patch("landscape.package.reporter.spawn_process", + return_value=succeed(("", "", 0))) + def test_run_apt_update_honors_https_proxy(self, mock_spawn_process): + """ + The PackageReporter.run_apt_update method honors the https_proxy + config when calling the apt-update wrapper. + """ + self.config.https_proxy = "http://proxy_server:8443" + self.reporter.sources_list_filename = "/I/Dont/Exist" + + update_result = self.reporter.run_apt_update() + # run_apt_update uses reactor.call_later, so advance a bit + self.reactor.advance(0) + self.successResultOf(update_result) + + mock_spawn_process.assert_called_once_with( + self.reporter.apt_update_filename, + env={"https_proxy": "http://proxy_server:8443"}) + def test_run_apt_update_error_on_cache_file(self): """ L{PackageReporter.run_apt_update} succeeds if the command fails because debian/patches/detect-cloudstack-kvm-1754073.patch0000644000000000000000000001702013404451062016662 0ustar Description: Detect ApacheCloudstack KVM instances Extend vm_info detection to other dmi fields. Scan sys, bios and chassis DMI to detect hypervisor (LP: #1754073) Author: Simon Poirier Bug-Ubuntu: https://bugs.launchpad.net/bugs/1754073 Origin: backport, https://github.com/CanonicalLtd/landscape-client/commit/e08cd1ac06b0c2e1761749e05e10593a4b123317 Last-Update: 2018-03-26 Index: landscape-client-14.12/landscape/lib/tests/test_vm_info.py =================================================================== --- landscape-client-14.12.orig/landscape/lib/tests/test_vm_info.py +++ landscape-client-14.12/landscape/lib/tests/test_vm_info.py @@ -16,11 +16,12 @@ class GetVMInfoTest(LandscapeTest): self.proc_sys_path = self.makeDir( path=os.path.join(self.proc_path, "sys")) - def make_sys_vendor(self, content): - """Create /sys/class/dmi/id/sys_vendor with the specified content.""" + def make_dmi_info(self, name, content): + """Create /sys/class/dmi/id/ with the specified content.""" dmi_path = os.path.join(self.root_path, "sys/class/dmi/id") - self.makeDir(path=dmi_path) - self.makeFile(dirname=dmi_path, basename="sys_vendor", content=content) + if not os.path.exists(dmi_path): + self.makeDir(path=dmi_path) + self.makeFile(dirname=dmi_path, basename=name, content=content) def test_get_vm_info_empty_when_no_virtualization_is_found(self): """ @@ -82,14 +83,32 @@ class GetVMInfoTest(LandscapeTest): get_vm_info should return "kvm" when sys_vendor is "Amazon EC2", which is the case for C5 instances which are based on KVM. """ - self.make_sys_vendor("Amazon EC2") + self.make_dmi_info("sys_vendor", "Amazon EC2") self.assertEqual("kvm", get_vm_info(root_path=self.root_path)) def test_get_vm_info_with_digitalocean_sys_vendor(self): """ get_vm_info should return "kvm" when sys_vendor is "DigitalOcean". """ - self.make_sys_vendor("DigitalOcean") + self.make_dmi_info("sys_vendor", "DigitalOcean") + self.assertEqual(b"kvm", get_vm_info(root_path=self.root_path)) + + def test_get_vm_info_with_kvm_bios_vendor(self): + """ + get_vm_info should return "kvm" when bios_vendor maps to kvm. + """ + # DigitalOcean is known to set the bios_vendor on their instances. + self.make_dmi_info("bios_vendor", "DigitalOcean") + self.assertEqual(b"kvm", get_vm_info(root_path=self.root_path)) + + def test_get_vm_info_with_bochs_chassis_vendor(self): + """ + get_vm_info should return "kvm" when chassis_vendor is "Bochs". + """ + # DigitalOcean, AWS and Cloudstack are known to customize sys_vendor + # and/or bios_vendor. + self.make_dmi_info("sys_vendor", "Apache Software Foundation") + self.make_dmi_info("chassis_vendor", "Bochs") self.assertEqual("kvm", get_vm_info(root_path=self.root_path)) def test_get_vm_info_with_bochs_sys_vendor(self): @@ -97,7 +116,7 @@ class GetVMInfoTest(LandscapeTest): L{get_vm_info} should return "kvm" when we detect the sys_vendor is Bochs. """ - self.make_sys_vendor("Bochs") + self.make_dmi_info("sys_vendor", "Bochs") self.assertEqual("kvm", get_vm_info(root_path=self.root_path)) def test_get_vm_info_with_openstack_sys_vendor(self): @@ -105,7 +124,7 @@ class GetVMInfoTest(LandscapeTest): L{get_vm_info} should return "kvm" when we detect the sys_vendor is Openstack. """ - self.make_sys_vendor("OpenStack Foundation") + self.make_dmi_info("sys_vendor", "OpenStack Foundation") self.assertEqual("kvm", get_vm_info(root_path=self.root_path)) def test_get_vm_info_with_qemu_sys_vendor(self): @@ -113,7 +132,7 @@ class GetVMInfoTest(LandscapeTest): L{get_vm_info} should return "kvm" when we detect the sys_vendor is QEMU. """ - self.make_sys_vendor("QEMU") + self.make_dmi_info("sys_vendor", "QEMU") self.assertEqual("kvm", get_vm_info(root_path=self.root_path)) def test_get_vm_info_with_vmware_sys_vendor(self): @@ -121,7 +140,7 @@ class GetVMInfoTest(LandscapeTest): L{get_vm_info} should return "vmware" when we detect the sys_vendor is VMware Inc. """ - self.make_sys_vendor("VMware, Inc.") + self.make_dmi_info("sys_vendor", "VMware, Inc.") self.assertEqual("vmware", get_vm_info(root_path=self.root_path)) def test_get_vm_info_with_virtualbox_sys_vendor(self): @@ -129,28 +148,28 @@ class GetVMInfoTest(LandscapeTest): L{get_vm_info} should return "virtualbox" when we detect the sys_vendor is innotek. GmbH. """ - self.make_sys_vendor("innotek GmbH") + self.make_dmi_info("sys_vendor", "innotek GmbH") self.assertEqual("virtualbox", get_vm_info(root_path=self.root_path)) def test_get_vm_info_with_microsoft_sys_vendor(self): """ L{get_vm_info} returns "hyperv" if the sys_vendor is Microsoft. """ - self.make_sys_vendor("Microsoft Corporation") + self.make_dmi_info("sys_vendor", "Microsoft Corporation") self.assertEqual("hyperv", get_vm_info(root_path=self.root_path)) def test_get_vm_info_with_google_sys_vendor(self): """ L{get_vm_info} returns "gce" if the sys_vendor is Google. """ - self.make_sys_vendor("Google") + self.make_dmi_info("sys_vendor", "Google") self.assertEqual("gce", get_vm_info(root_path=self.root_path)) def test_get_vm_info_matches_insensitive(self): """ L{get_vm_info} matches the vendor string in a case-insentive way. """ - self.make_sys_vendor("openstack foundation") + self.make_dmi_info("sys_vendor", "openstack foundation") self.assertEqual("kvm", get_vm_info(root_path=self.root_path)) def test_get_vm_info_with_kvm_on_other_architecture(self): @@ -171,7 +190,7 @@ class GetVMInfoTest(LandscapeTest): L{get_vm_info} should return an empty string when the sys_vendor is unknown. """ - self.make_sys_vendor("Some other vendor") + self.make_dmi_info("sys_vendor", "Some other vendor") self.assertEqual("", get_vm_info(root_path=self.root_path)) Index: landscape-client-14.12/landscape/lib/vm_info.py =================================================================== --- landscape-client-14.12.orig/landscape/lib/vm_info.py +++ landscape-client-14.12/landscape/lib/vm_info.py @@ -22,11 +22,18 @@ def get_vm_info(root_path="/"): if _is_vm_xen(root_path): return "xen" - sys_vendor_path = os.path.join(root_path, "sys/class/dmi/id/sys_vendor") - if os.path.exists(sys_vendor_path): - return _get_vm_by_vendor(sys_vendor_path) - else: - return _get_vm_legacy(root_path) + # Iterate through all dmi *_vendors, as clouds can (and will) customize + # sysinfo values. (https://libvirt.org/formatdomain.html#elementsSysinfo) + dmi_info_path = os.path.join(root_path, "sys/class/dmi/id") + for dmi_info_file in ("sys_vendor", "chassis_vendor", "bios_vendor"): + dmi_vendor_path = os.path.join(dmi_info_path, dmi_info_file) + if not os.path.exists(dmi_vendor_path): + continue + vendor = _get_vm_by_vendor(dmi_vendor_path) + if vendor: + return vendor + + return _get_vm_legacy(root_path) def get_container_info(path="/run/container_type"): debian/patches/set-vm-info-to-kvm-for-digitalocean-instances.patch0000644000000000000000000000373513404451062022416 0ustar Description: Some digitalocean instances are not recognised as kvm. Some digiticalocean instance type is not a recognised VM type in Landscape and thus won't allow use of a Virtual Guest asset to register with. The digitalocean instance type uses a KVM-family hypervisor with a custom naming: "DigitalOcean" in sys_vendor which landscape-client doesn't recognise, thus cannot associate/map to kvm). Author: Simon Poirier Origin: upstream, https://github.com/CanonicalLtd/landscape-client/commit/6231b9b Bug: https://bugs.launchpad.net/bugs/1743232 Bug-Ubuntu: https://bugs.launchpad.net/bugs/1743232 Index: landscape-client-14.12/landscape/lib/tests/test_vm_info.py =================================================================== --- landscape-client-14.12.orig/landscape/lib/tests/test_vm_info.py +++ landscape-client-14.12/landscape/lib/tests/test_vm_info.py @@ -85,6 +85,13 @@ class GetVMInfoTest(LandscapeTest): self.make_sys_vendor("Amazon EC2") self.assertEqual("kvm", get_vm_info(root_path=self.root_path)) + def test_get_vm_info_with_digitalocean_sys_vendor(self): + """ + get_vm_info should return "kvm" when sys_vendor is "DigitalOcean". + """ + self.make_sys_vendor("DigitalOcean") + self.assertEqual("kvm", get_vm_info(root_path=self.root_path)) + def test_get_vm_info_with_bochs_sys_vendor(self): """ L{get_vm_info} should return "kvm" when we detect the sys_vendor is Index: landscape-client-14.12/landscape/lib/vm_info.py =================================================================== --- landscape-client-14.12.orig/landscape/lib/vm_info.py +++ landscape-client-14.12/landscape/lib/vm_info.py @@ -64,6 +64,7 @@ def _get_vm_by_vendor(sys_vendor_path): content_vendors_map = ( ("amazon ec2", "kvm"), ("bochs", "kvm"), + ("digitalocean", "kvm"), ("google", "gce"), ("innotek", "virtualbox"), ("microsoft", "hyperv"), debian/patches/bug-1429888-revno-814.diff0000644000000000000000000001304513404451062014534 0ustar Description: Client backtraces and hangs when incorrect account/pass is given. Merge fix-1429888-wrong-credentials-stacktrace [f=1429888] [r=bjornt,adam-collard] [a=Chris Glass] This branch fixes the related bug, where inappropriate account/password combinations made client registration badly. Author: Christopher Glass Origin: upstream, http://bazaar.launchpad.net/~landscape/landscape-client/trunk/revision/814 Bug: https://bugs.launchpad.net/bugs/1429888 Bug-Ubuntu: https://bugs.launchpad.net/bugs/1429888 --- a/landscape/configuration.py +++ b/landscape/configuration.py @@ -601,11 +601,17 @@ add_result("non-ssl-error") -def handle_registration_errors(add_result, connector, failure): - """HAndle a failed registration by recording the kind of failure.""" - # We'll get invalid credentials through the signal. +def handle_registration_errors(failure, connector): + """Handle invalid credentials. + + The connection to the broker succeeded but the registration itself + failed, because of invalid credentials. We need to trap the exceptions + so they don't stacktrace (we know what is going on), and try to cleanly + disconnect from the broker. + + Note: "results" contains a failure indication already (or will shortly) + since the registration-failed signal will fire.""" failure.trap(InvalidCredentialsError, MethodCallError) - add_result("registration-error") connector.disconnect() @@ -627,8 +633,7 @@ "exchange-failed": partial(exchange_failure, add_result)} deferreds = [ remote.call_on_event(handlers), - remote.register().addErrback( - handle_registration_errors, add_result, connector)] + remote.register().addErrback(handle_registration_errors, connector)] results = gather_results(deferreds) results.addCallback(done, connector, reactor) return results --- a/landscape/tests/test_configuration.py +++ b/landscape/tests/test_configuration.py @@ -7,7 +7,7 @@ import sys import unittest -from twisted.internet.defer import succeed +from twisted.internet.defer import succeed, fail, Deferred from landscape.broker.registration import InvalidCredentialsError from landscape.broker.tests.helpers import RemoteBrokerHelper @@ -82,11 +82,10 @@ class HandleRegistrationErrorsTests(unittest.TestCase): - def test_handle_registration_errors(self): + def test_handle_registration_errors_traps(self): """ - The handle_registration_errors() function handles - InvalidCredentialsError and MethodCallError errors and records the - type of failure and disconnects. + The handle_registration_errors() function traps InvalidCredentialsError + and MethodCallError errors. """ class FauxFailure(object): def trap(self, *trapped): @@ -95,16 +94,49 @@ faux_connector = FauxConnector() faux_failure = FauxFailure() - results = [] - self.assertNotEqual(0, - handle_registration_errors( - results.append, faux_connector, faux_failure)) - self.assertEqual(["registration-error"], results) - self.assertTrue(faux_connector.was_disconnected) + self.assertNotEqual( + 0, handle_registration_errors(faux_failure, faux_connector)) self.assertTrue( [InvalidCredentialsError, MethodCallError], faux_failure.trapped_exceptions) + def test_handle_registration_errors_disconnects_cleanly(self): + """ + The handle_registration_errors function disconnects the broker + connector cleanly. + """ + class FauxFailure(object): + def trap(self, *trapped): + pass + + faux_connector = FauxConnector() + faux_failure = FauxFailure() + + self.assertNotEqual( + 0, handle_registration_errors(faux_failure, faux_connector)) + self.assertTrue(faux_connector.was_disconnected) + + def test_handle_registration_errors_as_errback(self): + """ + The handle_registration_errors functions works as an errback. + + This test was put in place to assert the parameters passed to the + function when used as an errback are in the correct order. + """ + faux_connector = FauxConnector() + calls = [] + + def i_raise(result): + calls.append(True) + return InvalidCredentialsError("Bad mojo") + + deferred = Deferred() + deferred.addCallback(i_raise) + deferred.addErrback(handle_registration_errors, faux_connector) + deferred.callback("") # This kicks off the callback chain. + + self.assertEqual([True], calls) + class DoneTests(unittest.TestCase): @@ -1933,6 +1965,24 @@ self.config, self.reactor, connector_factory, max_retries=99) self.assertEqual("success", result) + def test_register_registration_error(self): + """ + If we get a registration error, the register() function returns + "failure". + """ + self.reactor.call_later(0, self.reactor.fire, "registration-failed") + + def fail_register(): + return fail(InvalidCredentialsError("Nope.")) + + self.remote.register = fail_register + + connector_factory = FakeConnectorFactory(self.remote) + result = register( + config=self.config, reactor=self.reactor, + connector_factory=connector_factory, max_retries=99) + self.assertEqual("failure", result) + class FauxConnection(object): def __init__(self): debian/patches/bug-1508110-revno-829.diff0000644000000000000000000002362513404451062014517 0ustar Description: Users tab doesn't work as expected. Merge bug-1508110-resync-users-on-upgrade f=1508110] [r=chad.smith,simpoir] [a=Benji York] Add a complete refresh of user data after client install (remediation for bug 1508110) Author: Benji York Origin: upstream, http://bazaar.launchpad.net/~landscape/landscape-client/trunk/revision/829 Bug: https://bugs.launchpad.net/bugs/1508110 Bug-Ubuntu: https://bugs.launchpad.net/bugs/1508110 Index: landscape-client-14.12/landscape/monitor/tests/test_usermonitor.py =================================================================== --- landscape-client-14.12.orig/landscape/monitor/tests/test_usermonitor.py +++ landscape-client-14.12/landscape/monitor/tests/test_usermonitor.py @@ -1,3 +1,6 @@ +import os +import tempfile + from twisted.internet.defer import fail from landscape.amp import ComponentPublisher @@ -7,6 +10,7 @@ from landscape.manager.usermanager impor from landscape.user.tests.helpers import FakeUserProvider from landscape.tests.helpers import LandscapeTest, MonitorHelper from landscape.tests.mocker import ANY +import landscape.monitor.usermonitor class UserMonitorNoManagerTest(LandscapeTest): @@ -59,10 +63,16 @@ class UserMonitorTest(LandscapeTest): self.publisher.start() self.provider = FakeUserProvider() self.plugin = UserMonitor(self.provider) + # Part of bug 1048576 remediation: + self._original_USER_UPDATE_FLAG_FILE = ( + landscape.monitor.usermonitor.USER_UPDATE_FLAG_FILE) def tearDown(self): self.publisher.stop() self.plugin.stop() + # Part of bug 1048576 remediation: + landscape.monitor.usermonitor.USER_UPDATE_FLAG_FILE = ( + self._original_USER_UPDATE_FLAG_FILE) return super(UserMonitorTest, self).tearDown() def test_constants(self): @@ -326,6 +336,65 @@ class UserMonitorTest(LandscapeTest): result.addCallback(got_result) result.addCallback(lambda x: connector.disconnect()) return result + + def test_detect_changes_clears_user_provider_if_flag_file_exists(self): + """ + Temporary bug 1508110 remediation: If a special flag file exists, + cached user data is dumped and a complete refresh of all user data is + transmitted. + """ + self.monitor.add(self.plugin) + + class FauxUserChanges(object): + cleared = False + + def __init__(self, *args): + pass + + def create_diff(self): + return None + + def clear(self): + self.__class__.cleared = True + + # Create the (temporary, test) user update flag file. + landscape.monitor.usermonitor.USER_UPDATE_FLAG_FILE = \ + update_flag_file = tempfile.mkstemp()[1] + self.addCleanup(lambda: os.remove(update_flag_file)) + + # Trigger a detect changes. + self.plugin._persist = None + self.plugin._detect_changes([], UserChanges=FauxUserChanges) + + # The clear() method was called. + self.assertTrue(FauxUserChanges.cleared) + + def test_detect_changes_deletes_flag_file(self): + """ + Temporary bug 1508110 remediation: The total user data refresh flag + file is deleted once the data has been sent. + """ + + def got_result(result): + # The flag file has been deleted. + self.assertFalse(os.path.exists(update_flag_file)) + + # Create the (temporary, test) user update flag file. + landscape.monitor.usermonitor.USER_UPDATE_FLAG_FILE = \ + update_flag_file = tempfile.mkstemp()[1] + + self.broker_service.message_store.set_accepted_types(["users"]) + self.provider.users = [("jdoe", "x", 1000, 1000, "JD,,,,", + "/home/jdoe", "/bin/sh")] + self.provider.groups = [] + + self.monitor.add(self.plugin) + connector = RemoteUserMonitorConnector(self.reactor, self.config) + result = connector.connect() + result.addCallback(lambda remote: remote.detect_changes()) + result.addCallback(got_result) + result.addCallback(lambda x: connector.disconnect()) + return result def test_no_message_if_not_accepted(self): """ Index: landscape-client-14.12/landscape/monitor/usermonitor.py =================================================================== --- landscape-client-14.12.orig/landscape/monitor/usermonitor.py +++ landscape-client-14.12/landscape/monitor/usermonitor.py @@ -1,3 +1,7 @@ +import logging +import os +import os.path + from twisted.internet.defer import maybeDeferred from landscape.lib.log import log_failure @@ -8,6 +12,10 @@ from landscape.user.changes import UserC from landscape.user.provider import UserProvider +# Part of bug 1048576 remediation: +USER_UPDATE_FLAG_FILE = "user-update-flag" + + class UserMonitor(MonitorPlugin): """ A plugin which monitors the system user databases. @@ -87,7 +95,8 @@ class UserMonitor(MonitorPlugin): result.addErrback(lambda f: self._detect_changes([], operation_id)) return result - def _detect_changes(self, locked_users, operation_id=None): + def _detect_changes(self, locked_users, operation_id=None, + UserChanges=UserChanges): def update_snapshot(result): changes.snapshot() @@ -99,6 +108,22 @@ class UserMonitor(MonitorPlugin): self._provider.locked_users = locked_users changes = UserChanges(self._persist, self._provider) + + # Part of bug 1048576 remediation: If the flag file exists, we need to + # do a full update of user data. + full_refresh = os.path.exists(self.user_update_flag_file_path) + if full_refresh: + # Clear the record of what changes have been sent to the server in + # order to force sending of all user data which will do one of two + # things server side: either the server has no user data at all, + # in which case it will now have a complete copy, otherwise it + # will have at least some user data which this message will + # duplicate, provoking the server to note the inconsistency and + # request a full resync of the user data. Either way, the result + # is the same: the client and server will be in sync with regard + # to users. + changes.clear() + message = changes.create_diff() if message: @@ -108,9 +133,35 @@ class UserMonitor(MonitorPlugin): result = self.registry.broker.send_message( message, self._session_id, urgent=True) result.addCallback(update_snapshot) + + # Part of bug 1048576 remediation: + if full_refresh: + # If we are doing a full refresh, we want to remove the flag + # file that triggered the refresh if it completes successfully. + result.addCallback(lambda _: self._remove_update_flag_file()) + result.addErrback(log_error) return result + def _remove_update_flag_file(self): + """Remove the full update flag file, logging any errors. + + This is part of the bug 1048576 remediation. + """ + try: + os.remove(self.user_update_flag_file_path) + except OSError: + logging.exception("Error removing user update flag file.") + + @property + def user_update_flag_file_path(self): + """Location of the user update flag file. + + This is part of the bug 1048576 remediation. + """ + return os.path.join( + self.registry.config.data_path, USER_UPDATE_FLAG_FILE) + class RemoteUserMonitorConnector(ComponentConnector): Index: landscape-client-14.12/landscape/user/tests/helpers.py =================================================================== --- landscape-client-14.12.orig/landscape/user/tests/helpers.py +++ landscape-client-14.12/landscape/user/tests/helpers.py @@ -42,7 +42,7 @@ class FakeUserManagement(object): gecos_string = "%s,%s,%s,%s" % (name, location or "", work_phone or "", home_phone or "") userdata = (username, "x", uid, primary_gid, gecos_string, - "/bin/sh" , "/home/user") + "/bin/sh", "/home/user") self.provider.users.append(userdata) except KeyError: raise UserManagementError("add_user failed") @@ -61,7 +61,8 @@ class FakeUserManagement(object): data = self._users.get(username, None) if data: data["enabled"] = True - # This will generate a shadow file with only the unlocked user in it. + # This will generate a shadow file with only the unlocked user in + # it. self._make_fake_shadow_file([], [username]) return "unlock_user succeeded" raise UserManagementError("unlock_user failed") @@ -94,7 +95,7 @@ class FakeUserManagement(object): userdata = (username, "x", data["uid"], data["primary-gid"], "%s,%s,%s,%s," % (name, location, work_number, home_number), - "/bin/sh" , "/home/user") + "/bin/sh", "/home/user") self.provider.users = [userdata] return "set_user_details succeeded" @@ -150,7 +151,7 @@ class FakeUserManagement(object): class FakeUserProvider(UserProviderBase): - def __init__(self, users=None, groups=None, popen=None, shadow_file=None, + def __init__(self, users=None, groups=None, popen=None, shadow_file=None, locked_users=None): self.users = users self.groups = groups @@ -169,6 +170,7 @@ class FakeUserProvider(UserProviderBase) self.groups = [] return self.groups + class FakeUserInfo(object): """Implements enough functionality to work for Changes tests.""" debian/patches/nutanix-kvm.patch0000644000000000000000000000142113404451062014114 0ustar Description: Update vm_info.py to include Nutanix hypervisor Author: Simon Poirier Origin: upstream, https://github.com/CanonicalLtd/landscape-client/commit/6aa197e083e08734ab84e77039ce4fd057ec268c Bug-Ubuntu: https://bugs.launchpad.net/bugs/1788219 Last-Update: 2018-12-10 Index: sru/landscape/lib/vm_info.py =================================================================== --- sru.orig/landscape/lib/vm_info.py 2018-12-03 18:27:08.370239645 -0500 +++ sru/landscape/lib/vm_info.py 2018-12-03 18:27:08.370239645 -0500 @@ -75,6 +75,7 @@ ("google", "gce"), ("innotek", "virtualbox"), ("microsoft", "hyperv"), + ("nutanix", "kvm"), ("openstack", "kvm"), ("qemu", "kvm"), ("vmware", "vmware")) debian/patches/bug-1398090-revno-808.diff0000644000000000000000000005173413404451062014534 0ustar Description: Changed the package reporter so it retries running apt-get update when an error 100 is returned. Merge retry-apt-update [f=1398090] [r=danilo,free.ekanayaka] [a=Fernando Correa Neto] Change the package reporter so it retries running apt-get update when an error 100 is returned, pretty much in the same way the charm code does it. Author: Fernando Correa Neto Origin: upstream, http://bazaar.launchpad.net/~landscape/landscape-client/trunk/revision/808 Bug: https://bugs.launchpad.net/bugs/1398090 Bug-Ubuntu: https://bugs.launchpad.net/bugs/1398090 Index: landscape-client-14.12/landscape/package/changer.py =================================================================== --- landscape-client-14.12.orig/landscape/package/changer.py 2017-08-07 19:50:10.435944749 +0000 +++ landscape-client-14.12/landscape/package/changer.py 2017-08-07 19:50:10.431944706 +0000 @@ -66,7 +66,8 @@ def __init__(self, store, facade, remote, config, process_factory=reactor, landscape_reactor=None, reboot_required_filename=REBOOT_REQUIRED_FILENAME): - super(PackageChanger, self).__init__(store, facade, remote, config) + super(PackageChanger, self).__init__( + store, facade, remote, config, landscape_reactor) self._process_factory = process_factory if landscape_reactor is None: # For testing purposes. from landscape.reactor import LandscapeReactor Index: landscape-client-14.12/landscape/package/reporter.py =================================================================== --- landscape-client-14.12.orig/landscape/package/reporter.py 2017-08-07 19:50:10.435944749 +0000 +++ landscape-client-14.12/landscape/package/reporter.py 2017-08-07 19:50:10.431944706 +0000 @@ -6,7 +6,8 @@ import glob import apt_pkg -from twisted.internet.defer import Deferred, succeed +from twisted.internet.defer import ( + Deferred, succeed, inlineCallbacks, returnValue) from landscape.lib.sequenceranges import sequence_to_ranges from landscape.lib.twisted_util import gather_results, spawn_process @@ -22,6 +23,7 @@ HASH_ID_REQUEST_TIMEOUT = 7200 MAX_UNKNOWN_HASHES_PER_REQUEST = 500 +LOCK_RETRY_DELAYS = [0, 20, 40] class PackageReporterConfiguration(PackageTaskHandlerConfiguration): @@ -193,8 +195,11 @@ last_update = os.stat(stamp).st_mtime return (last_update + interval) < time.time() + @inlineCallbacks def run_apt_update(self): - """Run apt-update and log a warning in case of non-zero exit code. + """ + Check if an L{_apt_update} call must be performed looping over specific + delays so it can be retried. @return: a deferred returning (out, err, code) """ @@ -202,12 +207,15 @@ or self._apt_update_timeout_expired( self._config.apt_update_interval)): - result = spawn_process(self.apt_update_filename) - - def callback((out, err, code)): - accepted_apt_errors = ( - "Problem renaming the file /var/cache/apt/srcpkgcache.bin", - "Problem renaming the file /var/cache/apt/pkgcache.bin") + accepted_apt_errors = ( + "Problem renaming the file /var/cache/apt/srcpkgcache.bin", + "Problem renaming the file /var/cache/apt/pkgcache.bin") + + for retry in range(len(LOCK_RETRY_DELAYS)): + deferred = Deferred() + self._reactor.call_later( + LOCK_RETRY_DELAYS[retry], self._apt_update, deferred) + out, err, code = yield deferred touch_file(self._config.update_stamp_filename) logging.debug( @@ -215,11 +223,19 @@ self.apt_update_filename, code, out, err)) if code != 0: + if code == 100: + if retry < len(LOCK_RETRY_DELAYS) - 1: + logging.warning( + "Could not acquire the apt lock. Retrying in" + " %s seconds." % LOCK_RETRY_DELAYS[retry + 1]) + continue + logging.warning("'%s' exited with status %d (%s)" % ( self.apt_update_filename, code, err)) - # Errors caused by missing cache files are acceptable, as - # they are not an issue for the lists update process. + # Errors caused by missing cache files are acceptable, + # as they are not an issue for the lists update + # process. # These errors can happen if an 'apt-get clean' is run # while 'apt-get update' is running. for message in accepted_apt_errors: @@ -233,17 +249,25 @@ (self.sources_list_filename, self.sources_list_directory)) - deferred = self._broker.call_if_accepted( + yield self._broker.call_if_accepted( "package-reporter-result", self.send_result, code, err) - deferred.addCallback(lambda ignore: (out, err, code)) - return deferred - - return result.addCallback(callback) - + yield returnValue((out, err, code)) else: logging.debug("'%s' didn't run, update interval has not passed" % self.apt_update_filename) - return succeed(("", "", 0)) + yield returnValue(("", "", 0)) + + def _apt_update(self, deferred): + """ + Run apt-update using the passed in deferred, which allows for callers + to inspect the result code. + """ + result = spawn_process(self.apt_update_filename) + + def callback((out, err, code), deferred): + return deferred.callback((out, err, code)) + + return result.addCallback(callback, deferred) def send_result(self, code, err): """ Index: landscape-client-14.12/landscape/package/taskhandler.py =================================================================== --- landscape-client-14.12.orig/landscape/package/taskhandler.py 2017-08-07 19:50:10.435944749 +0000 +++ landscape-client-14.12/landscape/package/taskhandler.py 2017-08-07 19:50:10.431944706 +0000 @@ -97,13 +97,15 @@ # update-notifier-common package is installed. update_notifier_stamp = "/var/lib/apt/periodic/update-success-stamp" - def __init__(self, package_store, package_facade, remote_broker, config): + def __init__(self, package_store, package_facade, remote_broker, config, + reactor): self._store = package_store self._facade = package_facade self._broker = remote_broker self._config = config self._count = 0 self._session_id = None + self._reactor = reactor def run(self): return self.handle_tasks() @@ -295,7 +297,7 @@ connector = RemoteBrokerConnector(reactor, config, retry_on_reconnect=True) remote = LazyRemoteBroker(connector) - handler = cls(package_store, package_facade, remote, config) + handler = cls(package_store, package_facade, remote, config, reactor) result = Deferred() result.addCallback(lambda x: handler.run()) result.addCallback(lambda x: finish()) Index: landscape-client-14.12/landscape/package/tests/test_releaseupgrader.py =================================================================== --- landscape-client-14.12.orig/landscape/package/tests/test_releaseupgrader.py 2017-08-07 19:50:10.435944749 +0000 +++ landscape-client-14.12/landscape/package/tests/test_releaseupgrader.py 2017-08-07 19:50:10.431944706 +0000 @@ -42,7 +42,7 @@ os.mkdir(self.config.upgrade_tool_directory) self.store = PackageStore(self.makeFile()) self.upgrader = ReleaseUpgrader(self.store, None, - self.remote, self.config) + self.remote, self.config, None) service = self.broker_service service.message_store.set_accepted_types(["operation-result"]) Index: landscape-client-14.12/landscape/package/tests/test_reporter.py =================================================================== --- landscape-client-14.12.orig/landscape/package/tests/test_reporter.py 2017-08-07 19:50:10.435944749 +0000 +++ landscape-client-14.12/landscape/package/tests/test_reporter.py 2017-08-07 19:50:10.431944706 +0000 @@ -25,6 +25,7 @@ from landscape.tests.helpers import ( LandscapeTest, BrokerServiceHelper, EnvironSaverHelper) from landscape.tests.mocker import ANY +from landscape.reactor import FakeReactor SAMPLE_LSB_RELEASE = "DISTRIB_CODENAME=codename\n" @@ -53,8 +54,9 @@ super(PackageReporterAptTest, self).setUp() self.store = PackageStore(self.makeFile()) self.config = PackageReporterConfiguration() + self.reactor = FakeReactor() self.reporter = PackageReporter( - self.store, self.facade, self.remote, self.config) + self.store, self.facade, self.remote, self.config, self.reactor) self.reporter.get_session_id() # Assume update-notifier-common stamp file is not present by # default. @@ -1207,7 +1209,6 @@ deferred = self.reporter.run() def check_result(result): - # The hashes should not go away. hash1 = self.store.get_hash_id(foo_hash) hash2 = self.store.get_hash_id(HASH2) @@ -1228,6 +1229,7 @@ self.assertEqual(request.hashes, [HASH3, HASH1]) deferred.addCallback(check_result) + self.reactor.advance(0) return deferred def test_run_apt_update(self): @@ -1253,6 +1255,7 @@ self.assertEqual("error", err) self.assertEqual(0, code) result.addCallback(callback) + self.reactor.advance(0) result.chainDeferred(deferred) reactor.callWhenRunning(do_test) @@ -1267,18 +1270,14 @@ self.config.load(["--force-apt-update"]) self._make_fake_apt_update() - deferred = Deferred() - - def do_test(): - result = self.reporter.run_apt_update() + result = self.reporter.run_apt_update() - def callback((out, err, code)): - self.assertEqual("output", out) - result.addCallback(callback) - result.chainDeferred(deferred) + def callback((out, err, code)): + self.assertEqual("output", out) - reactor.callWhenRunning(do_test) - return deferred + result.addCallback(callback) + self.reactor.advance(0) + return result def test_run_apt_update_with_force_apt_update_if_sources_changed(self): """ @@ -1291,18 +1290,14 @@ self.reporter.sources_list_filename = self.makeFile("deb ftp://url ./") self._make_fake_apt_update() - deferred = Deferred() - - def do_test(): - result = self.reporter.run_apt_update() + result = self.reporter.run_apt_update() - def callback((out, err, code)): - self.assertEqual("output", out) - result.addCallback(callback) - result.chainDeferred(deferred) + def callback((out, err, code)): + self.assertEqual("output", out) - reactor.callWhenRunning(do_test) - return deferred + result.addCallback(callback) + self.reactor.advance(0) + return result def test_run_apt_update_warns_about_failures(self): """ @@ -1313,21 +1308,87 @@ logging_mock = self.mocker.replace("logging.warning") logging_mock("'%s' exited with status 2" " (error)" % self.reporter.apt_update_filename) + self.mocker.replay() - deferred = Deferred() - def do_test(): - result = self.reporter.run_apt_update() + result = self.reporter.run_apt_update() - def callback((out, err, code)): - self.assertEqual("output", out) - self.assertEqual("error", err) - self.assertEqual(2, code) - result.addCallback(callback) - result.chainDeferred(deferred) + def callback((out, err, code)): + self.assertEqual("output", out) + self.assertEqual("error", err) + self.assertEqual(2, code) - reactor.callWhenRunning(do_test) - return deferred + result.addCallback(callback) + self.reactor.advance(0) + return result + + def test_run_apt_update_warns_about_lock_failure(self): + """ + The L{PackageReporter.run_apt_update} method logs a warnings when + apt-update fails acquiring the lock. + """ + self._make_fake_apt_update(code=100) + logging_mock = self.mocker.replace("logging.warning") + logging_mock("Could not acquire the apt lock. Retrying in 20 seconds.") + logging_mock("Could not acquire the apt lock. Retrying in 40 seconds.") + logging_mock("'%s' exited with status 100 ()" % + self.reporter.apt_update_filename) + + spawn_mock = self.mocker.replace( + "landscape.lib.twisted_util.spawn_process") + spawn_mock(ANY) + # Simulate the first failure. + self.mocker.result(succeed(('', '', 100))) + spawn_mock(ANY) + # Simulate the second failure. + self.mocker.result(succeed(('', '', 100))) + spawn_mock(ANY) + # Simulate the second failure. + self.mocker.result(succeed(('', '', 100))) + + self.mocker.replay() + + result = self.reporter.run_apt_update() + + def callback((out, err, code)): + self.assertEqual("", out) + self.assertEqual("", err) + self.assertEqual(100, code) + + result.addCallback(callback) + self.reactor.advance(60) + return result + + def test_run_apt_update_stops_retrying_after_lock_acquired(self): + """ + When L{PackageReporter.run_apt_update} method successfully acquires the + lock, it will stop retrying. + """ + self._make_fake_apt_update(code=100) + logging_mock = self.mocker.replace("logging.warning") + logging_mock("Could not acquire the apt lock. Retrying in 20 seconds.") + + spawn_mock = self.mocker.replace( + "landscape.lib.twisted_util.spawn_process") + spawn_mock(ANY) + # Simulate the first failure. + self.mocker.result(succeed(('', '', 100))) + spawn_mock(ANY) + # Simulate a successful apt lock grab. + self.mocker.result(succeed(('output', 'error', 0))) + + self.mocker.replay() + + result = self.reporter.run_apt_update() + + def callback((out, err, code)): + self.assertEqual("output", out) + self.assertEqual("error", err) + self.assertEqual(0, code) + + result.addCallback(callback) + self.reactor.advance(20) + return result def test_run_apt_update_report_apt_failure(self): """ @@ -1348,6 +1409,7 @@ [{"type": "package-reporter-result", "code": 2, "err": u"error"}]) result.addCallback(callback) + self.reactor.advance(0) result.chainDeferred(deferred) reactor.callWhenRunning(do_test) @@ -1378,6 +1440,7 @@ [{"type": "package-reporter-result", "code": 1, "err": error}]) result.addCallback(callback) + self.reactor.advance(0) result.chainDeferred(deferred) reactor.callWhenRunning(do_test) @@ -1403,6 +1466,7 @@ [{"type": "package-reporter-result", "code": 2, "err": u"error"}]) result.addCallback(callback) + self.reactor.advance(0) result.chainDeferred(deferred) reactor.callWhenRunning(do_test) @@ -1427,6 +1491,7 @@ [{"type": "package-reporter-result", "code": 0, "err": u"message"}]) result.addCallback(callback) + self.reactor.advance(0) result.chainDeferred(deferred) reactor.callWhenRunning(do_test) @@ -1454,6 +1519,7 @@ self.assertEqual("", err) self.assertEqual(0, code) result.addCallback(callback) + self.reactor.advance(0) result.chainDeferred(deferred) reactor.callWhenRunning(do_test) @@ -1490,6 +1556,7 @@ self.assertEqual("", err) self.assertEqual(0, code) result.addCallback(callback) + self.reactor.advance(0) result.chainDeferred(deferred) reactor.callWhenRunning(do_test) @@ -1526,6 +1593,7 @@ [{"type": "package-reporter-result", "code": 0, "err": u"message"}]) result.addCallback(callback) + self.reactor.advance(0) result.chainDeferred(deferred) reactor.callWhenRunning(do_test) @@ -1547,6 +1615,7 @@ self.assertTrue( os.path.exists(self.config.update_stamp_filename)) result.addCallback(callback) + self.reactor.advance(0) result.chainDeferred(deferred) reactor.callWhenRunning(do_test) @@ -1580,6 +1649,7 @@ [{"type": "package-reporter-result", "code": 0, "err": u""}]) result.addCallback(callback) + self.reactor.advance(0) result.chainDeferred(deferred) reactor.callWhenRunning(do_test) @@ -1616,6 +1686,7 @@ [{"type": "package-reporter-result", "code": 0, "err": u""}]) result.addCallback(callback) + self.reactor.advance(0) result.chainDeferred(deferred) reactor.callWhenRunning(do_test) @@ -1720,8 +1791,9 @@ super(GlobalPackageReporterAptTest, self).setUp() self.store = FakePackageStore(self.makeFile()) self.config = PackageReporterConfiguration() + self.reactor = FakeReactor() self.reporter = FakeGlobalReporter( - self.store, self.facade, self.remote, self.config) + self.store, self.facade, self.remote, self.config, self.reactor) # Assume update-notifier-common stamp file is not present by # default. self.reporter.update_notifier_stamp = "/Not/Existing" @@ -1742,6 +1814,7 @@ def do_test(): self.reporter.get_session_id() result = self.reporter.run_apt_update() + self.reactor.advance(0) def callback(ignore): message = {"type": "package-reporter-result", @@ -1771,8 +1844,9 @@ self.global_store = FakePackageStore(global_file) os.environ["FAKE_PACKAGE_STORE"] = global_file self.config = PackageReporterConfiguration() + self.reactor = FakeReactor() self.reporter = FakeReporter( - self.store, None, self.remote, self.config) + self.store, None, self.remote, self.config, self.reactor) self.config.data_path = self.makeDir() os.mkdir(self.config.package_directory) Index: landscape-client-14.12/landscape/package/tests/test_taskhandler.py =================================================================== --- landscape-client-14.12.orig/landscape/package/tests/test_taskhandler.py 2017-08-07 19:50:10.435944749 +0000 +++ landscape-client-14.12/landscape/package/tests/test_taskhandler.py 2017-08-07 19:50:10.431944706 +0000 @@ -44,8 +44,9 @@ super(PackageTaskHandlerTest, self).setUp() self.config = PackageTaskHandlerConfiguration() self.store = PackageStore(self.makeFile()) + self.reactor = FakeReactor() self.handler = PackageTaskHandler( - self.store, self.facade, self.remote, self.config) + self.store, self.facade, self.remote, self.config, self.reactor) def test_use_hash_id_db(self): @@ -363,7 +364,7 @@ umask(022) handler_args = [] - HandlerMock(ANY, ANY, ANY, ANY) + HandlerMock(ANY, ANY, ANY, ANY, ANY) self.mocker.passthrough() # Let the real constructor run for testing. self.mocker.call(lambda *args: handler_args.extend(args)) @@ -390,7 +391,7 @@ def assert_task_handler(ignored): - store, facade, broker, config = handler_args + store, facade, broker, config, reactor = handler_args try: # Verify the arguments passed to the reporter constructor. @@ -399,6 +400,7 @@ self.assertEqual(type(broker), LazyRemoteBroker) self.assertEqual(type(config), PackageTaskHandlerConfiguration) + self.assertEqual(type(reactor), LandscapeReactor) # Let's see if the store path is where it should be. filename = os.path.join(self.data_path, "package", "database") debian/patches/bug-1434546-revno-815.diff0000644000000000000000000002466713404451062014534 0ustar Description: Client does not return the correct exit code when failing to communicate with server. Merge take-two-1434546-exit-code-2-on-error [f=1434546] [r=ack,benji] [a=Chris Glass] Restore behavior of the program exiting with a non-zero exit code in case of error during registration (specifically, "2"). Author: Christopher Glass Origin: upstream, http://bazaar.launchpad.net/~landscape/landscape-client/trunk/revision/815 Bug: https://bugs.launchpad.net/bugs/1434546 Bug-Ubuntu: https://bugs.launchpad.net/bugs/1434546 --- a/landscape/configuration.py +++ b/landscape/configuration.py @@ -694,8 +694,7 @@ def report_registration_outcome(what_happened, print=print): - """ - Report the registrtion interaction outcome to the user in human-readable + """Report the registrtion interaction outcome to the user in human-readable form. """ if what_happened == "success": @@ -715,6 +714,16 @@ "the server periodically.", file=sys.stderr) +def determine_exit_code(what_happened): + """Return what the application's exit code should be depending on the + registration result. + """ + if what_happened == "success": + return 0 + else: + return 2 # An error happened + + def main(args, print=print): """Interact with the user and the server to set up client configuration.""" @@ -751,9 +760,11 @@ if config.silent: result = register(config, reactor) report_registration_outcome(result, print=print) + sys.exit(determine_exit_code(result)) else: answer = raw_input("\nRequest a new registration for " "this computer now? (Y/n): ") if not answer.upper().startswith("N"): result = register(config, reactor) report_registration_outcome(result, print=print) + sys.exit(determine_exit_code(result)) --- a/landscape/tests/test_configuration.py +++ b/landscape/tests/test_configuration.py @@ -17,7 +17,8 @@ stop_client_and_disable_init_script, ConfigurationError, ImportOptionError, store_public_key_data, bootstrap_tree, got_connection, success, failure, exchange_failure, - handle_registration_errors, done, got_error, report_registration_outcome) + handle_registration_errors, done, got_error, report_registration_outcome, + determine_exit_code) from landscape.lib.amp import MethodCallError from landscape.lib.fetch import HTTPCodeError, PyCurlError from landscape.sysvconfig import SysVConfig, ProcessError @@ -1185,6 +1186,7 @@ register_mock = self.mocker.replace(register, passthrough=False) register_mock(ANY, ANY) + self.mocker.result("success") self.mocker.count(1) self.mocker.replay() @@ -1195,7 +1197,11 @@ "account_name = Old Name\n" "registration_key = Old Password\n" ) - main(["-c", config_filename, "--silent"], print=noop_print) + + exception = self.assertRaises( + SystemExit, main, ["-c", config_filename, "--silent"], + print=noop_print) + self.assertEqual(0, exception.code) def test_main_user_interaction_success(self): """The successful result of register() is communicated to the user.""" @@ -1219,7 +1225,10 @@ def faux_print(string, file=sys.stdout): printed.append((string, file)) - main(["-c", self.make_working_config()], print=faux_print) + exception = self.assertRaises( + SystemExit, main, ["-c", self.make_working_config()], + print=faux_print) + self.assertEqual(0, exception.code) self.assertEqual( [("Please wait...", sys.stdout), ("System successfully registered.", sys.stdout)], @@ -1247,7 +1256,11 @@ def faux_print(string, file=sys.stdout): printed.append((string, file)) - main(["-c", self.make_working_config()], print=faux_print) + exception = self.assertRaises( + SystemExit, main, ["-c", self.make_working_config()], + print=faux_print) + self.assertEqual(2, exception.code) + # Note that the error is output via sys.stderr. self.assertEqual( [("Please wait...", sys.stdout), @@ -1276,7 +1289,11 @@ def faux_print(string, file=sys.stdout): printed.append((string, file)) - main(["--silent", "-c", self.make_working_config()], print=faux_print) + exception = self.assertRaises( + SystemExit, main, ["--silent", "-c", self.make_working_config()], + print=faux_print) + self.assertEqual(0, exception.code) + self.assertEqual( [("Please wait...", sys.stdout), ("System successfully registered.", sys.stdout)], @@ -1304,7 +1321,10 @@ def faux_print(string, file=sys.stdout): printed.append((string, file)) - main(["--silent", "-c", self.make_working_config()], print=faux_print) + exception = self.assertRaises( + SystemExit, main, ["--silent", "-c", self.make_working_config()], + print=faux_print) + self.assertEqual(2, exception.code) # Note that the error is output via sys.stderr. self.assertEqual( [("Please wait...", sys.stdout), @@ -1344,7 +1364,9 @@ register_mock(ANY, ANY) self.mocker.replay() - main(["--config", self.make_working_config()], print=noop_print) + self.assertRaises( + SystemExit, main, ["--config", self.make_working_config()], + print=noop_print) def test_errors_from_restart_landscape(self): """ @@ -1404,7 +1426,8 @@ register_mock(ANY, ANY) self.mocker.replay() - main(["-c", self.make_working_config()], print=noop_print) + self.assertRaises(SystemExit, main, ["-c", self.make_working_config()], + print=noop_print) def test_setup_init_script_and_start_client(self): sysvconfig_mock = self.mocker.patch(SysVConfig) @@ -1442,7 +1465,9 @@ self.mocker.replay() - main(["--silent", "-c", self.make_working_config()], print=noop_print) + self.assertRaises( + SystemExit, main, ["--silent", "-c", self.make_working_config()], + print=noop_print) def test_disable(self): stop_client_and_disable_init_script_mock = self.mocker.replace( @@ -2272,3 +2297,23 @@ "The landscape client will continue to try and contact " "the server periodically.", self.result) self.assertIn(sys.stderr.name, self.output) + + +class DetermineExitCodeTest(unittest.TestCase): + + def test_success_means_exit_code_0(self): + """ + When passed "success" the determine_exit_code function returns 0. + """ + result = determine_exit_code("success") + self.assertEqual(0, result) + + def test_a_failure_means_exit_code_2(self): + """ + When passed a failure result, the determine_exit_code function returns + 2. + """ + failure_codes = ["failure", "ssl-error", "non-ssl-error"] + for code in failure_codes: + result = determine_exit_code(code) + self.assertEqual(2, result) --- a/landscape/package/facade.py +++ b/landscape/package/facade.py @@ -675,12 +675,16 @@ install_progress=install_progress) if not install_progress.dpkg_exited: raise SystemError("dpkg didn't exit cleanly.") - except (apt.cache.LockFailedException, SystemError), error: + except (apt.cache.LockFailedException, SystemError), exception: result_text = (fetch_output.getvalue() + read_file(install_output_path)) - error = TransactionError(error.args[0] + + error = TransactionError(exception.args[0] + "\n\nPackage operation log:\n" + result_text) + if isinstance(exception, SystemError): + # No need to retry SystemError, since it's most + # likely a permanent error. + break else: result_text = (fetch_output.getvalue() + read_file(install_output_path)) --- a/landscape/package/tests/test_facade.py +++ b/landscape/package/tests/test_facade.py @@ -1065,7 +1065,7 @@ "Stderr output", "Stdout output again"], output) - def _test_retry_changes(self, error_type): + def test_retry_changes_lock_failed(self): """ Test that changes are retried with the given exception type. """ @@ -1080,7 +1080,7 @@ def commit1(fetch_progress, install_progress): self.facade._cache.commit = commit2 os.write(2, "bad stuff!\n") - raise error_type("Oops") + raise LockFailedException("Oops") def commit2(fetch_progress, install_progress): install_progress.dpkg_exited = True @@ -1095,15 +1095,28 @@ def test_retry_changes_system_error(self): """ - Changes are retried in the event of a SystemError. + Changes are not retried in the event of a SystemError, since + it's most likely a permanent error. """ - self._test_retry_changes(SystemError) + self.facade.max_dpkg_retries = 1 + deb_dir = self.makeDir() + self._add_package_to_deb_dir(deb_dir, "foo") + self.facade.add_channel_apt_deb("file://%s" % deb_dir, "./") + self.facade.reload_channels() + [foo] = self.facade.get_packages_by_name("foo") + self.facade.mark_install(foo) - def test_retry_changes_lock_failed(self): - """ - Changes are retried in the event of a L{LockFailedException}. - """ - self._test_retry_changes(LockFailedException) + def commit1(fetch_progress, install_progress): + self.facade._cache.commit = commit2 + os.write(2, "bad stuff!\n") + raise SystemError("Oops") + + def commit2(fetch_progress, install_progress): + install_progress.dpkg_exited = True + os.write(1, "good stuff!") + + self.facade._cache.commit = commit1 + self.assertRaises(TransactionError, self.facade.perform_changes) def test_perform_changes_dpkg_error_real(self): """ debian/patches/bug-1409700-revno-807.diff0000644000000000000000000002650013404451062014513 0ustar Description: Add a specific error message when the cause of registration failure is an SSL error. Merge more-information-on-ssl-errors [f=1409700] [r=danilo,free.ekanayaka] [a=Chris Glass] Add a specific error message when the cause of registration failure is an SSL error. Author: Christopher Glass Origin: upstream, http://bazaar.launchpad.net/~landscape/landscape-client/trunk/revision/807 Bug: https://bugs.launchpad.net/bugs/1409700 Bug-Ubuntu: https://bugs.launchpad.net/bugs/1409700 --- a/landscape/broker/amp.py +++ b/landscape/broker/amp.py @@ -29,7 +29,8 @@ callable will be fired. """ result = self.listen_events(handlers.keys()) - return result.addCallback(lambda event_type: handlers[event_type]()) + return result.addCallback( + lambda (event_type, kwargs): handlers[event_type](**kwargs)) class FakeRemoteBroker(object): --- a/landscape/broker/exchange.py +++ b/landscape/broker/exchange.py @@ -348,7 +348,7 @@ from twisted.internet.defer import Deferred, succeed -from landscape.lib.fetch import HTTPCodeError +from landscape.lib.fetch import HTTPCodeError, PyCurlError from landscape.lib.message import got_next_expected, ANCIENT from landscape.lib.versioning import is_version_higher, sort_versions from landscape.log import format_delta @@ -573,6 +573,7 @@ def handle_failure(error_class, error, traceback): self._exchanging = False + if isinstance(error, HTTPCodeError) and error.http_code == 404: # If we got a 404 HTTP error it could be that we're trying to # speak a server API version that the server does not support, @@ -583,7 +584,17 @@ self._message_store.set_server_api(DEFAULT_SERVER_API) self.exchange() return - self._reactor.fire("exchange-failed") + + ssl_error = False + if isinstance(error, PyCurlError) and error.error_code == 60: + # The error returned is an SSL error, most likely the server + # is using a self-signed certificate. Let's fire a special + # event so that the GUI can display a nice message. + logging.error("Message exchange failed: %s" % error.message) + ssl_error = True + + self._reactor.fire("exchange-failed", ssl_error=ssl_error) + self._message_store.record_failure(int(self._reactor.time())) logging.info("Message exchange failed.") exchange_completed() --- a/landscape/broker/server.py +++ b/landscape/broker/server.py @@ -312,10 +312,10 @@ def get_handler(event_type): - def handler(): + def handler(**kwargs): for call in calls: self._reactor.cancel_call(call) - deferred.callback(event_type) + deferred.callback((event_type, kwargs)) return handler --- a/landscape/broker/tests/test_amp.py +++ b/landscape/broker/tests/test_amp.py @@ -171,7 +171,7 @@ self.reactor.call_later(0.05, self.reactor.fire, "event2") self.reactor.advance(0.05) self.remote._factory.fake_connection.flush() - self.assertEqual("event2", self.successResultOf(deferred)) + self.assertEqual(("event2", {}), self.successResultOf(deferred)) def test_call_on_events(self): """ --- a/landscape/broker/tests/test_exchange.py +++ b/landscape/broker/tests/test_exchange.py @@ -1,6 +1,6 @@ from landscape import CLIENT_API from landscape.lib.persist import Persist -from landscape.lib.fetch import HTTPCodeError +from landscape.lib.fetch import HTTPCodeError, PyCurlError from landscape.lib.hashlib import md5 from landscape.schema import Message, Int from landscape.broker.config import BrokerConfiguration @@ -993,7 +993,7 @@ """ events = [] - def failed_exchange(): + def failed_exchange(ssl_error=False): events.append(None) self.reactor.call_on("exchange-failed", failed_exchange) @@ -1001,6 +1001,43 @@ self.exchanger.exchange() self.assertEqual([None], events) + def test_SSL_error_exchanging_causes_failed_exchange(self): + """ + If an SSL error occurs when exchanging, the 'exchange-failed' + event should be fired with the optional "ssl_error" flag set to True. + """ + self.log_helper.ignore_errors("Message exchange failed: Failed to " + "communicate.") + events = [] + + def failed_exchange(ssl_error): + if ssl_error: + events.append(None) + + self.reactor.call_on("exchange-failed", failed_exchange) + self.transport.responses.append(PyCurlError(60, + "Failed to communicate.")) + self.exchanger.exchange() + self.assertEqual([None], events) + + def test_pycurl_error_exchanging_causes_failed_exchange(self): + """ + If an undefined PyCurl error is raised during exchange, (not an SSL + error), the 'exchange-failed' event should be fired with the ssl_error + flag set to False. + """ + events = [] + + def failed_exchange(ssl_error): + if not ssl_error: + events.append(None) + + self.reactor.call_on("exchange-failed", failed_exchange) + self.transport.responses.append(PyCurlError(10, # Not 60 + "Failed to communicate.")) + self.exchanger.exchange() + self.assertEqual([None], events) + def test_wb_error_exchanging_records_failure_in_message_store(self): """ If a traceback occurs whilst exchanging, the failure is recorded --- a/landscape/broker/tests/test_server.py +++ b/landscape/broker/tests/test_server.py @@ -348,19 +348,33 @@ The L{BrokerServer.listen_events} method returns a deferred which is fired when the first of the given events occurs. """ - result = self.broker.listen_events(["event1", "event2"]) + deferred = self.broker.listen_events(["event1", "event2"]) self.reactor.fire("event2") - return self.assertSuccess(result, "event2") + result = self.successResultOf(deferred) + self.assertIsNotNone(result) + + def test_listen_events_with_payload(self): + """ + The L{BrokerServer.listen_events} method returns a deferred which is + fired when the first of the given events occurs. The result of the + deferred is a 2-tuple with name of the event and any keyword arguments + passed when the event was fired. + """ + deferred = self.broker.listen_events(["event1", "event2"]) + self.reactor.fire("event2", foo=123) + result = self.successResultOf(deferred) + self.assertEqual(("event2", {"foo": 123}), result) def test_listen_event_only_once(self): """ The L{BrokerServer.listen_events} listens only to one occurrence of the given events. """ - result = self.broker.listen_events(["event"]) + deferred = self.broker.listen_events(["event"]) self.assertEqual(self.reactor.fire("event"), [None]) self.assertEqual(self.reactor.fire("event"), []) - return self.assertSuccess(result, "event") + result = self.successResultOf(deferred) + self.assertEqual("event", result[0]) def test_listen_events_call_cancellation(self): """ --- a/landscape/configuration.py +++ b/landscape/configuration.py @@ -626,12 +626,20 @@ def success(): on_message("System successfully registered.") - def exchange_failure(): - on_message("We were unable to contact the server. " - "Your internet connection may be down. " - "The landscape client will continue to try and contact " - "the server periodically.", - error=True) + def exchange_failure(ssl_error=False): + if ssl_error: + message = ("\nThe server's SSL information is incorrect, or fails " + "signature verification!\n" + "If the server is using a self-signed certificate, " + "please ensure you supply it with the --ssl-public-key " + "parameter.") + else: + message = ("\nWe were unable to contact the server.\n" + "Your internet connection may be down. " + "The landscape client will continue to try and contact " + "the server periodically.") + + on_message(message, error=True) return 2 def handle_registration_errors(failure): --- a/landscape/tests/test_configuration.py +++ b/landscape/tests/test_configuration.py @@ -1805,13 +1805,64 @@ self.mocker.call(register_done) # The deferred errback finally prints out this message. - print_text_mock("We were unable to contact the server. " + print_text_mock("\nWe were unable to contact the server.\n" "Your internet connection may be down. " "The landscape client will continue to try and " "contact the server periodically.", error=True) reactor_mock.stop() + + # This is actually called after everything else since all deferreds + # are synchronous and callbacks will be executed immediately. + reactor_mock.run() + + # Nothing else is printed! + print_text_mock(ANY) + self.mocker.count(0) + + self.mocker.replay() + + # DO IT! + exit = [] + register(self.config, print_text, exit.append, reactor=FakeReactor()) + self.assertEqual([2], exit) + + def test_register_exchange_SSL_failure(self): + """ + When registration fails because the server's SSL certificate could not + be validated, a message is printed and the program quits. + """ + service = self.broker_service + + registration_mock = self.mocker.replace(service.registration) + print_text_mock = self.mocker.replace(print_text) + reactor_mock = self.mocker.patch(FakeReactor) + + # This must necessarily happen in the following order. + self.mocker.order() + + # This very informative message is printed out. + print_text_mock("Please wait... ", "") + + time_mock = self.mocker.replace("time") + time_mock.sleep(ANY) + self.mocker.count(1) + + def register_done(): + service.reactor.fire("exchange-failed", ssl_error=True) + registration_mock.register() + self.mocker.call(register_done) + + # The deferred errback finally prints out this message. + print_text_mock("\nThe server's SSL information is incorrect, or " + "fails signature verification!\n" + "If the server is using a self-signed certificate, " + "please ensure you supply it with the " + "--ssl-public-key parameter.", + error=True) + + reactor_mock.stop() # This is actually called after everything else since all deferreds # are synchronous and callbacks will be executed immediately. debian/patches/post-upgrade-reboot.patch0000644000000000000000000001464713404451062015553 0ustar Description: Force reboot operation in case systemd fails. Author: Simon Poirier Origin: upstream, https://github.com/CanonicalLtd/landscape-client/commit/e5e086565df841688e435890445104bdb3197c75 Bug-Ubuntu: https://bugs.launchpad.net/bugs/1670291 Last-Update: 2018-11-23 Index: sru/landscape/manager/shutdownmanager.py =================================================================== --- sru.orig/landscape/manager/shutdownmanager.py 2018-11-29 18:05:30.698127419 -0500 +++ sru/landscape/manager/shutdownmanager.py 2018-11-29 18:05:30.694127415 -0500 @@ -45,7 +45,7 @@ protocol = ShutdownProcessProtocol() protocol.set_timeout(self.registry.reactor) protocol.result.addCallback(self._respond_success, operation_id) - protocol.result.addErrback(self._respond_failure, operation_id) + protocol.result.addErrback(self._respond_failure, operation_id, reboot) command, args = self._get_command_and_args(protocol, reboot) self._process_factory.spawnProcess(protocol, command, args=args) @@ -58,9 +58,31 @@ lambda _: self.registry.broker.stop_exchanger()) return deferred - def _respond_failure(self, failure, operation_id): + def _respond_failure(self, failure, operation_id, reboot): logging.info("Shutdown request failed.") - return self._respond(FAILED, failure.value.data, operation_id) + failure_report = '\n'.join([ + failure.value.data, + "", + "Attempting to force {operation}. Please note that if this " + "succeeds, Landscape will have no way of knowing and will still " + "mark this activity as having failed. It is recommended you check " + "the state of the machine manually to determine whether " + "{operation} succeeded.".format( + operation="reboot" if reboot else "shutdown") + ]) + deferred = self._respond(FAILED, failure_report, operation_id) + # Add another callback spawning the poweroff or reboot command (which + # seem more reliable in aberrant situations like a post-trusty release + # upgrade where upstart has been replaced with systemd). If this + # succeeds, we won't have any opportunity to report it and if it fails + # we'll already have responded indicating we're attempting to force + # the operation so either way there's no sense capturing output + protocol = ProcessProtocol() + command, args = self._get_command_and_args(protocol, reboot, True) + deferred.addCallback( + lambda _: self._process_factory.spawnProcess( + protocol, command, args=args)) + return deferred def _respond(self, status, data, operation_id): message = {"type": "operation-result", @@ -70,19 +92,23 @@ return self.registry.broker.send_message( message, self._session_id, True) - def _get_command_and_args(self, protocol, reboot): + def _get_command_and_args(self, protocol, reboot, force=False): """ Returns a C{command, args} 2-tuple suitable for use with L{IReactorProcess.spawnProcess}. """ - minutes = "+%d" % (protocol.delay // 60,) - if reboot: - args = ["/sbin/shutdown", "-r", minutes, - "Landscape is rebooting the system"] - else: - args = ["/sbin/shutdown", "-h", minutes, - "Landscape is shutting down the system"] - return "/sbin/shutdown", args + minutes = None if force else "+%d" % (protocol.delay // 60,) + args = { + (False, False): [ + "/sbin/shutdown", "-h", minutes, + "Landscape is shutting down the system"], + (False, True): [ + "/sbin/shutdown", "-r", minutes, + "Landscape is rebooting the system"], + (True, False): ["/sbin/poweroff"], + (True, True): ["/sbin/reboot"], + }[force, reboot] + return args[0], args class ShutdownProcessProtocol(ProcessProtocol): Index: sru/landscape/manager/tests/test_shutdownmanager.py =================================================================== --- sru.orig/landscape/manager/tests/test_shutdownmanager.py 2018-11-29 18:05:30.698127419 -0500 +++ sru/landscape/manager/tests/test_shutdownmanager.py 2018-11-29 18:06:27.506185035 -0500 @@ -1,5 +1,6 @@ from twisted.python.failure import Failure from twisted.internet.error import ProcessTerminated, ProcessDone +from twisted.internet.protocol import ProcessProtocol from landscape.manager.plugin import SUCCEEDED, FAILED from landscape.manager.shutdownmanager import ( @@ -76,16 +77,28 @@ be failed. Data printed by the process prior to the failure is included in the activity's result text. """ - message = {"type": "shutdown", "reboot": False, "operation-id": 100} + message = {"type": "shutdown", "reboot": True, "operation-id": 100} self.plugin.perform_shutdown(message) def restart_failed(message_id): self.assertTrue(self.broker_service.exchanger.is_urgent()) - self.assertEqual( - self.broker_service.message_store.get_pending_messages(), - [{"type": "operation-result", "api": "3.2", - "operation-id": 100, "timestamp": 0, "status": FAILED, - "result-text": u"Failure text is reported."}]) + messages = self.broker_service.message_store.get_pending_messages() + self.assertEqual(len(messages), 1) + message = messages[0] + self.assertEqual(message["type"], "operation-result") + self.assertEqual(message["api"], b"3.2") + self.assertEqual(message["operation-id"], 100) + self.assertEqual(message["timestamp"], 0) + self.assertEqual(message["status"], FAILED) + self.assertIn(u"Failure text is reported.", message["result-text"]) + + # Check that after failing, we attempt to force the shutdown by + # switching the binary called + [spawn1_args, spawn2_args] = self.process_factory.spawns + protocol = spawn2_args[0] + self.assertIsInstance(protocol, ProcessProtocol) + self.assertEqual(spawn2_args[1:3], + ("/sbin/reboot", ["/sbin/reboot"])) [arguments] = self.process_factory.spawns protocol = arguments[0] debian/patches/bug-1409700-revno-812.diff0000644000000000000000000013476413404451062014523 0ustar Description: Add a specific error message when the cause of registration failure is an SSL error. Merge better-self-signed-cert-ux-3 [f=] [r=bjornt,danilo] [a=Benji York] This branch is a pure refactoring of the way the client configuration communicates so as to separate the UI and network code in preparation for better SSL error handling and user interaction. Tests were then able to be improved -- but not as far as we would like. Notes from Chris: Most of the code breaks out nested function declaration to make them easier to test, using functools.partial to keep the invocation sane in a twisted context (test them "flat", use functools to pass closures to event handlers). We used manual stubs instead of mocks for readability and personal preference [of both of us]. Author: Benji York Origin: upstream, http://bazaar.launchpad.net/~landscape/landscape-client/trunk/revision/812 Bug: https://bugs.launchpad.net/bugs/1409700 Bug-Ubuntu: https://bugs.launchpad.net/bugs/1409700 --- a/landscape/broker/amp.py +++ b/landscape/broker/amp.py @@ -65,6 +65,21 @@ return maybeDeferred(callable, *args) return succeed(None) + def call_on_event(self, handlers): + """Call a given handler as soon as a certain event occurs. + + @param handlers: A dictionary mapping event types to callables, where + an event type is string (the name of the event). When the first of + the given event types occurs in the broker reactor, the associated + callable will be fired. + """ + result = self.broker_server.listen_events(handlers.keys()) + return result.addCallback( + lambda (event_type, kwargs): handlers[event_type](**kwargs)) + + def register(self): + return succeed(None) + class RemoteBrokerConnector(ComponentConnector): """Helper to create connections with the L{BrokerServer}.""" --- a/landscape/configuration.py +++ b/landscape/configuration.py @@ -4,12 +4,14 @@ for the C{landscape-config} script. """ +from __future__ import print_function + +from functools import partial import base64 -import time -import sys -import os import getpass +import os import pwd +import sys from StringIO import StringIO @@ -35,6 +37,8 @@ def print_text(text, end="\n", error=False): + """Display the given text to the user, using stderr if flagged as an error. + """ if error: stream = sys.stderr else: @@ -584,8 +588,60 @@ return key_filename -def register(config, on_message=print_text, on_error=sys.exit, reactor=None, - max_retries=14): +def failure(add_result): + """Handle a failed communication by recording the kind of failure.""" + add_result("failure") + + +def exchange_failure(add_result, ssl_error=False): + """Handle a failed call by recording if the failure was SSL-related.""" + if ssl_error: + add_result("ssl-error") + else: + add_result("non-ssl-error") + + +def handle_registration_errors(add_result, connector, failure): + """HAndle a failed registration by recording the kind of failure.""" + # We'll get invalid credentials through the signal. + failure.trap(InvalidCredentialsError, MethodCallError) + add_result("registration-error") + connector.disconnect() + + +def success(add_result): + """Handle a successful communication by recording the fact.""" + add_result("success") + + +def done(ignored_result, connector, reactor): + """Clean up after communicating with the server.""" + connector.disconnect() + reactor.stop() + + +def got_connection(add_result, connector, reactor, remote): + """Handle becomming connected to a broker.""" + handlers = {"registration-done": partial(success, add_result), + "registration-failed": partial(failure, add_result), + "exchange-failed": partial(exchange_failure, add_result)} + deferreds = [ + remote.call_on_event(handlers), + remote.register().addErrback( + handle_registration_errors, add_result, connector)] + results = gather_results(deferreds) + results.addCallback(done, connector, reactor) + return results + + +def got_error(failure, print=print): + """...from broker.""" + print(failure.getTraceback(), file=sys.stderr) + raise SystemExit + + +def register(config, reactor, connector_factory=RemoteBrokerConnector, + got_connection=got_connection, max_retries=14, results=None): """Instruct the Landscape Broker to register the client. The broker will be instructed to reload its configuration and then to @@ -605,91 +661,48 @@ max_retries = 14 0.05 * (1 - 1.62 ** 14) / (1 - 1.62) = 69 seconds - """ - if reactor is None: - reactor = LandscapeReactor() - exit_with_error = [] - - def stop(errors): - if not config.ok_no_register: - for error in errors: - if error is not None: - exit_with_error.append(error) - connector.disconnect() - reactor.stop() - - def failure(): - on_message("Invalid account name or " - "registration key.", error=True) - return 2 - - def success(): - on_message("System successfully registered.") - - def exchange_failure(ssl_error=False): - if ssl_error: - message = ("\nThe server's SSL information is incorrect, or fails " - "signature verification!\n" - "If the server is using a self-signed certificate, " - "please ensure you supply it with the --ssl-public-key " - "parameter.") - else: - message = ("\nWe were unable to contact the server.\n" - "Your internet connection may be down. " - "The landscape client will continue to try and contact " - "the server periodically.") - - on_message(message, error=True) - return 2 - - def handle_registration_errors(failure): - # We'll get invalid credentials through the signal. - failure.trap(InvalidCredentialsError, MethodCallError) - connector.disconnect() - - def catch_all(failure): - on_message(failure.getTraceback(), error=True) - on_message("Unknown error occurred.", error=True) - return [2] - - on_message("Please wait... ", "") - - time.sleep(2) - - def got_connection(remote): - handlers = {"registration-done": success, - "registration-failed": failure, - "exchange-failed": exchange_failure} - deferreds = [ - remote.call_on_event(handlers), - remote.register().addErrback(handle_registration_errors)] - # We consume errors here to ignore errors after the first one. - # catch_all will be called for the very first deferred that fails. - results = gather_results(deferreds, consume_errors=True) - results.addErrback(catch_all) - results.addCallback(stop) - - def got_error(failure): - on_message("There was an error communicating with the Landscape" - " client.", error=True) - on_message("This machine will be registered with the provided " - "details when the client runs.", error=True) - stop([2]) - - connector = RemoteBrokerConnector(reactor, config) - result = connector.connect(max_retries=max_retries, quiet=True) - result.addCallback(got_connection) - result.addErrback(got_error) - + """ + if results is None: + results = [] + add_result = results.append + + connector = connector_factory(reactor, config) + connection = connector.connect(max_retries=max_retries, quiet=True) + connection.addCallback( + partial(got_connection, add_result, connector, reactor)) + connection.addErrback(got_error) reactor.run() - if exit_with_error: - on_error(exit_with_error[0]) + assert len(results) == 1, "We expect exactly one result." + # Results will be things like "success" or "ssl-error". + return results[0] + + +def report_registration_outcome(what_happened, print=print): + """ + Report the registrtion interaction outcome to the user in human-readable + form. + """ + if what_happened == "success": + print("System successfully registered.") + elif what_happened == "failure": + print("Invalid account name or registration key.", file=sys.stderr) + elif what_happened == "ssl-error": + print("\nThe server's SSL information is incorrect, or fails " + "signature verification!\n" + "If the server is using a self-signed certificate, " + "please ensure you supply it with the --ssl-public-key " + "parameter.", file=sys.stderr) + elif what_happened == "non-ssl-error": + print("\nWe were unable to contact the server.\n" + "Your internet connection may be down. " + "The landscape client will continue to try and contact " + "the server periodically.", file=sys.stderr) - return result +def main(args, print=print): + """Interact with the user and the server to set up client configuration.""" -def main(args): config = LandscapeSetupConfiguration() try: config.load(args) @@ -716,11 +729,16 @@ print_text(str(e)) sys.exit("Aborting Landscape configuration") + print("Please wait...") + # Attempt to register the client. + reactor = LandscapeReactor() if config.silent: - register(config) + result = register(config, reactor) + report_registration_outcome(result, print=print) else: answer = raw_input("\nRequest a new registration for " "this computer now? (Y/n): ") if not answer.upper().startswith("N"): - register(config) + result = register(config, reactor) + report_registration_outcome(result, print=print) --- a/landscape/tests/helpers.py +++ b/landscape/tests/helpers.py @@ -359,6 +359,7 @@ transport_factory = FakeTransport test_case.broker_service = FakeBrokerService(config) + test_case.reactor = test_case.broker_service.reactor test_case.remote = FakeRemoteBroker( test_case.broker_service.exchanger, test_case.broker_service.message_store, --- a/landscape/tests/test_configuration.py +++ b/landscape/tests/test_configuration.py @@ -1,27 +1,29 @@ -import os -import sys -from getpass import getpass +from __future__ import print_function + from ConfigParser import ConfigParser from cStringIO import StringIO +from getpass import getpass +import os +import sys +import unittest -from twisted.internet.defer import succeed, fail -from twisted.internet.task import Clock +from twisted.internet.defer import succeed -from landscape.lib.amp import MethodCallSender -from landscape.reactor import LandscapeReactor, FakeReactor +from landscape.broker.registration import InvalidCredentialsError +from landscape.tests.helpers import FakeBrokerServiceHelper +from landscape.broker.tests.helpers import RemoteBrokerHelper +from landscape.lib.amp import MethodCallError from landscape.lib.fetch import HTTPCodeError, PyCurlError from landscape.configuration import ( print_text, LandscapeSetupScript, LandscapeSetupConfiguration, register, setup, main, setup_init_script_and_start_client, stop_client_and_disable_init_script, ConfigurationError, ImportOptionError, store_public_key_data, - bootstrap_tree) -from landscape.broker.registration import InvalidCredentialsError + bootstrap_tree, got_connection, success, failure, exchange_failure, + handle_registration_errors, done, got_error, report_registration_outcome) from landscape.sysvconfig import SysVConfig, ProcessError -from landscape.tests.helpers import ( - LandscapeTest, BrokerServiceHelper, EnvironSaverHelper) -from landscape.tests.mocker import ARGS, ANY, MATCH, CONTAINS, expect -from landscape.broker.amp import RemoteBroker +from landscape.tests.helpers import LandscapeTest, EnvironSaverHelper +from landscape.tests.mocker import ANY, MATCH, CONTAINS, expect class LandscapeConfigurationTest(LandscapeTest): @@ -41,6 +43,113 @@ return config +class SuccessTests(unittest.TestCase): + def test_success(self): + """The success handler records the success.""" + results = [] + success(results.append) + self.assertEqual(["success"], results) + + +class FailureTests(unittest.TestCase): + def test_failure(self): + """The failure handler records the failure and returns non-zero.""" + results = [] + self.assertNotEqual(0, failure(results.append)) + self.assertEqual(["failure"], results) + + +class ExchangeFailureTests(unittest.TestCase): + + def test_exchange_failure_ssl(self): + """The exchange_failure() handler records whether or not the failure + involved SSL or not and returns non-zero.""" + results = [] + self.assertNotEqual(0, + exchange_failure(results.append, ssl_error=True)) + self.assertEqual(["ssl-error"], results) + + def test_exchange_failure_non_ssl(self): + """ + The exchange_failure() handler records whether or not the failure + involved SSL or not and returns non-zero. + """ + results = [] + self.assertNotEqual(0, + exchange_failure(results.append, ssl_error=False)) + self.assertEqual(["non-ssl-error"], results) + + +class HandleRegistrationErrorsTests(unittest.TestCase): + + def test_handle_registration_errors(self): + """ + The handle_registration_errors() function handles + InvalidCredentialsError and MethodCallError errors and records the + type of failure and disconnects. + """ + class FauxFailure(object): + def trap(self, *trapped): + self.trapped_exceptions = trapped + + faux_connector = FauxConnector() + faux_failure = FauxFailure() + + results = [] + self.assertNotEqual(0, + handle_registration_errors( + results.append, faux_connector, faux_failure)) + self.assertEqual(["registration-error"], results) + self.assertTrue(faux_connector.was_disconnected) + self.assertTrue( + [InvalidCredentialsError, MethodCallError], + faux_failure.trapped_exceptions) + + +class DoneTests(unittest.TestCase): + + def test_done(self): + """The done() function handles cleaning up.""" + class FauxConnector(object): + was_disconnected = False + + def disconnect(self): + self.was_disconnected = True + + class FauxReactor(object): + was_stopped = False + + def stop(self): + self.was_stopped = True + + faux_connector = FauxConnector() + faux_reactor = FauxReactor() + + done(None, faux_connector, faux_reactor) + self.assertTrue(faux_connector.was_disconnected) + self.assertTrue(faux_reactor.was_stopped) + + +class GotErrorTests(unittest.TestCase): + + def test_got_error(self): + """The got_error() function handles displaying errors and exiting.""" + class FauxFailure(object): + + def getTraceback(self): + return "traceback" + + printed = [] + + def faux_print(text, file): + printed.append((text, file)) + + with self.assertRaises(SystemExit): + got_error(FauxFailure(), print=faux_print) + + self.assertEqual([('traceback', sys.stderr)], printed) + + class PrintTextTest(LandscapeTest): def test_default(self): @@ -683,6 +792,11 @@ self.assertTrue(os.path.isdir(annotations_path)) +def noop_print(*args, **kws): + """A print that doesn't do anything.""" + pass + + class ConfigurationFunctionsTest(LandscapeConfigurationTest): helpers = [EnvironSaverHelper] @@ -1022,12 +1136,12 @@ # This must not be called. register_mock = self.mocker.replace(register, passthrough=False) - register_mock(ANY) + register_mock(ANY, ANY) self.mocker.count(0) self.mocker.replay() - main(["-c", self.make_working_config()]) + main(["-c", self.make_working_config()], print=noop_print) def test_main_silent(self): """ @@ -1038,7 +1152,7 @@ setup_mock(ANY) register_mock = self.mocker.replace(register, passthrough=False) - register_mock(ANY) + register_mock(ANY, ANY) self.mocker.count(1) self.mocker.replay() @@ -1049,7 +1163,121 @@ "account_name = Old Name\n" "registration_key = Old Password\n" ) - main(["-c", config_filename, "--silent"]) + main(["-c", config_filename, "--silent"], print=noop_print) + + def test_main_user_interaction_success(self): + """The successful result of register() is communicated to the user.""" + setup_mock = self.mocker.replace(setup) + setup_mock(ANY) + + raw_input_mock = self.mocker.replace(raw_input) + raw_input_mock("\nRequest a new registration for " + "this computer now? (Y/n): ") + self.mocker.result("y") + + # The register() function will be called. + register_mock = self.mocker.replace(register, passthrough=False) + register_mock(ANY, ANY) + self.mocker.result("success") + + self.mocker.replay() + + printed = [] + + def faux_print(string, file=sys.stdout): + printed.append((string, file)) + + main(["-c", self.make_working_config()], print=faux_print) + self.assertEqual( + [("Please wait...", sys.stdout), + ("System successfully registered.", sys.stdout)], + printed) + + def test_main_user_interaction_failure(self): + """The failed result of register() is communicated to the user.""" + setup_mock = self.mocker.replace(setup) + setup_mock(ANY) + + raw_input_mock = self.mocker.replace(raw_input) + raw_input_mock("\nRequest a new registration for " + "this computer now? (Y/n): ") + self.mocker.result("y") + + # The register() function will be called. + register_mock = self.mocker.replace(register, passthrough=False) + register_mock(ANY, ANY) + self.mocker.result("failure") + + self.mocker.replay() + + printed = [] + + def faux_print(string, file=sys.stdout): + printed.append((string, file)) + + main(["-c", self.make_working_config()], print=faux_print) + # Note that the error is output via sys.stderr. + self.assertEqual( + [("Please wait...", sys.stdout), + ("Invalid account name or registration key.", sys.stderr)], + printed) + + def test_main_user_interaction_success_silent(self): + """A successful result is communicated to the user even with --silent. + """ + setup_mock = self.mocker.replace(setup) + setup_mock(ANY) + + raw_input_mock = self.mocker.replace(raw_input) + raw_input_mock(ANY) + self.mocker.count(0) + + # The register() function will be called. + register_mock = self.mocker.replace(register, passthrough=False) + register_mock(ANY, ANY) + self.mocker.result("success") + + self.mocker.replay() + + printed = [] + + def faux_print(string, file=sys.stdout): + printed.append((string, file)) + + main(["--silent", "-c", self.make_working_config()], print=faux_print) + self.assertEqual( + [("Please wait...", sys.stdout), + ("System successfully registered.", sys.stdout)], + printed) + + def test_main_user_interaction_failure_silent(self): + """A failure result is communicated to the user even with --silent. + """ + setup_mock = self.mocker.replace(setup) + setup_mock(ANY) + + raw_input_mock = self.mocker.replace(raw_input) + raw_input_mock(ANY) + self.mocker.count(0) + + # The register() function will be called. + register_mock = self.mocker.replace(register, passthrough=False) + register_mock(ANY, ANY) + self.mocker.result("failure") + + self.mocker.replay() + + printed = [] + + def faux_print(string, file=sys.stdout): + printed.append((string, file)) + + main(["--silent", "-c", self.make_working_config()], print=faux_print) + # Note that the error is output via sys.stderr. + self.assertEqual( + [("Please wait...", sys.stdout), + ("Invalid account name or registration key.", sys.stderr)], + printed) def make_working_config(self): return self.makeFile("[client]\n" @@ -1081,10 +1309,10 @@ self.mocker.result("") register_mock = self.mocker.replace(register, passthrough=False) - register_mock(ANY) + register_mock(ANY, ANY) self.mocker.replay() - main(["--config", self.make_working_config()]) + main(["--config", self.make_working_config()], print=noop_print) def test_errors_from_restart_landscape(self): """ @@ -1141,10 +1369,10 @@ self.mocker.result("") register_mock = self.mocker.replace(register, passthrough=False) - register_mock(ANY) + register_mock(ANY, ANY) self.mocker.replay() - main(["-c", self.make_working_config()]) + main(["-c", self.make_working_config()], print=noop_print) def test_setup_init_script_and_start_client(self): sysvconfig_mock = self.mocker.patch(SysVConfig) @@ -1178,11 +1406,11 @@ # The registration logic should be called and passed the configuration # file. register_mock = self.mocker.replace(register, passthrough=False) - register_mock(ANY) + register_mock(ANY, ANY) self.mocker.replay() - main(["--silent", "-c", self.make_working_config()]) + main(["--silent", "-c", self.make_working_config()], print=noop_print) def test_disable(self): stop_client_and_disable_init_script_mock = self.mocker.replace( @@ -1672,469 +1900,203 @@ # we care about are done. -class RegisterFunctionTest(LandscapeConfigurationTest): - - helpers = [BrokerServiceHelper] - - def setUp(self): - super(RegisterFunctionTest, self).setUp() - self.config = LandscapeSetupConfiguration() - self.config.load(["-c", self.config_filename]) - - def test_register_success(self): - service = self.broker_service - - registration_mock = self.mocker.replace(service.registration) - print_text_mock = self.mocker.replace(print_text) - reactor_mock = self.mocker.patch(FakeReactor) - - # This must necessarily happen in the following order. - self.mocker.order() - - # This very informative message is printed out. - print_text_mock("Please wait... ", "") - - time_mock = self.mocker.replace("time") - time_mock.sleep(ANY) - self.mocker.count(1) - - # The register() method is called. We fire the "registration-done" - # event after it's done, so that it cascades into a deferred callback. - - def register_done(): - service.reactor.fire("registration-done") - - registration_mock.register() - self.mocker.call(register_done) - - # The deferred callback finally prints out this message. - print_text_mock("System successfully registered.") - - reactor_mock.stop() - - # This is actually called after everything else since all deferreds - # are synchronous and callbacks will be executed immediately. - reactor_mock.run() - - # Nothing else is printed! - print_text_mock(ANY) - self.mocker.count(0) - - self.mocker.replay() - - # DO IT! - return register(self.config, print_text, sys.exit, - reactor=FakeReactor()) - - def test_register_failure(self): - """ - When registration fails because of invalid credentials, a message will - be printed to the console and the program will exit. - """ - service = self.broker_service - - self.log_helper.ignore_errors(InvalidCredentialsError) - registration_mock = self.mocker.replace(service.registration) - print_text_mock = self.mocker.replace(print_text) - reactor_mock = self.mocker.patch(FakeReactor) - - # This must necessarily happen in the following order. - self.mocker.order() - - # This very informative message is printed out. - print_text_mock("Please wait... ", "") - - time_mock = self.mocker.replace("time") - time_mock.sleep(ANY) - self.mocker.count(1) - - # The register() method is called. We fire the "registration-failed" - # event after it's done, so that it cascades into a deferred errback. - - def register_done(): - service.reactor.fire("registration-failed") - - registration_mock.register() - self.mocker.call(register_done) - - # The deferred errback finally prints out this message. - print_text_mock("Invalid account name or registration key.", - error=True) - - reactor_mock.stop() - - # This is actually called after everything else since all deferreds - # are synchronous and callbacks will be executed immediately. - reactor_mock.run() - - # Nothing else is printed! - print_text_mock(ANY) - self.mocker.count(0) - - self.mocker.replay() - - # DO IT! - exit = [] - register(self.config, print_text, exit.append, reactor=FakeReactor()) - self.assertEqual([2], exit) - - def test_register_exchange_failure(self): - """ - When registration fails because the server couldn't be contacted, a - message is printed and the program quits. - """ - service = self.broker_service - - registration_mock = self.mocker.replace(service.registration) - print_text_mock = self.mocker.replace(print_text) - reactor_mock = self.mocker.patch(FakeReactor) - - # This must necessarily happen in the following order. - self.mocker.order() - - # This very informative message is printed out. - print_text_mock("Please wait... ", "") - - time_mock = self.mocker.replace("time") - time_mock.sleep(ANY) - self.mocker.count(1) - - def register_done(): - service.reactor.fire("exchange-failed") - registration_mock.register() - self.mocker.call(register_done) - - # The deferred errback finally prints out this message. - print_text_mock("\nWe were unable to contact the server.\n" - "Your internet connection may be down. " - "The landscape client will continue to try and " - "contact the server periodically.", - error=True) +class FakeConnectorFactory(object): - reactor_mock.stop() + def __init__(self, remote): + self.remote = remote - # This is actually called after everything else since all deferreds - # are synchronous and callbacks will be executed immediately. - reactor_mock.run() + def __call__(self, reactor, config): + self.reactor = reactor + self.config = config + return self - # Nothing else is printed! - print_text_mock(ANY) - self.mocker.count(0) - - self.mocker.replay() - - # DO IT! - exit = [] - register(self.config, print_text, exit.append, reactor=FakeReactor()) - self.assertEqual([2], exit) - - def test_register_exchange_SSL_failure(self): - """ - When registration fails because the server's SSL certificate could not - be validated, a message is printed and the program quits. - """ - service = self.broker_service - - registration_mock = self.mocker.replace(service.registration) - print_text_mock = self.mocker.replace(print_text) - reactor_mock = self.mocker.patch(FakeReactor) - - # This must necessarily happen in the following order. - self.mocker.order() - - # This very informative message is printed out. - print_text_mock("Please wait... ", "") - - time_mock = self.mocker.replace("time") - time_mock.sleep(ANY) - self.mocker.count(1) - - def register_done(): - service.reactor.fire("exchange-failed", ssl_error=True) - registration_mock.register() - self.mocker.call(register_done) - - # The deferred errback finally prints out this message. - print_text_mock("\nThe server's SSL information is incorrect, or " - "fails signature verification!\n" - "If the server is using a self-signed certificate, " - "please ensure you supply it with the " - "--ssl-public-key parameter.", - error=True) + def connect(self, max_retries=None, quiet=False): + return succeed(self.remote) - reactor_mock.stop() - - # This is actually called after everything else since all deferreds - # are synchronous and callbacks will be executed immediately. - reactor_mock.run() - - # Nothing else is printed! - print_text_mock(ANY) - self.mocker.count(0) - - self.mocker.replay() - - # DO IT! - exit = [] - register(self.config, print_text, exit.append, reactor=FakeReactor()) - self.assertEqual([2], exit) - - def test_register_timeout_failure(self): - service = self.broker_service - - registration_mock = self.mocker.replace(service.registration) - print_text_mock = self.mocker.replace(print_text) - reactor_mock = self.mocker.patch(FakeReactor) - remote_mock = self.mocker.patch(RemoteBroker) - - protocol_mock = self.mocker.patch(MethodCallSender) - protocol_mock.timeout - self.mocker.result(0.1) - self.mocker.count(0, None) - - # This must necessarily happen in the following order. - self.mocker.order() - - # This very informative message is printed out. - print_text_mock("Please wait... ", "") - - time_mock = self.mocker.replace("time") - time_mock.sleep(ANY) - self.mocker.count(1) + def disconnect(self): + return succeed(None) - remote_mock.call_on_event(ANY) - self.mocker.result(succeed(None)) - registration_mock.register() - self.mocker.passthrough() +class RegisterRealFunctionTest(LandscapeConfigurationTest): - # This is actually called after everything else since all deferreds - # are synchronous and callbacks will be executed immediately. - reactor_mock.run() - - # Nothing else is printed! - print_text_mock(ANY) - self.mocker.count(0) - - self.mocker.replay() - - # DO IT! - fake_reactor = FakeReactor() - fake_reactor._reactor = Clock() - deferred = register(self.config, print_text, sys.exit, - reactor=fake_reactor) - fake_reactor._reactor.advance(100) - return deferred - - def test_register_bus_connection_failure(self): - """ - If the socket can't be connected to, landscape-config will print an - explanatory message and exit cleanly. - """ - # This will make the RemoteBrokerConnector.connect call fail - print_text_mock = self.mocker.replace(print_text) - time_mock = self.mocker.replace("time") - sys_mock = self.mocker.replace("sys") - reactor_mock = self.mocker.patch(LandscapeReactor) - - connector_factory = self.mocker.replace( - "landscape.broker.amp.RemoteBrokerConnector", passthrough=False) - connector = connector_factory(ANY, ANY) - connector.connect(max_retries=0, quiet=True) - self.mocker.result(fail(ZeroDivisionError)) - - print_text_mock(ARGS) - time_mock.sleep(ANY) - reactor_mock.run() - - print_text_mock( - CONTAINS("There was an error communicating with the " - "Landscape client"), - error=True) - print_text_mock(CONTAINS("This machine will be registered"), - error=True) - - sys_mock.exit(2) - connector.disconnect() - reactor_mock.stop() - - self.mocker.replay() - - config = self.get_config(["-a", "accountname", "--silent"]) - return register(config, print_text, sys.exit, max_retries=0) - - def test_register_bus_connection_failure_ok_no_register(self): - """ - Exit code 0 will be returned if we can't contact Landscape via DBus and - --ok-no-register was passed. - """ - print_text_mock = self.mocker.replace(print_text) - time_mock = self.mocker.replace("time") - reactor_mock = self.mocker.patch(LandscapeReactor) - - print_text_mock(ARGS) - time_mock.sleep(ANY) - reactor_mock.run() - reactor_mock.stop() - - print_text_mock( - CONTAINS("There was an error communicating with the " - "Landscape client"), - error=True) - print_text_mock(CONTAINS("This machine will be registered"), - error=True) - - self.mocker.replay() - - config = self.get_config( - ["-a", "accountname", "--silent", "--ok-no-register"]) - return self.assertSuccess(register(config, print_text, sys.exit, - max_retries=0)) - - -class RegisterFunctionRetryTest(LandscapeConfigurationTest): - - helpers = [BrokerServiceHelper] + helpers = [FakeBrokerServiceHelper] def setUp(self): - super(RegisterFunctionRetryTest, self).setUp() + super(RegisterRealFunctionTest, self).setUp() self.config = LandscapeSetupConfiguration() self.config.load(["-c", self.config_filename]) - def test_register_with_retry_parameters(self): - """ - Retry parameters are passed to the L{connect} method of the connector. - """ - print_text_mock = self.mocker.replace(print_text) - time_mock = self.mocker.replace("time") - sys_mock = self.mocker.replace("sys") - reactor_mock = self.mocker.patch(LandscapeReactor) - - connector_factory = self.mocker.replace( - "landscape.broker.amp.RemoteBrokerConnector", passthrough=False) - connector = connector_factory(ANY, ANY) - connector.connect(quiet=True, max_retries=12) - - self.mocker.result(succeed(None)) - - print_text_mock(ARGS) - time_mock.sleep(ANY) - reactor_mock.run() - - print_text_mock( - CONTAINS("There was an error communicating with the " - "Landscape client"), - error=True) - print_text_mock(CONTAINS("This machine will be registered"), - error=True) + def test_register_success(self): + self.reactor.call_later(0, self.reactor.fire, "registration-done") + connector_factory = FakeConnectorFactory(self.remote) + result = register( + self.config, self.reactor, connector_factory, max_retries=99) + self.assertEqual("success", result) - sys_mock.exit(2) - connector.disconnect() - reactor_mock.stop() - self.mocker.replay() +class FauxConnection(object): + def __init__(self): + self.callbacks = [] + self.errbacks = [] - config = self.get_config(["-a", "accountname", "--silent"]) - return register(config, print_text, sys.exit, max_retries=12) + def addCallback(self, func, *args, **kws): + self.callbacks.append(func) - def test_register_with_default_retry_parameters(self): - """ - max_retries has reasonable default behavior - retry 14 times which - will result in a wait of about 60 seconds, until the broker has time - to start on heavily loaded systems. + def addErrback(self, func, *args, **kws): + self.errbacks.append(func) - initialDelay = 0.05 - factor = 1.62 - maxDelay = 30 - max_retries = 14 - 0.05 * (1 - 1.62 ** 14) / (1 - 1.62) = 69 seconds - """ - print_text_mock = self.mocker.replace(print_text) - time_mock = self.mocker.replace("time") - sys_mock = self.mocker.replace("sys") - reactor_mock = self.mocker.patch(LandscapeReactor) - - connector_factory = self.mocker.replace( - "landscape.broker.amp.RemoteBrokerConnector", passthrough=False) - connector = connector_factory(ANY, ANY) - connector.connect(quiet=True, max_retries=14) - - self.mocker.result(succeed(None)) - - print_text_mock(ARGS) - time_mock.sleep(ANY) - reactor_mock.run() - - print_text_mock( - CONTAINS("There was an error communicating with the " - "Landscape client"), - error=True) - print_text_mock(CONTAINS("This machine will be registered"), - error=True) +class FauxConnector(object): - sys_mock.exit(2) - connector.disconnect() - reactor_mock.stop() + was_disconnected = False - self.mocker.replay() - - config = self.get_config(["-a", "accountname", "--silent"]) - return register(config, print_text, sys.exit) + def __init__(self, reactor=None, config=None): + self.reactor = reactor + self.config = config + def connect(self, max_retries, quiet): + self.max_retries = max_retries + self.connection = FauxConnection() + return self.connection -class RegisterFunctionNoServiceTest(LandscapeTest): + def disconnect(self): + self.was_disconnected = True - def test_register_unknown_error(self): - """ - When registration fails because of an unknown error, a message is - printed and the program exits. - """ - configuration = LandscapeSetupConfiguration() - # We'll just mock the remote here to have it raise an exception. - connector_factory = self.mocker.replace( - "landscape.broker.amp.RemoteBrokerConnector", passthrough=False) - remote_broker = self.mocker.mock() +class RegisterFunctionTest(LandscapeConfigurationTest): - print_text_mock = self.mocker.replace(print_text) - reactor_mock = self.mocker.patch(FakeReactor) + helpers = [RemoteBrokerHelper] - # This is unordered. It's just way too much of a pain. - print_text_mock("Please wait... ", "") - time_mock = self.mocker.replace("time") - time_mock.sleep(ANY) - self.mocker.count(1) + def setUp(self): + super(RegisterFunctionTest, self).setUp() + self.config = LandscapeSetupConfiguration() + self.config.load(["-c", self.config_filename]) - # SNORE - connector = connector_factory(ANY, configuration) - connector.connect(max_retries=0, quiet=True) - self.mocker.result(succeed(remote_broker)) - remote_broker.call_on_event(ANY) - self.mocker.result(succeed(None)) - - # here it is! - remote_broker.register() - self.mocker.result(fail(ZeroDivisionError)) - - print_text_mock(ANY, error=True) - - def check_logged_failure(text, error): - self.assertTrue("ZeroDivisionError" in text) - self.mocker.call(check_logged_failure) - print_text_mock("Unknown error occurred.", error=True) - - # WHOAH DUDE. This waits for callLater(0, reactor.stop). - connector.disconnect() - reactor_mock.stop() - reactor_mock.run() + def test_register(self): + """Is the async machinery wired up properly?""" - self.mocker.replay() + class FauxFailure(object): + def getTraceback(self): + return 'traceback' + + class FauxReactor(object): + def run(self): + self.was_run = True + + def stop(self, *args): + self.was_stopped = True + + reactor = FauxReactor() + connector = FauxConnector(reactor, self.config) + + def connector_factory(reactor, config): + return connector + + # We pre-seed a success because no actual result will be generated. + register(self.config, reactor, connector_factory, max_retries=99, + results=['success']) + self.assertTrue(reactor.was_run) + # Only a single callback is registered, it does the real work when a + # connection is established. + self.assertTrue(1, len(connector.connection.callbacks)) + self.assertEqual( + 'got_connection', + connector.connection.callbacks[0].func.__name__) + # Should something go wrong, there is an error handler registered. + self.assertTrue(1, len(connector.connection.errbacks)) + self.assertEqual( + 'got_error', + connector.connection.errbacks[0].__name__) + # We ask for retries because networks aren't reliable. + self.assertEqual(99, connector.max_retries) + + def test_got_connection(self): + """got_connection() adds deferreds and callbacks.""" + + def faux_got_connection(add_result, remote, connector, reactor): + pass + + class FauxRemote(object): + handlers = None + deferred = None + + def call_on_event(self, handlers): + assert not self.handlers, "Called twice" + self.handlers = handlers + self.call_on_event_deferred = FauxCallOnEventDeferred() + return self.call_on_event_deferred + + def register(self): + assert not self.deferred, "Called twice" + self.register_deferred = FauxRegisterDeferred() + return self.register_deferred + + class FauxCallOnEventDeferred(object): + def __init__(self): + self.callbacks = [] + self.errbacks = [] + + def addCallbacks(self, *funcs, **kws): + self.callbacks.extend(funcs) + + class FauxRegisterDeferred(object): + def __init__(self): + self.callbacks = [] + self.errbacks = [] + + def addCallback(self, func): + assert func.__name__ == "got_connection", "Wrong callback." + self.callbacks.append(faux_got_connection) + self.gather_results_deferred = GatherResultsDeferred() + return self.gather_results_deferred + + def addCallbacks(self, *funcs, **kws): + self.callbacks.extend(funcs) + + def addErrback(self, func, *args, **kws): + self.errbacks.append(func) + return self + + class GatherResultsDeferred(object): + def __init__(self): + self.callbacks = [] + self.errbacks = [] + + def addCallbacks(self, *funcs, **kws): + self.callbacks.extend(funcs) + + faux_connector = FauxConnector(self.reactor, self.config) + + status_results = [] + faux_remote = FauxRemote() + results = got_connection( + status_results.append, faux_connector, self.reactor, faux_remote) + # We set up two deferreds, one for the RPC call and one for event + # handlers. + self.assertEqual(2, len(results.resultList)) + # Handlers are registered for the events we are interested in. + self.assertEqual( + ['registration-failed', 'exchange-failed', 'registration-done'], + faux_remote.handlers.keys()) + self.assertEqual( + ['failure', 'exchange_failure', 'success'], + [handler.func.__name__ + for handler in faux_remote.handlers.values()]) + # We include a single error handler to react to exchange errors. + self.assertTrue(1, len(faux_remote.register_deferred.errbacks)) + self.assertEqual( + 'handle_registration_errors', + faux_remote.register_deferred.errbacks[0].__name__) - exit = [] - register(configuration, print_text, exit.append, max_retries=0, - reactor=FakeReactor()) - self.assertEqual(exit, [2]) + def test_register_happy_path(self): + """A successful result provokes no exceptions.""" + def faux_got_connection(add_result, remote, connector, reactor): + add_result('success') + self.reactor.call_later(1, self.reactor.stop) + self.assertEqual( + "success", + register(self.config, reactor=self.reactor, + got_connection=faux_got_connection)) class SSLCertificateDataTest(LandscapeConfigurationTest): @@ -2159,3 +2121,41 @@ self.assertEqual(key_filename, store_public_key_data(config, "123456789")) self.assertEqual("123456789", open(key_filename, "r").read()) + + +class ReportRegistrationOutcomeTest(unittest.TestCase): + + def setUp(self): + self.result = [] + self.output = [] + + def record_result(self, result, file=sys.stdout): + self.result.append(result) + self.output.append(file.name) + + def test_success_case(self): + report_registration_outcome("success", print=self.record_result) + self.assertIn("System successfully registered.", self.result) + self.assertIn(sys.stdout.name, self.output) + + def test_failure_case(self): + report_registration_outcome("failure", print=self.record_result) + self.assertIn("Invalid account name or registration key.", self.result) + self.assertIn(sys.stderr.name, self.output) + + def test_ssl_error_case(self): + report_registration_outcome("ssl-error", print=self.record_result) + self.assertIn("\nThe server's SSL information is incorrect, or fails " + "signature verification!\n" + "If the server is using a self-signed certificate, " + "please ensure you supply it with the --ssl-public-key " + "parameter.", self.result) + self.assertIn(sys.stderr.name, self.output) + + def test_non_ssl_error_case(self): + report_registration_outcome("non-ssl-error", print=self.record_result) + self.assertIn("\nWe were unable to contact the server.\n" + "Your internet connection may be down. " + "The landscape client will continue to try and contact " + "the server periodically.", self.result) + self.assertIn(sys.stderr.name, self.output) debian/patches/bug-1428826-revno-813.diff0000644000000000000000000001727513404451062014533 0ustar Description: register() function is a public API without tests and has changed. Merge bug-1428826-restore-register-function-public-API [f=1428826] [r=adam-collard,bjornt] [a=Benji York] Instead of running client configuration as an independent executable (which would have been preferable), the client charm reaches inside and calls the register() function directly. That has resulted in a dependency on register()'s API which was not enforced via testing or documented in register()'s docstring. This branch restores the API the charm depends on, adds documentation as to its meaning and importance, and adds tests that will warn us if the API is broken in the future. Author: Benji York Origin: upstream, http://bazaar.launchpad.net/~landscape/landscape-client/trunk/revision/813 Bug: https://bugs.launchpad.net/bugs/1428826 Bug-Ubuntu: https://bugs.launchpad.net/bugs/1428826 --- a/landscape/configuration.py +++ b/landscape/configuration.py @@ -640,28 +640,32 @@ raise SystemExit -def register(config, reactor, connector_factory=RemoteBrokerConnector, - got_connection=got_connection, max_retries=14, results=None): +def register(config, reactor=None, connector_factory=RemoteBrokerConnector, + got_connection=got_connection, max_retries=14, on_error=None, + results=None): """Instruct the Landscape Broker to register the client. The broker will be instructed to reload its configuration and then to attempt a registration. - @param reactor: The reactor to use. Please only pass reactor when you - have totally mangled everything with mocker. Otherwise bad things - will happen. + @param reactor: The reactor to use. This parameter is optional because + the client charm does not pass it. + @param connector_factory: A callable that accepts a reactor and a + configuration object and returns a new remote broker connection. Used + primarily for dependency injection. + @param got_connection: The handler to trigger when the remote broker + connects. Used primarily for dependency injection. @param max_retries: The number of times to retry connecting to the landscape client service. The delay between retries is calculated - by Twisted and increases geometrically. The default of 14 results in - a total wait time of about 70 seconds. - - initialDelay = 0.05 - factor = 1.62 - maxDelay = 30 - max_retries = 14 - - 0.05 * (1 - 1.62 ** 14) / (1 - 1.62) = 69 seconds + by Twisted and increases geometrically. + @param on_error: A callable that will be passed a non-zero positive + integer argument in the case that some error occurs. This is a legacy + API provided for use by the client charm. + @param results: This parameter provides a mechanism to pre-seed the result + of registering. Used for testing. """ + if reactor is None: + reactor = LandscapeReactor() if results is None: results = [] add_result = results.append @@ -675,7 +679,13 @@ assert len(results) == 1, "We expect exactly one result." # Results will be things like "success" or "ssl-error". - return results[0] + result = results[0] + + # If there was an error and the caller requested that errors be reported + # to the on_error callable, then do so. + if result != "success" and on_error is not None: + on_error(1) + return result def report_registration_outcome(what_happened, print=print): --- a/landscape/tests/test_configuration.py +++ b/landscape/tests/test_configuration.py @@ -10,10 +10,7 @@ from twisted.internet.defer import succeed from landscape.broker.registration import InvalidCredentialsError -from landscape.tests.helpers import FakeBrokerServiceHelper from landscape.broker.tests.helpers import RemoteBrokerHelper -from landscape.lib.amp import MethodCallError -from landscape.lib.fetch import HTTPCodeError, PyCurlError from landscape.configuration import ( print_text, LandscapeSetupScript, LandscapeSetupConfiguration, register, setup, main, setup_init_script_and_start_client, @@ -21,7 +18,10 @@ ImportOptionError, store_public_key_data, bootstrap_tree, got_connection, success, failure, exchange_failure, handle_registration_errors, done, got_error, report_registration_outcome) +from landscape.lib.amp import MethodCallError +from landscape.lib.fetch import HTTPCodeError, PyCurlError from landscape.sysvconfig import SysVConfig, ProcessError +from landscape.tests.helpers import FakeBrokerServiceHelper from landscape.tests.helpers import LandscapeTest, EnvironSaverHelper from landscape.tests.mocker import ANY, MATCH, CONTAINS, expect @@ -2010,6 +2010,32 @@ # We ask for retries because networks aren't reliable. self.assertEqual(99, connector.max_retries) + def test_register_without_reactor(self): + """If no reactor is passed, a LandscapeReactor will be instantiated. + + This behaviour is exclusively for compatability with the client charm + which does not pass in a reactor. + """ + + def connector_factory(reactor, config): + return FauxConnector(reactor, self.config) + + reactor_mock = self.mocker.replace( + "landscape.reactor.LandscapeReactor", passthrough=False) + # The mock acts as both the constructor... + reactor_mock() + self.mocker.result(reactor_mock) + # ...and the constructed reactor itself. + reactor_mock.run() + self.mocker.replay() + + # We pre-seed a success because no actual result will be generated. + register(self.config, connector_factory=connector_factory, + results=["success"]) + # The reactor mock being run is what this test asserts, which is + # verified by the test infrastructure, so there are no assertions + # here. + def test_got_connection(self): """got_connection() adds deferreds and callbacks.""" @@ -2088,6 +2114,43 @@ 'handle_registration_errors', faux_remote.register_deferred.errbacks[0].__name__) + def test_register_with_on_error_and_an_error(self): + """A caller-provided on_error callable will be called if errors occur. + + The on_error parameter is provided for the client charm which calls + register() directly and provides on_error as a keyword argument. + """ + def faux_got_connection(add_result, remote, connector, reactor): + add_result("something bad") + + on_error_was_called = [] + + def on_error(status): + # A positive number is provided for the status. + self.assertGreater(status, 0) + on_error_was_called.append(True) + + self.reactor.call_later(1, self.reactor.stop) + register(self.config, reactor=self.reactor, on_error=on_error, + got_connection=faux_got_connection) + self.assertTrue(on_error_was_called) + + def test_register_with_on_error_and_no_error(self): + """A caller-provided on_error callable will not be called if no error. + """ + def faux_got_connection(add_result, remote, connector, reactor): + add_result("success") + + on_error_was_called = [] + + def on_error(status): + on_error_was_called.append(True) + + self.reactor.call_later(1, self.reactor.stop) + register(self.config, reactor=self.reactor, on_error=on_error, + got_connection=faux_got_connection) + self.assertFalse(on_error_was_called) + def test_register_happy_path(self): """A successful result provokes no exceptions.""" def faux_got_connection(add_result, remote, connector, reactor): debian/patches/1699179-release-upgrade-check.diff0000644000000000000000000002124113404451062016435 0ustar Description: Check if ubuntu-release-upgrader is running before apt-update. Author: Dariusz Gadomski Origin: upstream, https://github.com/CanonicalLtd/landscape-client/commit/c926cfaf3ad1a8759f0b269540391bbfc20dc00a Bug-Ubuntu: https://launchpad.net/bugs/1699179 Last-Update: 2018-11-29 Index: sru/landscape/package/reporter.py =================================================================== --- sru.orig/landscape/package/reporter.py 2018-11-29 18:57:16.767821454 -0500 +++ sru/landscape/package/reporter.py 2018-11-29 19:15:52.753552944 -0500 @@ -5,6 +5,7 @@ import os import glob import apt_pkg +import re from twisted.internet.defer import ( Deferred, succeed, inlineCallbacks, returnValue) @@ -24,6 +25,9 @@ HASH_ID_REQUEST_TIMEOUT = 7200 MAX_UNKNOWN_HASHES_PER_REQUEST = 500 LOCK_RETRY_DELAYS = [0, 20, 40] +PYTHON_BIN = "/usr/bin/python3" +RELEASE_UPGRADER_PATTERN = "/tmp/ubuntu-release-upgrader-" +UID_ROOT = "0" class PackageReporterConfiguration(PackageTaskHandlerConfiguration): @@ -205,6 +209,51 @@ last_update = os.stat(stamp).st_mtime return (last_update + interval) < time.time() + def _is_release_upgrader_running(self): + """Detect whether ubuntu-release-upgrader is running. + + This is done by iterating the /proc tree (to avoid external + dependencies) and checkign the cmdline and the uid of the process. + The assumption is that ubuntu-release-upgrader is something that: + * is run by a python interpreter + * its first argument starts with '/tmp/ubuntu-release-upgrader-' + * is executed by root (effective uid == 0)""" + logging.debug("Checking if ubuntu-release-upgrader is running.") + + for cmdline in glob.glob("/proc/*/cmdline"): + base = os.path.dirname(cmdline) + try: + with open(cmdline) as fd: + read = fd.read() + + pid = os.path.basename(os.path.dirname(cmdline)) + + cmdline = [f for f in read.split("\x00") if f] + if len(cmdline) <= 1: + continue + + with open(os.path.join(base, "status")) as fd: + read = fd.read() + + pattern = re.compile('^Uid\:(.*)$', + re.VERBOSE | re.MULTILINE) + + for pattern in pattern.finditer(read): + uid = pattern.groups()[0].split("\t")[1] + except IOError: + continue + + (executable, args) = (cmdline[0], cmdline[1:]) + + if (executable.startswith(PYTHON_BIN) and + any(x.startswith(RELEASE_UPGRADER_PATTERN) + for x in args) and + uid == UID_ROOT): + logging.info("Found ubuntu-release-upgrader running (pid: %s)" + % (pid)) + return True + return False + @inlineCallbacks def run_apt_update(self): """ @@ -215,7 +264,8 @@ """ if (self._config.force_apt_update or self._apt_sources_have_changed() or self._apt_update_timeout_expired( - self._config.apt_update_interval)): + self._config.apt_update_interval) + ) and not self._is_release_upgrader_running(): accepted_apt_errors = ( "Problem renaming the file /var/cache/apt/srcpkgcache.bin", @@ -263,7 +313,7 @@ "package-reporter-result", self.send_result, code, err) yield returnValue((out, err, code)) else: - logging.debug("'%s' didn't run, update interval has not passed" % + logging.debug("'%s' didn't run, conditions not met" % self.apt_update_filename) yield returnValue(("", "", 0)) Index: sru/landscape/package/tests/test_reporter.py =================================================================== --- sru.orig/landscape/package/tests/test_reporter.py 2018-11-29 18:57:16.767821454 -0500 +++ sru/landscape/package/tests/test_reporter.py 2018-11-29 19:25:56.404558918 -0500 @@ -4,6 +4,7 @@ import apt_pkg import shutil import mock +import subprocess from twisted.internet.defer import Deferred, succeed, inlineCallbacks from twisted.internet import reactor @@ -295,7 +296,7 @@ hash_id_db_url = self.config.package_hash_id_url + "uuid_codename_arch" fetch_async_mock = self.mocker.replace("landscape.lib." "fetch.fetch_async") - fetch_async_mock(hash_id_db_url, cainfo=None, patch=None) + fetch_async_mock(hash_id_db_url, cainfo=None, proxy=None) fetch_async_result = Deferred() fetch_async_result.callback("hash-ids") self.mocker.result(fetch_async_result) @@ -345,7 +346,7 @@ result = self.reporter.fetch_hash_id_db() mock_fetch_async.assert_called_once_with( hash_id_db_url, cainfo=None, proxy="http://helloproxy:8000") - return result + return result def test_fetch_hash_id_db_does_not_download_twice(self): @@ -366,7 +367,7 @@ # Intercept any call to fetch_async fetch_async_mock = self.mocker.replace("landscape.lib." "fetch.fetch_async") - fetch_async_mock(ANY, proxy=None) + fetch_async_mock(ANY) # Go! self.mocker.replay() @@ -1323,6 +1324,7 @@ self.reporter.sources_list_directory = "/I/Dont/Exist" self._make_fake_apt_update() debug_mock = self.mocker.replace("logging.debug") + debug_mock("Checking if ubuntu-release-upgrader is running.") debug_mock("'%s' exited with status 0 (out='output', err='error')" % self.reporter.apt_update_filename) warning_mock = self.mocker.replace("logging.warning") @@ -1419,13 +1421,13 @@ spawn_mock = self.mocker.replace( "landscape.lib.twisted_util.spawn_process") - spawn_mock(ANY) + spawn_mock(ANY, env={}) # Simulate the first failure. self.mocker.result(succeed(('', '', 100))) - spawn_mock(ANY) + spawn_mock(ANY, env={}) # Simulate the second failure. self.mocker.result(succeed(('', '', 100))) - spawn_mock(ANY) + spawn_mock(ANY, env={}) # Simulate the second failure. self.mocker.result(succeed(('', '', 100))) @@ -1453,10 +1455,10 @@ spawn_mock = self.mocker.replace( "landscape.lib.twisted_util.spawn_process") - spawn_mock(ANY) + spawn_mock(ANY, env={}) # Simulate the first failure. self.mocker.result(succeed(('', '', 100))) - spawn_mock(ANY) + spawn_mock(ANY, env={}) # Simulate a successful apt lock grab. self.mocker.result(succeed(('output', 'error', 0))) @@ -1589,7 +1591,7 @@ self.makeFile("", path=self.config.update_stamp_filename) logging_mock = self.mocker.replace("logging.debug") - logging_mock("'%s' didn't run, update interval has not passed" % + logging_mock("'%s' didn't run, conditions not met" % self.reporter.apt_update_filename) self.mocker.replay() deferred = Deferred() @@ -1626,7 +1628,7 @@ self.reporter.update_notifier_stamp = self.makeFile("") logging_mock = self.mocker.replace("logging.debug") - logging_mock("'%s' didn't run, update interval has not passed" % + logging_mock("'%s' didn't run, conditions not met" % self.reporter.apt_update_filename) self.mocker.replay() deferred = Deferred() @@ -1903,6 +1905,24 @@ result = self.reporter._package_state_has_changed() self.assertTrue(result) + def test_is_release_upgrader_running(self): + """ + The L{PackageReporter._is_release_upgrader_running} method should + return True if the simle heuristics detects a release upgrader + running concurrently. + """ + # no 'release upgrader running' + self.assertFalse(self.reporter._is_release_upgrader_running()) + # fake 'release ugrader' running with non-root UID + p = subprocess.Popen([reporter.PYTHON_BIN, '-c', + 'import time; time.sleep(10)', + reporter.RELEASE_UPGRADER_PATTERN + "12345"]) + self.assertFalse(self.reporter._is_release_upgrader_running()) + # fake 'release upgrader' running + reporter.UID_ROOT = "%d" % os.getuid() + self.assertTrue(self.reporter._is_release_upgrader_running()) + p.terminate() + class GlobalPackageReporterAptTest(LandscapeTest): debian/patches/iso-configuration-1699789.diff0000644000000000000000000000601513404451062015765 0ustar Description: Stop reactor in landscape-config on broker error Handle SystemExit exception, as those don't propagate past the reactor, in order to avoid getting stuck when installed in chroot. Author: Simon Poirier Origin: upstream, https://github.com/CanonicalLtd/landscape-client/commit/e0b1b0ca43c13bc65374df13d192405abb3014e6 Bug-Ubuntu: https://launchpad.net/bugs/1699789 Last-Update: 2017-11-10 --- This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ --- a/landscape/configuration.py +++ b/landscape/configuration.py @@ -639,10 +639,12 @@ return results -def got_error(failure, print=print): - """...from broker.""" +def got_error(failure, reactor, add_result, print=print): + """Handle errors contacting broker.""" print(failure.getTraceback(), file=sys.stderr) - raise SystemExit + # Can't just raise SystemExit; it would be ignored by the reactor. + add_result(SystemExit()) + reactor.stop() def register(config, reactor=None, connector_factory=RemoteBrokerConnector, @@ -671,6 +673,7 @@ """ if reactor is None: reactor = LandscapeReactor() + if results is None: results = [] add_result = results.append @@ -679,13 +682,17 @@ connection = connector.connect(max_retries=max_retries, quiet=True) connection.addCallback( partial(got_connection, add_result, connector, reactor)) - connection.addErrback(got_error) + connection.addErrback( + partial(got_error, reactor=reactor, add_result=add_result)) reactor.run() assert len(results) == 1, "We expect exactly one result." # Results will be things like "success" or "ssl-error". result = results[0] + if isinstance(result, SystemExit): + raise result + # If there was an error and the caller requested that errors be reported # to the on_error callable, then do so. if result != "success" and on_error is not None: --- a/landscape/tests/test_configuration.py +++ b/landscape/tests/test_configuration.py @@ -172,14 +172,19 @@ def getTraceback(self): return "traceback" + results = [] printed = [] def faux_print(text, file): printed.append((text, file)) - with self.assertRaises(SystemExit): - got_error(FauxFailure(), print=faux_print) + mock_reactor = mock.Mock() + got_error(FauxFailure(), reactor=mock_reactor, + add_result=results.append, print=faux_print) + mock_reactor.stop.assert_called_once_with() + + self.assertIsInstance(results[0], SystemExit) self.assertEqual([('traceback', sys.stderr)], printed) @@ -2081,7 +2086,7 @@ self.assertTrue(1, len(connector.connection.errbacks)) self.assertEqual( 'got_error', - connector.connection.errbacks[0].__name__) + connector.connection.errbacks[0].func.__name__) # We ask for retries because networks aren't reliable. self.assertEqual(99, connector.max_retries) debian/patches/bug-1508110-revno-824.diff0000644000000000000000000001460313404451062014506 0ustar Description: Users tab doesn't work as expected. Merge timed-force-reset-user-database [f=1508110] [r=ack,free.ekanayaka] [a=Fernando Correa Neto] Change the user monitor to always reset its database upon running. It will prevent data loss if any messages are dropped right after registration. Author: Fernando Correa Neto Origin: upstream, http://bazaar.launchpad.net/~landscape/landscape-client/trunk/revision/824 Bug: https://bugs.launchpad.net/bugs/1508110 Bug-Ubuntu: https://bugs.launchpad.net/bugs/1508110 --- a/landscape/monitor/tests/test_usermonitor.py +++ b/landscape/monitor/tests/test_usermonitor.py @@ -206,6 +206,28 @@ result.addCallback(got_result) return result + def test_run_passes_force_reset_as_true(self): + """ + The run method will pass True for the underlying _run_detect_changes + method to force a database reset. + """ + original_run_detect_changes = self.plugin._run_detect_changes + + def fake_run_detect_changes(operation_id=None, force_reset=False): + return force_reset + + self.plugin._run_detect_changes = fake_run_detect_changes + + def got_result(result): + self.assertTrue(result) + + self.broker_service.message_store.set_accepted_types(["users"]) + self.monitor.add(self.plugin) + result = self.plugin.run() + result.addCallback(got_result) + self.plugin._run_detect_changes = original_run_detect_changes + return result + def test_run_interval(self): """ L{UserMonitor.register} calls the C{register} method on it's --- a/landscape/monitor/usermonitor.py +++ b/landscape/monitor/usermonitor.py @@ -27,7 +27,7 @@ def register(self, registry): super(UserMonitor, self).register(registry) - self.call_on_accepted("users", self._run_detect_changes, None) + self.call_on_accepted("users", self._run_detect_changes, None, True) self._publisher = ComponentPublisher(self, self.registry.reactor, self.registry.config) @@ -49,9 +49,11 @@ return self.registry.broker.call_if_accepted( "users", self._run_detect_changes, operation_id) - run = detect_changes + def run(self, operation_id=None): + return self.registry.broker.call_if_accepted( + "users", self._run_detect_changes, operation_id, True) - def _run_detect_changes(self, operation_id=None): + def _run_detect_changes(self, operation_id=None, force_reset=False): """ If changes are detected an C{urgent-exchange} is fired to send updates to the server immediately. @@ -66,7 +68,7 @@ # We'll skip checking the locked users if we're in monitor-only mode. if getattr(self.registry.config, "monitor_only", False): result = maybeDeferred(self._detect_changes, - [], operation_id) + [], operation_id, force_reset) else: def get_locked_usernames(user_manager): @@ -79,11 +81,13 @@ result = user_manager_connector.connect() result.addCallback(get_locked_usernames) result.addCallback(disconnect) - result.addCallback(self._detect_changes, operation_id) - result.addErrback(lambda f: self._detect_changes([], operation_id)) + result.addCallback(self._detect_changes, operation_id, force_reset) + result.addErrback(lambda f: self._detect_changes([], operation_id, + force_reset)) return result - def _detect_changes(self, locked_users, operation_id=None): + def _detect_changes(self, locked_users, operation_id=None, + force_reset=False): def update_snapshot(result): changes.snapshot() @@ -95,7 +99,7 @@ self._provider.locked_users = locked_users changes = UserChanges(self._persist, self._provider) - message = changes.create_diff() + message = changes.create_diff(force_reset=force_reset) if message: message["type"] = "users" --- a/landscape/user/changes.py +++ b/landscape/user/changes.py @@ -20,10 +20,17 @@ # that from the necessary places. self._refresh() - def _refresh(self): - """Load the previous snapshot and update current data.""" + def _refresh(self, force_reset=False): + """Load the previous snapshot and update current data. + + @param force_reset: Whether the user and group databases should be + reset. + """ self._old_users = self._persist.get("users", {}) self._old_groups = self._persist.get("groups", {}) + if force_reset: + self.clear() + self._new_users = self._create_index( "username", self._provider.get_users()) self._new_groups = self._create_index( @@ -52,13 +59,13 @@ index[data[key]] = data return index - def create_diff(self): + def create_diff(self, force_reset=False): """Returns the changes since the last snapshot. - + See landscape.message_schemas.USERS schema for a description of the dictionary returned by this method. """ - self._refresh() + self._refresh(force_reset=force_reset) changes = {} changes.update(self._detect_user_changes()) changes.update(self._detect_group_changes()) --- a/landscape/user/tests/test_changes.py +++ b/landscape/user/tests/test_changes.py @@ -105,6 +105,21 @@ changes.snapshot() self.assertEqual(changes.create_diff(), {}) + def test_create_diff_with_force_reset(self): + """ + UserChanges.create_diff accepts a force_reset parameter that can be + used to force a database reset. + """ + users = [("jdoe", "x", 1000, 1000, "JD,,,,", "/home/jdoe", "/bin/sh")] + groups = [("webdev", "x", 1000, ["jdoe"])] + provider = FakeUserProvider(users=users, groups=groups) + FakeUserInfo(provider=provider) + + changes = UserChanges(self.persist, provider) + changes.create_diff() + changes.snapshot() + self.assertEqual({}, changes.create_diff(force_reset=True)) + def test_add_user(self): """ L{UserChanges.create_diff} should report new users created debian/landscape-client.manpages0000644000000000000000000000010613404451062014105 0ustar man/landscape-client.1 man/landscape-config.1 man/landscape-message.1 debian/landscape-sysinfo.wrapper0000644000000000000000000000061513404451062014213 0ustar #!/bin/sh cores=$(grep -c ^processor /proc/cpuinfo 2>/dev/null) [ "$cores" -eq "0" ] && cores=1 threshold="${cores:-1}.0" if [ $(echo "`cut -f1 -d ' ' /proc/loadavg` < $threshold" | bc) -eq 1 ]; then echo echo -n " System information as of " /bin/date echo /usr/bin/landscape-sysinfo else echo echo " System information disabled due to load higher than $threshold" fi debian/landscape-common.manpages0000644000000000000000000000003013404451062014113 0ustar man/landscape-sysinfo.1 debian/landscape-client.docs0000644000000000000000000000002413404451062013241 0ustar README example.conf debian/landscape-client.logrotate0000644000000000000000000000114113404451062014312 0ustar /var/log/landscape/watchdog.log /var/log/landscape/monitor.log /var/log/landscape/broker.log /var/log/landscape/manager.log { weekly rotate 4 missingok notifempty compress nocreate postrotate [ -f /var/run/landscape/landscape-client.pid ] && kill -s USR1 `cat /var/run/landscape/landscape-client.pid` > /dev/null 2>&1 || : endscript } /var/log/landscape/package-changer.log /var/log/landscape/package-reporter.log /var/log/landscape/sysinfo.log /var/log/landscape/release-upgrader.log { weekly rotate 4 missingok notifempty compress nocreate } debian/landscape-common.dirs0000644000000000000000000000002213404451062013262 0ustar etc/update-motd.d debian/landscape-client.install0000644000000000000000000000046013404451062013763 0ustar usr/bin/landscape-broker usr/bin/landscape-client usr/bin/landscape-config usr/bin/landscape-manager usr/bin/landscape-message usr/bin/landscape-monitor usr/bin/landscape-package-changer usr/bin/landscape-package-reporter usr/bin/landscape-release-upgrader usr/bin/landscape-dbus-proxy usr/lib/landscape debian/landscape-common.config0000755000000000000000000000021513404451062013575 0ustar #!/bin/sh PACKAGE=landscape-common set -e . /usr/share/debconf/confmodule # Ask questions. db_input medium $PACKAGE/sysinfo || true db_go debian/rules0000755000000000000000000000556113404451062010254 0ustar #!/usr/bin/make -f dist_release := $(shell lsb_release -cs) ifneq ($(dist_release),dapper) use_pycentral = yes endif ifeq (,$(filter $(dist_release), hardy lucid)) use_dhpython2 = yes endif dh_extra_flags = -plandscape-common -plandscape-client ifeq (,$(filter $(dist_release),hardy lucid natty oneiric)) # We want landscape-client-ui only from precise onward dh_extra_flags += -plandscape-client-ui -plandscape-client-ui-install endif -include /usr/share/python/python.mk ifeq (,$(py_sitename)) py_sitename = site-packages py_libdir = /usr/lib/python$(subst python,,$(1))/site-packages py_sitename_sh = $(py_sitename) py_libdir_sh = $(py_libdir) endif package = landscape-client root_dir = debian/tmp/ revision = $(shell dpkg-parsechangelog | grep ^Version | cut -f 2 -d " "| cut -f 2 -d "-") landscape_common_substvars = debian/landscape-common.substvars landscape_client_substvars = debian/landscape-client.substvars build-arch: build build-indep: build build: build-stamp build-stamp: dh_testdir sed -i -e "s/^DEBIAN_REVISION = \"\"/DEBIAN_REVISION = \"-$(revision)\"/g" landscape/__init__.py python setup.py build make -C apt-update touch build-stamp clean: dh_testdir dh_testroot rm -f build-stamp rm -rf build make -C apt-update clean dh_clean debconf-updatepo sed -i -e "s/^DEBIAN_REVISION = .*/DEBIAN_REVISION = \"\"/g" landscape/__init__.py install: build dh_testdir dh_testroot dh_prep dh_installdirs \ etc/landscape \ var/lib/landscape \ var/log/landscape \ usr/share/landscape python setup.py install --root $(root_dir) $(py_setup_install_args) # Keep in mind that some installed files are defined in setup.py install -D -o root -g root -m 755 debian/landscape-sysinfo.wrapper $(root_dir)/usr/share/landscape/landscape-sysinfo.wrapper install -D -o root -g root -m 755 apt-update/apt-update $(root_dir)/usr/lib/landscape/apt-update binary-indep: # do nothing # binary-arch: build install dh_lintian dh_testdir dh_testroot dh_installdocs dh_installman dh_installchangelogs dh_install --sourcedir debian/tmp/ dh_installinit -- start 45 2 3 4 5 . stop 15 0 1 6 . dh_installlogrotate dh_installdebconf dh_strip dh_compress dh_fixperms dh_shlibdeps # from quantal onwards, we don't want python-gnupginterface anymore (#1045237) ifneq (,$(filter $(dist_release),lucid precise)) echo "extra:Depends=python-gnupginterface" >> $(landscape_common_substvars) endif ifeq ($(use_dhpython2),yes) dh_python2 --no-guessing-versions else ifeq ($(use_pycentral),yes) ifneq (,$(py_setup_install_args)) DH_PYCENTRAL=include-links dh_pycentral else DH_PYCENTRAL=nomove dh_pycentral endif else dh_python endif endif dh_installdeb $(dh_extra_flags) dh_gencontrol $(dh_extra_flags) dh_md5sums $(dh_extra_flags) dh_builddeb $(dh_extra_flags) binary: binary-arch binary-indep .PHONY: binary binary-arch binary-indep clean debian/landscape-client.postinst0000644000000000000000000001271113404451062014202 0ustar #!/bin/sh set -e # summary of how this script can be called: # * `configure' # * `abort-upgrade' # * `abort-remove' `in-favour' # # * `abort-remove' # * `abort-deconfigure' `in-favour' # `removing' # # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package . /usr/share/debconf/confmodule trap "db_stop || true" EXIT HUP INT QUIT TERM PACKAGE=landscape-client # Use the default installed Python. Running just "python" might run # something from /usr/local/bin, which doesn't necessarily support # running the landscape client. PYTHON=/usr/bin/python case "$1" in configure) CONFIG_FILE=/etc/landscape/client.conf if [ ! -f $CONFIG_FILE ]; then # Create new configuration, with private mode TEMPFILE=$(mktemp -p /etc/landscape) cat > $TEMPFILE </dev/null 2>&1; then dpkg-statoverride --remove $smart_update fi # Add the setuid flag to apt-update, and make it be executable by # users in the landscape group (that normally means landscape itself) apt_update=/usr/lib/landscape/apt-update if ! dpkg-statoverride --list $apt_update >/dev/null 2>&1; then dpkg-statoverride --update --add root landscape 4754 $apt_update fi # Remove old cron jobs old_cron_job=/etc/cron.hourly/landscape-client if [ -e $old_cron_job ]; then rm $old_cron_job fi very_old_cron_job=/etc/cron.hourly/smartpm-core if [ -e $very_old_cron_job ]; then rm $very_old_cron_job fi # Check if we're upgrading from a D-Bus version like the client in the # lucid archives. if ! [ -z $2 ]; then if dpkg --compare-versions $2 lt 1.5.1; then # Launch a proxy service that will forward requests over DBus # from the old package-changer to the new AMP-based broker. This # is a one-off only needed for the DBus->AMP upgrade start-stop-daemon -x /usr/bin/landscape-dbus-proxy -b -c landscape -u landscape -S fi fi # In response to bug 1508110 we need to trigger a complete update of # user information. The flag file will be removed by the client when # the update completes. DATA_PATH="`grep ^data_path /etc/landscape/client.conf | cut -d= -f2 | tr -d '[[:space:]]'`" install --owner=landscape --directory $DATA_PATH USER_UPDATE_FLAG_FILE="$DATA_PATH/user-update-flag" install --owner=landscape /dev/null $USER_UPDATE_FLAG_FILE echo "This file indicates that the Landscape client needs to send updated user information to the server." >> $USER_UPDATE_FLAG_FILE ;; abort-upgrade|abort-remove|abort-deconfigure) ;; *) echo "postinst called with unknown argument \`$1'" >&2 exit 1 ;; esac # dh_installdeb will replace this with shell code automatically # generated by other debhelper scripts. #DEBHELPER# exit 0 debian/landscape-client.prerm0000644000000000000000000000213213404451062013440 0ustar #!/bin/sh # prerm script for landscape-client # # see: dh_installdeb(1) set -e # summary of how this script can be called: # * `remove' # * `upgrade' # * `failed-upgrade' # * `remove' `in-favour' # * `deconfigure' `in-favour' # `removing' # # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package case "$1" in remove|upgrade|deconfigure) # Remove statoverride for apt-update apt_update=/usr/lib/landscape/apt-update if dpkg-statoverride --list $apt_update >/dev/null 2>&1; then dpkg-statoverride --remove $apt_update fi ;; failed-upgrade) ;; *) echo "prerm called with unknown argument \`$1'" >&2 exit 1 ;; esac # dh_installdeb will replace this with shell code automatically # generated by other debhelper scripts. #DEBHELPER# exit 0 debian/landscape-client.templates0000644000000000000000000000452113404451062014315 0ustar Template: landscape-client/computer_title Type: string _Description: Computer Title: Descriptive text to identify this computer uniquely in the Landscape user interface. Template: landscape-client/account_name Type: string _Description: Account Name: Short lowercase identifier of the Landscape account this computer will be assigned. Template: landscape-client/registration_key Type: password _Description: Registration Key: Client registration key for the given Landscape account. Only needed if the given account is requesting a client registration key. Template: landscape-client/url Type: string Default: https://landscape.canonical.com/message-system _Description: Landscape Server URL: The server URL to connect to. Template: landscape-client/exchange_interval Type: string Default: 900 _Description: Message Exchange Interval: Interval, in seconds, between normal message exchanges with the Landscape server. Template: landscape-client/urgent_exchange_interval Type: string Default: 60 _Description: Urgent Message Exchange Interval: Interval, in seconds, between urgent message exchanges with the Landscape server. Template: landscape-client/ping_url Type: string Default: http://landscape.canonical.com/ping _Description: Landscape PingServer URL: The URL to perform lightweight exchange initiation with. Template: landscape-client/ping_interval Type: string Default: 30 _Description: Ping Interval: Interval, in seconds, between client ping exchanges with the Landscape server. Template: landscape-client/http_proxy Type: string Default: _Description: HTTP proxy (blank for none): The URL of the HTTP proxy, if one is needed. Template: landscape-client/https_proxy Type: string Default: _Description: HTTPS proxy (blank for none): The URL of the HTTPS proxy, if one is needed. Template: landscape-client/tags Type: string Default: _Description: Initial tags for first registration: Comma separated list of tags which will be assigned to this computer on its first registration. Once the machine is registered, these tags can only be changed using the Landscape server. Template: landscape-client/register_system Type: boolean Default: false _Description: Register this system with the Landscape server? Register this system with a preexisting Landscape account. Please go to http://landscape.canonical.com if you need a Landscape account. debian/landscape-common.templates0000644000000000000000000000172213404451062014327 0ustar Template: landscape-common/sysinfo Type: select # Translators beware! the following three strings form a single # Choices menu. - Every one of these strings has to fit in a standard # 80 characters console, as the fancy screen setup takes up some space # try to keep below ~71 characters. # DO NOT USE commas (,) in Choices translations otherwise # this will break the choices shown to users __Choices: Do not display sysinfo on login, Cache sysinfo in /etc/motd, Run sysinfo on every login Default: Cache sysinfo in /etc/motd _Description: landscape-sysinfo configuration: Landscape includes a tool and a set of modules that can display system status, information, and statistics on login. . This information can be gathered periodically (every 10 minutes) and automatically written to /etc/motd. The data could be a few minutes out-of-date. . Or, this information can be gathered at login. The data will be more current, but might introduce a small delay at login. debian/changelog0000644000000000000000000016747013404451062011056 0ustar landscape-client (14.12-0ubuntu6.14.04.4) trusty; urgency=medium * debian/patches/nutanix-kvm.patch: Update vm_info.py to include Nutanix hypervisor. (LP: #1788219) * Fixes for release-upgrade (LP: #1699179). - debian/patches/1699179-release-upgrade-check.diff: Check if ubuntu- release-upgrader is running before apt-update. (LP: #1699179) - debian/patches/release-upgrade-success.patch: Enable landscape-client to survive trusty upgrade. (LP: #1670291) - debian/patches/post-upgrade-reboot.patch: Force reboot operation in case systemd fails. (LP: #1670291) * debian/patches/1616116-resync-loop.patch: Clear hash id database on package resync. (LP: #1616116) -- Simon Poirier Tue, 27 Nov 2018 09:24:22 -0500 landscape-client (14.12-0ubuntu6.14.04.3) trusty; urgency=medium * d/p/detect-cloudstack-kvm-1754073.patch: Detect CloudStack kvm hypervisor (LP: #1754073) -- Simon Poirier Mon, 26 Mar 2018 21:44:38 +0000 landscape-client (14.12-0ubuntu6.14.04.2) trusty; urgency=medium * d/p/set-vm-info-to-kvm-for-aws-C5-instances.patch: Sets vm_info to kvm for new AWS EC2 C5 instances. (LP: #1742531) * d/p/set-vm-info-to-kvm-for-digitalocean-instances.patch: Sets vm_info to kvm for digitalocean instances. (LP: #1743232) -- Eric Desrochers Tue, 23 Jan 2018 11:32:00 -0500 landscape-client (14.12-0ubuntu6.14.04.1) trusty; urgency=medium [ Simon Poirier ] * Add proxy handling to package reporter. (LP: #1531150) * Fix regression in configuration hook under install-cd chroot (LP: #1699789) * Report autoremovable packages (LP: #1208393) * No not re-register client by default (LP: #1618483) -- Andreas Hasenack Fri, 10 Nov 2017 16:21:54 -0200 landscape-client (14.12-0ubuntu6.14.04) trusty; urgency=medium * Minor updates (LP: #1636477): - Fixed error caused by nonexistent directory on install. - Fixed users tab issue that caused users not to be shown anymore. - Reverted the change that introduced forced user database updates in the client. - Stopped retrying package operations if a SystemError is raised. - Restore behavior of the program exiting with a non-zero exit code in case of error during registration. - Prevent backtrace during registration with inappropriate account/ password combinations. - Restore the API the charm depends on, add documentation as to its meaning and importance. - Add a specific error message when the cause of registration failure is an SSL error. - Changed the package reporter so it retries running apt-get update when an error 100 is returned. - Let the landscape-client-ui show up in Ubuntu MATE. -- Bogdana Vereha Tue, 01 Nov 2016 17:39:30 +0100 landscape-client (14.12-0ubuntu5.14.04) trusty; urgency=medium * Don't report packages that are coming from backports, so that Landscape doesn't try to upgrade to versions of packages that are in backports. (LP: #1668583) -- Bjorn Tillenius Thu, 23 Mar 2017 09:45:14 +0000 landscape-client (14.12-0ubuntu0.14.04) trusty; urgency=medium * New upstream version (LP: #1401523): - Fix regression occurring when performing Landscape-driven release upgrades (LP: #1389686) - Fix regression occurring when switching the client between different Landscape servers (LP: #1376134) - Support reporting QEMU virtualization (LP: #1374501) - Bump Juju integration message format (LP: #1369635, LP: #1362506) - Drop provisioning registration message (LP: #1344054) - Drop cloud registration message (LP: #1342646) - Fix handling broken packages (LP: #1326940) - Add new Swift usage message type (LP: #1320236) - Fix platform detection on POWER machines (LP: #1271615) - Fix platform detection for arm64 machines (LP: #1306824) - Added a mechanism to set the client's user-agent (LP: #1399139) - Fixed release-upgrader not asking for a seesion ID before attempting to send a message (LP: #1401867) * Dropped sysinfo-ignore-nonexistent-config patch, already applied upstream. -- Christopher Glass (Canonical) Mon, 15 Dec 2014 09:14:30 +0300 landscape-client (14.01-0ubuntu3) trusty; urgency=medium * landscape-sysinfo should ignore non existent or unreadable default config files. (LP: #1293990) -- Andreas Hasenack Fri, 28 Mar 2014 10:20:01 -0300 landscape-client (14.01-0ubuntu2) trusty; urgency=low * Added missing python-configobj dependency to -common (LP: #1283716) -- Andreas Hasenack Mon, 24 Feb 2014 09:43:33 -0300 landscape-client (14.01-0ubuntu1) trusty; urgency=low * New upstream release: 14.01 * Dropped unity-control-center patch, already applied upstream -- Andreas Hasenack Thu, 20 Feb 2014 12:03:20 -0300 landscape-client (13.07.3-0ubuntu2) trusty; urgency=medium * debian/patches/unity-control-center.patch: - Show in both GNOME control center and Unity control center (LP: #1257505) -- Robert Ancell Wed, 29 Jan 2014 10:44:27 +1300 landscape-client (13.07.3-0ubuntu1) saucy; urgency=low * New release: 13.07.3 - The metadata.d/ directory was renamed to annotations.d/ (LP: #1226087) - Metadata exchange mechanism now uses the less ambiguous term "annotations" instead of "metadata". (LP: #1226597) -- Christopher Glass (Canonical) Wed, 18 Sep 2013 11:42:28 +0200 landscape-client (13.07.1-0ubuntu2) saucy; urgency=low * New upstream version (LP: #1190510) - New metadata exchange mechanism allows clients to send any key-value data to the landscape server (LP: #1123932) - Network devices now report their maximum theoretical speeds, and duplex status to landscape-server (LP: #1126330, LP: #1130130) - Landscape.client is now HA aware when HA is implemented using juju charms (LP: #1122508) - The landscape client will now trigger a reboot if server sends a reboot-required message. (LP: #1133005) - Big AMP code cleanup and refactoring in order to improve testing, improve performance and ease future maintainability (LP: #1165047, LP: #1169102, LP: #1170669, LP: #1170669, LP: #1170669, LP: #1170669, LP: #1170669, LP: #1170669, LP: #1170669, LP: #1170669, LP: #1170669) - Added logic to detect cloned (virtual) computers (LP: #1161856) - The landscape-client and landscape-common packages do not use or depend on dbus code anymore, and the dependencies to python-gi and gudev are dropped. The hardware info plugin now looks at /proc instead of querying DBus (LP: #1175553, LP: #1180691) - The ceph manager plugin is now a monitor plugin and thus does not require root privileges anymore. (LP: #1186973) - The detection logic for virtual machine was changed to account for the different semantics between Openstack Folsom and Grizzly, and was expanded to detect more hypervisors (LP: #1191843) - Removed legacy upgrader code from postinst since support for it was dropped. - French translation patches were removed since the changes were merged upstream. - Removed legacy upgrader from postinst. - The /etc/dbus-1/system.d/landscape.conf file was moved from the landscape-common package to the landscape-client-ui as part of LP: #1175553, LP: #1180691. The added "Replaces:" stanza does not need an equivalent "Breaks:" since packages depend on each other with a strictly equal version number, avoiding the case described in http://www.debian.org/doc/debian-policy/footnotes.html#f53 -- Christopher Glass (Canonical) Fri, 9 Aug 2013 17:01:28 +0200 landscape-client (12.12-0ubuntu3) raring; urgency=low * Fixed sed command that updated registration_key (LP: #1131355). -- Andreas Hasenack Thu, 21 Feb 2013 16:35:46 -0300 landscape-client (12.12-0ubuntu2) raring; urgency=low * Added some fixes to the French translation file. -- Andreas Hasenack Thu, 24 Jan 2013 11:46:22 -0200 landscape-client (12.12-0ubuntu1) raring; urgency=low * New upstream release * Slightly better regexp to change registration_password to registration_key during upgrades. * Detect Microsoft Hyper-V virtualization (LP: #1087185). * Prevent Apport from generating crash reports when dpkg returns an error (LP: #1076150). -- Andreas Hasenack Mon, 10 Dec 2012 10:27:12 -0200 landscape-client (12.11-0ubuntu1) raring; urgency=low * New upstream release. * Dropped lshw-storm-1053057 patch, already applied. * New manpage: landscape-sysinfo(1) * Upstream changed a config setting from registration_password to registration_key. Adjust scripts and configs accordingly. * Install new sample config file in the doc directory. * Only depend on python-gnupginterface for releases before Quantal. * Ensure ownership of our config file (LP: #1066116). * Gearing towards debhelper 7 compatibility. Replaced dh_clean -k with dh_prep. -- Andreas Hasenack Wed, 21 Nov 2012 15:24:20 -0200 landscape-client (12.05-0ubuntu3) raring; urgency=low * Drop python-gnupginterface dependency. (LP: #1045237). -- Dmitrijs Ledkovs Fri, 09 Nov 2012 10:55:22 +0000 landscape-client (12.05-0ubuntu2) quantal-proposed; urgency=low * Added fix for lshw storm when the client was talking to an old Landscape server which was then upgraded (LP: #1053057). -- Andreas Hasenack Thu, 20 Sep 2012 15:28:37 -0700 landscape-client (12.05-0ubuntu1) quantal-proposed; urgency=low * New upstream release 12.05 (r561 in trunk) (LP: #1004678). * Make all subpackages that depend on each other require the exact same version, instead of >= $version. * Added python-gi to client depends starting natty. * Make change-packages also handle package holds (LP: #972489). * Fix typo in glade file (LP: #983096). * Tidy up apt facade (LP: #985493). * Remove SmartFacade and its tests, no longer used (LP: #996269). * Remove check for apt version, since we won't release this for Hardy where that check matters (LP: #996837). * Remove methods that were smart specific. We no longer use smart (LP: #996841). * Remove smart-update helper binary. We no longer use smart (LP: #997408). * Remove test-mixins that were useful only during the apt-to-smart code migration. Now with smart gone, they are no longer necessary (LP: #997760). * Build the packages from precise onward, not just precise. * Assorted packaging fixes: - Switched to format 3.0 (quilt). - Added source lintian overrides, with comments. - Updated debian/rules: - Added build-arch and build-indep dummy target. - Build the GUI packages from precise onwards, and not just on precise. - Re-enable dh_lintian. - Strip the binaries (dh_strip), and also call dh_shlibdeps for the automatic shlibs dependency. - Added python-gi from natty onwards. - Used __Choices instead of _Choices in landscape-common.templates, it's better for translators. - Updated standard version to 3.8.2, the version from Lucid (oldest one we support) - Added shlibs depends. - Dropped deprecated ${Source-Version} and replaced it with ${binary:Version} - Require exact version of the sibling package instead of >=. -- Andreas Hasenack Fri, 01 Jun 2012 17:48:43 -0300 landscape-client (12.04.3-0ubuntu1) precise; urgency=low * Warn on unicode entry into settings UI (LP: #956612). * Sanitise hostname field in settings UI (LP: #954507). * Make it clear that the Landscape service is commercial (LP: #965850) * Further internationalize the settings UI (LP: #962899) -- David Britton Wed, 28 Mar 2012 10:59:58 -0600 landscape-client (12.04.2-0ubuntu1) precise; urgency=low * Depend on python-aptdaemon.gtk3widgets instead of python-aptdaemon and replace dependency on python-gobject by python-gi (LP: #961894) * Add i18n to the landscape-client-ui-install script. (LP: #961891) -- David Britton Thu, 22 Mar 2012 10:10:39 -0600 landscape-client (12.04.1-0ubuntu1) precise; urgency=low * Fix default landscape hostname in glib schema. * dpkg test improvements to fix intermittent failures. * If ssl_public_key is supplied, use it also when fetching script attachments. This fixes the case of using script execution with attachments when the Landscape server is using a custom CA, most common in LDS deployments. (LP: #959846) * Make sure we have a PATH variable set before doing package activities, and also set it in the initscript for good measure. If the client was configured and restarted by the new UI configuration tool, PATH wasn't set, triggering an error in dpkg. (LP: #961190) * Make landscape-client-ui depend on landscape-client-ui-install, so that we get an entry in the system settings if just landscape-client-ui is installed. The actual entry comes from landscape-client-ui-install. * Optimization: when adding binaries, don't reload every repo, only the one containing the binaries. (LP: #954822) * Handle the case where the user clicks twice inadvertently on the Landscape icon in system settings and don't start a second copy of itself. (LP: #960211) -- Andreas Hasenack Wed, 21 Mar 2012 16:15:49 -0300 landscape-client (12.04-0ubuntu1) precise; urgency=low * d/control: Dropping python-central and python-support since they are not used except in backports, and are not in main. -- Clint Byrum Mon, 19 Mar 2012 16:05:49 -0700 landscape-client (12.04-0ubuntu0.12.04.0) precise; urgency=low * Change package management features to use APT instead of Smart (LP: #856244, #861707, #859615, #861345, #863239, #863259, #865270, #865272, #865285, #865273, #871641, #865299, #873196, #873939, #876493, #881973, #882438, #866014, #881998, #884142, #884151, #884131, #887037, #886208, #887578, #887947, #889067, #889069, #889087, #889099, #865303, #889113, #890605, #890606, #890609, #897416, #891855, #898681, #898683, #897656, #898542, #862212, #903202, #914734, #914735, #914737, #916301, #915280, #914742, #918925, #918175, #919179, #921664, #921699, #922582, #922511, #921712, #928750, #932136, #928941, #937411, #937567, #925543, #947803, #952973, #948142, #953136, #953906, #956590). * Add a GTK interface to configure the client (LP: #911279, #911666, #912163, #911665, #916300, #931937, #931937, #943622, #945025, #911279, #944652, #948464, #948416, #949158, #911671, #950864, #949208, #949147, #953070, #953292, #953463, #953034, #949200, #953026, #954499, #954516, #954285, #953065, #954414, #954332, #954542, #955966, #955139, #956030, #956119). * Add the ability to auto discover the server location on local deployment (LP: #917422, #927620, #917422, #928585, #929087, #932325, #948564) * Allow the client to accept arbitrary environment variables from the server for script execution (LP: #954999). * Make landscape-config exit non-zero when registration fails and --ok-no-register is not passed (LP: #271759). * Check for the content of /sys/bus/xen/devices to report a machine as a Xen VM instead of just relying on the existence of /sys/bus/xen (LP: #921970). * Make sure cloud registration succeeds if there is no kernel specified in the meta-data service (LP: #920453). * Report private and public IP adresses from the metadata service at cloud registration time (LP: #918366). * Add support for reporting hardware information using lshw (LP: #899002, #943975, #955734). * Add support for the new attachment service in script execution (LP: #893040). * Adds a new message type, 'register-provisioned-machine', which is meant to register computers using an OTP (LP: #881405). * Add local cloning option for load testing (LP: #872830, #925924). * Add more variables to preseeding (LP: #863204, #867710). * Allow the configuration of the ping interval (LP: #397884). * Add fake package reporters for load testing purposes (LP: #821571, #821570). * Report a package reporter error to the server if no APT sources are configured, to trigger a package reporter alert (LP: #823769). -- Andreas Hasenack Mon, 19 Mar 2012 09:33:34 -0300 landscape-client (11.07.1.1-0ubuntu2) precise; urgency=low * Remove build dependency on python-central | python-support for demotion. -- Matthias Klose Sat, 17 Dec 2011 17:25:00 +0100 landscape-client (11.07.1.1-0ubuntu1.11.10.0) oneiric; urgency=low * Included missing files (LP: #814223). -- Andreas Hasenack Thu, 21 Jul 2011 15:40:46 -0300 landscape-client (11.07.1.1-0ubuntu0.11.10.0) oneiric; urgency=low * Try to load the old persist file if the current one doesn't exist or is empty (LP: #809210). * Fallback to gethostname to get something interesting out of get_fqdn. * Fix wrong ownership and permissions when the reporter is run as a result of applying a repository profile (LP: #804008). * Keep original sources.list ownership (LP: #804548). * Refactored tests (LP: #805746). * Preserve permissions of sources.list (LP: #804548). * Added a broker command line option (--record) that saves exchanges with the server to the filesystem * Detect if running in a vmware guest (LP: #795794). * Report VM type when run in the cloud (LP: #797069). * Report VM type in non-cloud registration (LP: #795752). * Report the package reporter result even in case of success, not just in case of failure (LP: #780406). * Report package reporter errors (LP: #732490). * Fix dependencies for hardy removing references to python 2.4 packages for pycurl and dbus (LP: #759764). * The landscape client now reports whether it is running on a virtual machine or not. * Add a plugin which manages APT sources.list and the associated GPG keys (LP: #758928). * Limit the number of items in a network message to 200, to prevent problems when communication is interrupted with the server and the client accumulates too many network items, thus overloading the server when it's available again (LP: #760486). * Updated version number in __init__.py so that the client reports the correct one in its user-agent string. -- Andreas Hasenack Mon, 18 Jul 2011 15:16:18 -0300 landscape-client (11.02-0ubuntu0.11.04.1) natty; urgency=low * debian/control, debian/rules: Add quilt * debian/patches/fix-landscape-monitor.patch: Fix landscape monitoring with gir1.0-gudev-1.0 installed. (LP: #747498) -- Chuck Short Fri, 08 Apr 2011 09:46:24 -0400 landscape-client (11.02-0ubuntu0.11.04.0) natty; urgency=low * New upstream version (LP: #727324) - Exit gracefully instead of crashing when the filesystem is read-only (LP: #649997). - Drop hal requirement (LP: #708502). - Enable HTTP compression in Curl (LP: #297623). - Explicitly name log files that need to be rotated (LP: #634236). - Assorted test suite fixes -- Andreas Hasenack Tue, 01 Mar 2011 15:38:11 -0300 landscape-client (11.01-0ubuntu0.11.04.0) natty; urgency=low * New upstream version (LP: #702928) - Use a better load check for the sysinfo wrapper, taking into account the number of cores (LP: #643565). - Add an option to bootstrap cloud instances using cloud-init (LP: #701972). - Fix packaging for Natty (LP: #688115). - Force deletion of all the persist data for the monitoring plugins at resynchronization, instead of relying each one of them to do (LP: #688161). - Don't send the mount-activity message to the server anymore (LP: #688514). - Workaround a new behavior in NetworkManager where getfqdn would report localhost instead of useful hostname (LP: #649142). -- Thomas Hervé Fri, 14 Jan 2011 10:11:04 -0600 landscape-client (1.5.5.1-0ubuntu0.10.10.0) maverick; urgency=low * The client network plugin would send erroneous data if a network interface was removed (and its kernel module removed as well) and then readded (LP: #641264). -- Andreas Hasenack Mon, 20 Sep 2010 13:52:49 -0300 landscape-client (1.5.5-0ubuntu0.10.10.0) maverick; urgency=low * New upstream version (LP: #633468) - The --help command line option can now be used without being root (LP: #613256). - The client Unix sockets and symlinks are now cleaned up at shutdown. Without this cleaning, the client could refuse to start because of a PID collision (LP: #607747). - The network traffic plugin didn't use to take into account integer overflows. This would cause the plugin to send negative values sometimes (LP: #615371). - If a payload had many user activities in it, only the last one would be carried out (LP: #617624). - The Eucalyptus plugin was not enabled by default, which means the Cloud Topology feature of Landscape was not available (LP: #614493). -- Andreas Hasenack Wed, 08 Sep 2010 15:34:09 -0400 landscape-client (1.5.4-0ubuntu0.10.10.0) maverick; urgency=low * New upstream version (LP: #610744): - The Eucalyptus management plugin reports the output of the 'euca-describe-availability-zones verbose' command, which includes information about the available instance types and the maximum number of each instance type that the cloud can support (LP: #599338) - Check if the package directory exists before trying to check the package changer lock in the dbus-proxy. This fixes a bug when upgrading a dbus-landscape which never registered (LP: #603514). - Allow an LDS server to bootstrap new cloud instances with its own CA, which is picked up by the client, written to a file on the instance, and used in subsequent exchanges with the server (LP: #605079). - Skip loopback interface when reporting device info (LP: #608314) - Disable landscape-sysinfo when load is more than 1 (LP: #608278) -- Free Ekanayaka Wed, 28 Jul 2010 08:14:02 +0200 landscape-client (1.5.2.1-0ubuntu0.10.10.0) maverick; urgency=low * Include maverick in debian/rules substvars (LP: #596062) * Filter duplicate network interfaces in get_active_interfaces (LP: #597000) -- Free Ekanayaka Mon, 28 Jun 2010 18:07:18 +0200 landscape-client (1.5.2-0ubuntu0.10.10.0) maverick; urgency=low * New upstream version (LP: #594594): - A new includes information about active network devices and their IP address in sysinfo output (LP: #272344). - A new plugin collects information about network traffic (#LP :284662). - Report information about which packages requested a reboot (LP: #538253). - Fix breakage on Lucid AMIs having no ramdisk (LP: #574810). - Migrate the inter-process communication system from DBus to Twisted AMP. -- Free Ekanayaka Wed, 16 Jun 2010 12:03:50 +0200 landscape-client (1.5.0-0ubuntu0.10.04.1) lucid; urgency=low * New upstream version - Fix smart-update failing its very first run (LP: 562496) - Depend on pythonX.Y-dbus and pythonX.Y-pycurl (LP: #563063) -- Free Ekanayaka Wed, 21 Apr 2010 12:31:28 +0200 landscape-client (1.5.0-0ubuntu0.10.04.0) lucid; urgency=low * New upstream version (LP: #557244) - Fix package-changer running before smart-update has completed (LP: #542215) - Report the version of Eucalyptus used to generate topology data (LP: #554007) - Enable the Eucalyptus plugin by default, if supported (LP: #546531) - Use a whitelist of allowed filesystem types to instead of a blacklist (LP: #351927) - Report the update-manager logs to the server (LP: #503384) - Turn off Curl's DNS caching for requests. (LP: #522688) -- Free Ekanayaka Wed, 07 Apr 2010 16:27:45 +0200 landscape-client (1.4.4-0ubuntu0.10.04.0) lucid; urgency=low * New upstream release (LP: #519200): - Add a message for creating package locks (LP: #514334) - Add support for auto-approved change-packages messages (LP: #517175) - Add support for installing server-generated debian packages (LP: #509752) - Add support for reporting Eucalyptus topology information (LP: #518501) - Fix timeout while inserting large free-space message (LP: #218388) - Fix wrong log path in motd (LP: #517454) - Fix race condition in process excecution (LP: #517453) -- Free Ekanayaka Sat, 16 Jan 2010 14:11:32 +0100 landscape-client (1.4.0-0ubuntu0.9.10.0) lucid; urgency=low * New upstream release with several bug fixes: - Fix landscape daemons fail to start when too many groups are available (LP: #456124) - Fix landscape programs wake up far too much. (LP: #340843) - Fix Package manager fails with 'no such table: task' (LP #465846) - Fix test suite leaving temporary files around (LP #476418) * Add support for Ubuntu release upgrades: - Add helper function to fetch many files at once (LP: #450629) - Handle release-upgrade messages in the packagemanager plugin (LP: #455217) - Add a release-upgrader task handler (LP: #462543) - Support upgrade-tool environment variables (LP: #463321) * Add initial support for Smart package locking: - Detect and report changes about Smart package locks (#488108) -- Free Ekanayaka Tue, 01 Dec 2009 09:16:26 +0100 landscape-client (1.3.2.4-0ubuntu0.9.10.0) karmic; urgency=low * New upstream release: - Catch import errors in the landscape-sysinfo script to prevent errors when landscape-sysinfo is run to update the motd during upgrade (LP: #349996) - Fix a long-standing bug in the client which causes resynchronisations on the server (LP: #144475) - When downloading hash-id stores, pass possible custom SSL certificates to the fetch fuction (LP: #435887) - Handle unicode username in custom graphs, and also report missing user to the server properly (LP: #406388) - Handle a SQlite bug when creating package store database, by making an extra query to flush the table cache (LP: #416629) -- Free Ekanayaka Fri, 09 Oct 2009 18:21:24 +0200 landscape-client (1.3.2.3-0ubuntu0.9.10.0) karmic; urgency=low * New upstream release: - Don't clear the hash_id_requests table upon resynchronize (LP #417122) -- Free Ekanayaka Wed, 26 Aug 2009 15:16:59 +0200 landscape-client (1.3.2.2-0ubuntu0.9.10.3) karmic; urgency=low * Drop unsed dependency on libcurl3 and explicitly depend on libcurl3-gnutls (>= 7.15.1-1ubuntu3) on dapper (LP: #406885) * Add missing debian/landscape-common.config (LP: #410378) -- Free Ekanayaka Thu, 30 Jul 2009 15:16:07 +0200 landscape-client (1.3.2.2-0ubuntu0.9.10.2) karmic; urgency=low * debian/control: dropping python-pysqlite2 dependency, since this package is in universe, and python now has built-in sqlite support, LP: #406641 -- Dustin Kirkland Wed, 29 Jul 2009 17:33:47 -0500 landscape-client (1.3.2.2-0ubuntu0.9.10.1) karmic; urgency=low [ Free Ekanayaka ] * New upstream release: - Include the README file in landscape-client (LP: #396260) - Fix client capturing stderr from run_command when constructing hash-id-databases url (LP: #397480) - Use substvars to conditionally depend on update-motd or libpam-modules (LP: #393454) - Fix reporting wrong version to the server (LP: #391225) - The init script does not wait for the network to be available before checking for EC2 user data (LP: #383336) - When the broker is restarted by the watchdog, the state of the client is inconsistent (LP: #380633) - Package stays unknown forever in the client with hash-id-databases support (LP: #381356) - Standard error not captured when calling smart-update (LP: #387441) - Changer calls reporter without switching groups, just user (LP: #388092) - Run smart update in the package-reporter instead of having a cronjob (LP: #362355) - Package changer does not inherit proxy settings (LP: #381241) - The ./test script doesn't work in landscape-client (LP: #381613) - The source package should build on all supported releases (LP: #385098) - Strip smart update's output (LP: #387331) - The fetch() timeout isn't based on activity (#389224) - Client can use a UUID of "None" when fetching the hash-id-database (LP: #381291) - Registration should use the fqdn rather than just the hostname (LP: #385730) -- Mathias Gug Wed, 22 Jul 2009 14:54:50 -0400 landscape-client (1.0.29.1-0ubuntu0.9.04.1) karmic; urgency=low * debian/control: depend on libpam-modules, rather than update-motd -- Dustin Kirkland Thu, 16 Jul 2009 11:36:27 -0500 landscape-client (1.0.29.1-0ubuntu0.9.04.0) jaunty; urgency=low * Apply a fix for segfault bug involving curl timeouts. (LP: #360510) -- Christopher Armstrong Mon, 13 Apr 2009 14:33:31 -0400 landscape-client (1.0.29-0ubuntu0.9.04.0) jaunty; urgency=low * New upstream bugfix release (LP: #358744) - Add a timeout to HTTP operations to avoid hanging (LP: #349737) - Clean up environment variables on startup to avoid propagating variables that will corrupt package installation (LP: #348681) - Clean up FDs on startup for the same reason (LP: #352458) - Catch and handle certain errors from smart (such as invalid package data) to avoid "stuck" Landscape activities (LP: #268745) - Don't print warnings meant for developers to the console (LP: #336669) -- Christopher Armstrong Thu, 09 Apr 2009 17:09:50 -0400 landscape-client (1.0.28-0ubuntu1.9.04.0) jaunty; urgency=low * Fix minor packaging issues in last release (LP: #343954) - Version number in landscape.VERSION is now correct - Fixed package version number to maintain convention * The following changes are in the 1.0.28 release: - Invalidate package cache when server UUID changes (LP: #339948) - Improve the "cloud mode" introduced in 1.0.26 to send more disambiguation data (LP: #343942) and allow the EC2 user data to specify the exchange and ping URLs (LP: #343947) - Allow importing of initial configurations (along with public SSL certificates) when running landscape-config (LP: #341705) - Support a non-root mode which allows running the client without the management functionality (LP: #82159) - Automatic cloud registration when there's no user-data to specify an OTP now works (LP: #344323) -- Christopher Armstrong Thu, 19 Mar 2009 09:52:03 -0400 landscape-client (1.0.28-0ubuntu1) jaunty; urgency=low * New upstream release. (LP: #343954) -- Martin Pitt Wed, 18 Mar 2009 20:42:05 +0100 landscape-client (1.0.26.1-0ubuntu0.9.04) jaunty; urgency=low * Build for python2.6, include the symlinks in the package. -- Matthias Klose Wed, 25 Feb 2009 12:03:23 +0000 landscape-client (1.0.26-0ubuntu0.9.04) jaunty; urgency=low * New upstream release (LP: #328151) -- Christopher Armstrong Wed, 11 Feb 2009 17:00:54 +0000 landscape-client (1.0.25-0ubuntu0.9.04) jaunty; urgency=low * New upstream release supporting custom graphs (LP: #306360) - Multiple custom graphs can be used at the same time (LP: #307314) - PATH is now set for scripts in script execution (LP: #257018) * debian/landscape-common.postinst: Only chown parts of /var/lib/landscape because we now store files in it that should maintain their ownership (LP: #307321). * debian/landscape-client.postinst: Work around chfn/system user problem by not specifying a --gecos (LP: #238755) * debian/landscape-client.logrotate: logrotate no longer reports spurious errors when the client isn't running (LP: #271767) -- Christopher Armstrong Thu, 11 Dec 2008 17:11:08 -0800 landscape-client (1.0.23-0ubuntu0.8.10.1) intrepid; urgency=low * debian/control: Update Replaces to < 1.0.23-0ubuntu0.8.10 to correctly replace newer unsplit versions of the landscape package (LP: #285030). -- Christopher Armstrong Fri, 17 Oct 2008 12:42:23 -0400 landscape-client (1.0.23-0ubuntu0.8.10) intrepid; urgency=low * New upstream release. (LP: #277658): Changes since 1.0.21.1: - Don't print duplicate warnings when / is nearing capacity in sysinfo (LP: #260230). - Slight change to link text in landscape-sysinfo. - Don't crash badly when programs are run as the incorrect user (LP: #268879). * debian/changelog: New debian-version scheme including Ubuntu version. The same upstream version is available for all supported releases. (LP: #277682). * debian/landscape-client.postrm: Delete log and data files upon purge (LP: #121182). * debian/landscape-common.postrm: Delete the sysinfo logs upon purge. -- Christopher Armstrong Thu, 09 Oct 2008 11:40:51 -0400 landscape-client (1.0.21.1-0ubuntu2) intrepid; urgency=low * debian/control: fix bzr url * debian/landscape-sysinfo.wrapper: print a timestamp before the sysinfo data to ensure appropriate context (LP: #270862) -- Dustin Kirkland Tue, 30 Sep 2008 17:13:18 -0500 landscape-client (1.0.21.1-0ubuntu1) intrepid; urgency=low * New upstream version: * Add ok-no-register option to landscape-config script to not fail if dbus isn't started or landscape-client isn't running. * lower timeout related to package management in landscape. * debian/control: Depend on cron. * debian/landscape-client.postinst: use ok-no-register option so that the postinst script doesn't fail when run from the installer. (LP: #274573). -- Mathias Gug Thu, 25 Sep 2008 17:54:00 -0400 landscape-client (1.0.21-0ubuntu2) intrepid; urgency=low * debian/rules: Install an hourly cron job to update smart package data. Thanks to Christopher Armstrong (LP: #268765). * debian/control: - Move ${misc:Depends} to Depends. - Add VCS-* headers. * debian/landscape-client.postrm: - remove /etc/default/landscape-client when the package is purged. -- Mathias Gug Tue, 23 Sep 2008 18:19:26 -0400 landscape-client (1.0.21-0ubuntu1) intrepid; urgency=low [ Christopher Armstrong ] * New upstream release (LP: #271886): - Bug fix release: - Avoid the PotentialZombieWarning on landscape-client startup. (LP: #257346) - When run as root, read sysinfo configuration from /etc and and write logs to /var/log instead of /root. (LP: #268560) - Avoid ZeroDivisionErrors when /home is an autofs. (LP: #269634) - Don't corrupt a pid file when trying to start the client when it's already running. (LP: #269634) - Remove the pid file when shutting down the client. (LP: #257081) [ Mathias Gug ] * debian/landscape-client.init: specify the pid file and use --startas instead of --exec when starting landscape-client so that the init script doesn't fail if landscape-client is already running. -- Mathias Gug Fri, 19 Sep 2008 17:28:08 -0400 landscape-client (1.0.18-0ubuntu4) intrepid; urgency=low [ Christopher Armstrong ] * debian/landscape-common.postinst: Don't blow up when the landscape-sysinfo symlinks already exist (LP: #270131) [ Mathias Gug ] * debian/landscape-common.postinst, debian/landscape-common.prerm: don't call update-motd init script as it's no longer available in the update-motd package. Call directly /usr/sbin/update-motd instead. (LP: #271854) -- Mathias Gug Thu, 18 Sep 2008 16:47:08 -0400 landscape-client (1.0.18-0ubuntu3) intrepid; urgency=low * debian/control: Add Replaces for landscape-common since landscape-sysinfo has been moved from -client to -common. -- Mathias Gug Tue, 16 Sep 2008 17:16:50 -0400 landscape-client (1.0.18-0ubuntu2) intrepid; urgency=low [ Mathias Gug ] * Split the package into two packages: - landscape-common: has the python libraries and the landscape-sysinfo command. A landscape account is not required to use this package. - landscape-client: has all the binaries required to run the landscape-client. Requires a landscape account. - debian/control: + move some dependencies to landscape-client so that landscape-common doesn't install unecessary packages in the default -server install. + move python-gobject to a Pre-Depends so that landscape-config can register the system during the postinst (LP: #268838). * debian/control: - depend on python-smartpm instead of smartpm-core. * debian/landscape-client.postrm: delete /etc/landscape/client.conf when the package is purged. * debian/landscape-client.postinst: remove sysinfo_in_motd debconf question as it wasn't used. [ Christopher Armstrong ] * Fixes for (LP: #268352). - scripts/landscape-sysinfo.wrapper: New script to run landscape-sysinfo with leading whitespace. - debian/rules: Install wrapper into /usr/share/landscape. - debian/landscape-client.postinst: Link wrapper into place. -- Mathias Gug Mon, 15 Sep 2008 17:21:53 -0400 landscape-client (1.0.18-0ubuntu1) intrepid; urgency=low * New upstream release -- Rick Clark Mon, 08 Sep 2008 16:35:57 -0500 landscape-client (1.0.17-0ubuntu1) intrepid; urgency=low [ Dustin Kirkland and Rick Clark ] * debian/compat: updated to 6. * debian/config: initial debconf config. * debian/control: reformatted depends and changed smartpm-core version; changed Standards-Version to 3.8.0; added adduser to depends as needed by postinst and postrm; updates smartpm-core dep to >1.0; moved most build deps to Build-Depends-Indep; add po-debconf build dependency; added debconf hooks for installing landscape-sysinfo as an update-motd or profile.d script. * debian/copyright: updated copyright date; removed incorrect symlink to GPL-3; added Dustin and Rick to the packaging credits. * debian/landscape-client.init: removed S from stop; rewritten to be more lsb/Debian compliant; added status action; fixed whitespacing inconsistency; use $NAME whenever possible; comment why we're not using status_of_proc(). * debian/landscape-client.lintian-overrides: added with two overrides * debian/landscape-client.overrides: removed. * debian/landscape-client.prerm: initial creation, remove landscape-sysinfo update-motd or profile.d script, if any exists. * debian/landscape-client.postinst: fixed deprecated chmod call; changed home dir of landscape user; removed uneeded update-rc.d; added code to handle debconf; created a function for retrieving values from ini config; /usr/share/debconf/comfmodule to top of postinst; add db_stop back in. * debian/landscape-client.postrm: added debhelper tag. * debian/rules: added po debconf bits for translation; depend on update-motd. * debian/pycompat: initial creation. * debian/po/POTFILES.in, debian/po/templates.pot: initial creation. * man/txt2man, man/*.txt: removed; manpages should *really* be in native format. * man/landscape-client.1, man/landscape-config.1, man/landscape-message.1: manpages in roff format, initially created by txt2man, some minor tweaks to the title and such. [ Gustavo Niemeyer ] * New Upstream release * Fix copyright dates in debian/copyright * fix the chown call to use a colon * Fixed missing $PACKAGES variable in postinst * Implemented support for configuration the computer title via debconf [ Kees Cook ] * removed unused debian/autoppa-switch. * fixed debian/landscape-client.init to run log_end_msg in the right place. * debian/landscape-client.postrm: rewrote to allow debhelper to do its job, fixed deluser call. * debian/landscape-client.postinst: rewrote to allow debhelper to do its job, fixed use of adduser. * debian/rules: use "install" instead of "cp", dropped lintian overrides. * debian/changelog: drop redundant changelog entries. [ Rick Clark ] * removed .override file * removed .autoppa files -- Dustin Kirkland Thu, 04 Sep 2008 13:28:14 -0500 landscape-client (1.0.16-intrepid1-landscape1) intrepid; urgency=low * landscape-config can now be run in a non-interactive mode -- Andreas Hasenack Fri, 8 Aug 2008 14:34:43 +0000 landscape-client (1.0.15-intrepid1-landscape1) intrepid; urgency=low * introduce a basic landscape-sysinfo tool * properly report a script execution error if its exit status is not zero -- Andreas Hasenack Thu, 7 Aug 2008 18:33:41 +0000 landscape-client (1.0.14-intrepid1-landscape1) intrepid; urgency=low * fixed some dbus errors in some of the supported distributions * added restart/shutdown plugin * scripts can now have accented characters in them -- Andreas Hasenack Thu, 24 Jul 2008 13:06:34 +0000 landscape-client (1.0.13-hardy1-landscape1) hardy; urgency=low * the timestamp in the error message from processkiller.py is now formatted properly * the process list sent to the server was only being updated for new processes, and not when existing processes changed * in script execution, a timeout error is now reported with a proper error message instead of just a generic failure in the script -- Andreas Hasenack Thu, 10 Jul 2008 14:24:15 +0000 landscape-client (1.0.12-hardy1-landscape1) hardy; urgency=low * the sleep average value of a process is no longer provided by recent kernels. The client now computes and sends the CPU usage instead. * initial directory for script execution is now the target user's home directory and, if that is unavailable, /. Previously it was /root. * new user creation was ignoring some extra fields such as Location and phone numbers * the machine uptime was incorrectly reported after a logrotate run and if there was no reboot afterwards. This affected the process listing page in the web interface, where processes would show up as having been started around 1970. -- Andreas Hasenack Thu, 26 Jun 2008 16:17:59 +0000 landscape-client (1.0.11-hardy1-landscape1) hardy; urgency=low * fixed a regression where an important fix was missing from the new staging branch -- Andreas Hasenack Fri, 13 Jun 2008 14:45:08 +0000 landscape-client (1.0.10-hardy1-landscape1) hardy; urgency=low * Change the utilisation of the csv module to be compatible with python 2.4 as used in Dapper. -- Andreas Hasenack Thu, 12 Jun 2008 17:58:05 +0000 landscape-client (1.0.9-hardy1-landscape1) hardy; urgency=low * restrict users and groups list to local ones only, i.e., /etc/passwd and /etc/group * fixed some tests that were failing in locales other than english -- Andreas Hasenack Wed, 11 Jun 2008 15:16:12 +0000 landscape-client (1.0.4-hardy1-landscape1) hardy; urgency=low * fixed permissions and ownership of some files and directories which could prevent package data from being uploaded to the Landscape server * fixed a problem where the root user password could not be changed * the watchdog.log log file is now also rotated -- Andreas Hasenack Wed, 16 Apr 2008 17:21:20 +0000 landscape-client (1.0-hardy1-landscape4) hardy; urgency=low * Memory consumption has is improved. The client's functionality is now broken up into a number of sub-processes. Functionality that would previously use or leak memory, such as package management, run in independent processes that exit when they complete so the OS can reclaim the memory. * Security is improved with this design. Monitoring plugins run as the unpriviledged landscape user while management functions run as root. * Secure inter-process communication occurs over DBUS. -- Michel Pelletier Wed, 27 Feb 2008 22:57:21 +0000 landscape-client (0.17.0-hardy1-landscape1) hardy; urgency=low * The client now reports the primary group for users. I can also modify it when requested by the server (#122212). -- Jamshed Kakar Wed, 21 Nov 2007 19:14:29 +0000 landscape-client (0.16.0-hardy1-landscape1) hardy; urgency=low * The client no longer schedules an urgent exchange if it detects that the server has not processed any messages in its most recent exchange (#138135). * The total number of pending messages are sent to the server to make it possible to generate backlog statistics (#162733). -- Jamshed Kakar Wed, 14 Nov 2007 23:09:58 +0000 landscape-client (0.15.0-gutsy1-landscape1) gutsy; urgency=low * Support for http_proxy and https_proxy variables in the configuration file and in the interactive setup, as well as --http-proxy and --https-proxy parameters is available (#151690). * The client gracefully fails when unknown request-ids for package-related operations are received (#151085). * Child devices that have been removed are not reported when one of their ancestors is also reported for deletion by the hardware inventory plugin (#136497). -- Jamshed Kakar Fri, 26 Oct 2007 23:23:20 +0000 landscape-client (0.14.0-gutsy1-landscape1) gutsy; urgency=low * Failures that would occur is a user wasn't in the shadow file are fixed (#102071). * Whitespace surrounding process command-line names is removed before they are reported to the server (#149112). * Test failures, some related to DBUS on gutsy, are fixed. -- Jamshed Kakar Thu, 25 Oct 2007 00:03:06 +0000 landscape-client (0.13.0-gutsy1-landscape1) gutsy; urgency=low * When unknown package IDs are sent by the server message handling is postponed until the client is aware of the unknown IDs (#128796). -- Jamshed Kakar Wed, 17 Oct 2007 17:36:11 +0000 landscape-client (0.12.2-gutsy1-landscape1) gutsy; urgency=low * The client now reports it's version correctly. -- Jamshed Kakar Thu, 11 Oct 2007 21:32:24 +0000 landscape-client (0.12.1-gutsy1-landscape1) gutsy; urgency=low * A critical bug that would break configure-landscape is fixed. -- Jamshed Kakar Tue, 9 Oct 2007 21:10:02 +0000 landscape-client (0.12.0-gutsy1-landscape7) gutsy; urgency=low * Fixed typo in version string. -- Jamshed Kakar Sat, 6 Oct 2007 00:49:10 +0000 landscape-client (0.12.0-gutsy1-landscape6) gutsy; urgency=low * Updated Depends to require new smartpm-core 0.52 packages. -- Jamshed Kakar Fri, 5 Oct 2007 23:23:17 +0000 landscape-client (0.12.0-gutsy1-landscape5) gutsy; urgency=low * No upstream changes. New package build. -- Jamshed Kakar Thu, 4 Oct 2007 22:35:50 +0000 landscape-client (0.12.0-gutsy1-landscape4) gutsy; urgency=low * Updated package requires current version of smartpm-core. -- Jamshed Kakar Tue, 2 Oct 2007 21:37:56 +0000 landscape-client (0.11.0-gutsy1-landscape1) gutsy; urgency=low * The full command name for a process, where retrievable, is reported to the server instead of just the first 16 characters (#131878). * The historic process plugin, unused for months, has been removed (#134122). * A new landscape-message command-line program is available. It allows administrators to send messages from a console that end up in the account history (#125083). * The landscape-client.log file is now logrotated regularly (#78494). -- Jamshed Kakar Thu, 13 Sep 2007 00:05:43 +0000 landscape-client (0.10.15-gutsy1-landscape1) gutsy; urgency=low * THIS IS A TEST BUILD! * The full command name, where retrievable, is reported to the server instead of just the first 16 characters (#131878). * The historic process plugin, unused for months, has been removed (#134122). * A new landscape-message command-line program is available. It allows administrators to send messages from a console that end up in the account history (#125083). * The landscape-client.log file is now logrotated regularly (#78494). -- Jamshed Kakar Wed, 12 Sep 2007 22:48:19 +0000 landscape-client (0.10.12-1ubuntu1) feisty; urgency=low * New upstream release. * Package Depends updated to use new smartpm-0.51-landscape4 package. This makes the package installable on Ubuntu systems with only the main repository enabled. -- Landscape Team Tue, 7 Aug 2007 16:08:00 -0700 landscape-client (0.10.11-1ubuntu1) feisty; urgency=low * New upstream release. * A bug related to package dependency reporting that would cause two approvals for and upgrade package request has been fixed. * Errors with the ping server are logged clearly now. * A crashing postinit-related bug is fixed. -- Landscape Team Fri, 3 Aug 2007 19:15:00 -0700 landscape-client (0.10.10-1ubuntu1) feisty; urgency=low * New upstream release. * CA certificates are no longer hard-coded. The default set of CAs in the system are used instead. * The client connects to the server immediately when it detects changes in packages and users. This dramatically reduces the turn-around time of some operations. * The new user and group change detection reports detailed information to the server about changes made external to the client, such as by local users. * The client reports detailed information about the outcome of user and group operations. * The client has support for a new synchronization system. If either the server or client detect inconsistencies in the data they have they can request a synchronization to get back in sync. * The client has support to calculate and report package upgrades. * The hostname is reported when clients first register with the server. * A new configure-landscape script eases registration including automated and interactive registration modes. * The client includes VM size and sleep average in active process data. -- Landscape Team Thu, 28 Jun 2007 16:19:00 -0700 landscape-client (0.10.9-1ubuntu1) feisty; urgency=low * New upstream release. * Package depends on python-gdbm to install correctly on systems without it. -- Landscape Team Fri, 8 Jun 2007 11:02:00 -0700 landscape-client (0.10.8-1ubuntu1) feisty; urgency=low * New upstream release. * The changes that included the new configure-landscape helper script have been removed. They were not quite ready for release. * Added python-twisted-web to Debian package Depends to satisfy dependencies introduced by the new ping plugin. -- Landscape Team Thu, 7 Jun 2007 11:03:00 -0700 landscape-client (0.10.7-1ubuntu1) feisty; urgency=low * New upstream release. * Package depends on new smartpm-core package which installs correctly on dapper. -- Landscape Team Wed, 6 Jun 2007 10:37:00 -0700 landscape-client (0.10.6-1ubuntu1) feisty; urgency=low * New upstream release. * Mispelled smartpm-core version is fixed. -- Landscape Team Mon, 1 Jun 2007 10:48:00 -0700 landscape-client (0.10.5-1ubuntu1) feisty; urgency=low * New upstream release. * Mismatched operation status codes are fixed. -- Landscape Team Thu, 31 May 2007 15:36:00 -0700 landscape-client (0.10.4-1ubuntu1) feisty; urgency=low * New upstream release. * Links to a new version of smart with a fixed crontab entry. -- Landscape Team Tue, 22 May 2007 23:29:00 -0700 landscape-client (0.10.3-1ubuntu1) feisty; urgency=low * New upstream release. * A bug related to bpickle conversions with float-point values is fixed (#114829). -- Landscape Team Mon, 15 May 2007 16:31:00 -0700 landscape-client (0.10.2-1ubuntu1) feisty; urgency=low * New upstream release. * New package includes previously missing package management plugin. -- Landscape Team Fri, 11 May 2007 10:20:00 -0700 landscape-client (0.10.1-1ubuntu1) feisty; urgency=low * New upstream release. * Minor fix in package management plugin timings. -- Landscape Team Thu, 10 May 2007 10:00:00 -0700 landscape-client (0.10.0-1ubuntu1) feisty; urgency=low * New upstream release. * Basic package management operations are now supported. The client reports package information to the server and can install and/or remove packages. * The client uses the new operations system. Support for the now unused action info system is gone. * A minor bpickle bug was fixed. -- Jamshed Kakar Mon, 7 May 2007 20:16:00 -0700 landscape-client (0.9.6-1ubuntu1) feisty; urgency=low * New upstream release. * Bugs related to the handling of DBus types are fixed on feisty. -- Jamshed Kakar Fri, 13 Apr 2007 18:06:10 -0700 landscape-client (0.9.5-1ubuntu1) feisty; urgency=low * New upstream release. * Old-school and oddly formatted GECOS fields are handled correctly. -- Jamshed Kakar Thu, 30 Mar 2007 16:50:49 -0400 landscape-client (0.9.4-1ubuntu1) feisty; urgency=low * New upstream release. * Change architecture to all. -- Jamshed Kakar Thu, 30 Mar 2007 12:46:23 -0400 landscape-client (0.9.3-1ubuntu1) feisty; urgency=low * New upstream release. * Add override file. * Bump application and package versions. -- Jamshed Kakar Thu, 29 Mar 2007 17:21:50 -0400 landscape-client (0.9.2-1ubuntu1) feisty; urgency=low * Bump debhelper version. * Build-Depend on python-dev, lsb-release. * Pre-Depend on debconf | debconf-2.0. * Move ${python:Depends} to Depends. * Make the packaging compatible with dapper and newer distro releases. -- Matthias Klose Wed, 28 Mar 2007 21:21:20 +0200 landscape-client (0.9.2-1) unstable; urgency=low * New upstream release. * Really fixed package. -- Jamshed Kakar Tue, 21 Mar 2007 09:57:00 -0400 landscape-client (0.9.1-3) unstable; urgency=low * Really fixed package. -- Jamshed Kakar Tue, 20 Mar 2007 16:35:00 -0400 landscape-client (0.9.1-2) unstable; urgency=low * Fixed package. -- Jamshed Kakar Tue, 20 Mar 2007 16:25:00 -0400 landscape-client (0.9.1-1) unstable; urgency=low * New upstream release. * bpickle encoding format has changed in a non-backwards compatible way to fix float encoding-related problems. * API version updated to 2.0. -- Jamshed Kakar Tue, 20 Mar 2007 15:06:17 -0400 landscape-client (0.9.0-1) unstable; urgency=low * New upstream release. * Group-management related tasks such as add/remove member and create group are now supported. * Client reports hardware inventory information to the server. * Unicode handling with regards to the passwd file is improved. -- Jamshed Kakar Fri, 9 Mar 2007 16:04:51 -0800 landscape-client (0.8.1-1) unstable; urgency=low * New upstream release. * Account registration log message no longer exposes account password. -- Jamshed Kakar Thu, 18 Jan 2007 23:07:00 -0800 landscape-client (0.8.0-1) unstable; urgency=low * New upstream release. * Process monitoring logic stops trying to restart the client if too many restarts occur during a short period of time. * Client includes an SSL certificate to verify the server with. * Package depends on python2.4-pycurl, which is necessary for the new SSL support. -- Jamshed Kakar Thu, 18 Jan 2007 10:48:00 -0800 landscape-client (0.7.0-1) unstable; urgency=low * New upstream release. * Client can daemonize itself and perform basic process monitoring. * Client writes a useful log now. Command-line options have been added to control log verbosity. * Package depends on perl-modules, which is necessary for bare-bones server installs. -- Jamshed Kakar Tue, 2 Jan 2007 12:54:00 -0800 landscape-client (0.6.1-1) unstable; urgency=low * User management plugin handles extra Ubuntu user fields. -- Jamshed Kakar Thu, 15 Dec 2006 10:22:00 -0800 landscape-client (0.6.0-2) unstable; urgency=low * Reverted to deprecated build rule to fix package breakage on dapper. -- Jamshed Kakar Thu, 7 Dec 2006 10:54:00 -0800 landscape-client (0.6.0-1) unstable; urgency=low * New upstream release. * The active-process-info plugin provides server-controllable end/kill actions. * Messages are no longer delivered twice if a broken message gets into the message store. -- Jamshed Kakar Wed, 6 Dec 2006 14:47:00 -0800 landscape-client (0.5.0-1) unstable; urgency=low * New upstream release. * Client-to-server exchanges now happen in a thread to avoid blocking monitoring plugins. * Problems that caused slow client registration have been fixed. * The computer-info plugin now reports information about the distribution installed on a machine. -- Jamshed Kakar Fri, 10 Nov 2006 12:47:00 -0800 landscape-client (0.4.0-1) unstable; urgency=low * New upstream release. * Disk, load, memory/swap and temperature data is now reported at 300s-aligned intervals. * Improvements to registration system. * Improvements to plugin infrastructure. -- Jamshed Kakar Fri, 13 Oct 2006 09:25:00 -0300 landscape-client (0.3.0-1) unstable; urgency=low * New upstream release. * Major reorganization of client code. -- Christopher Armstrong Tue, 22 Aug 2006 09:56:00 -0400 landscape-client (0.2.5-1) unstable; urgency=low * New upstream release. * Fix a bug in the newly introduced client upgrade machinery. -- Christopher Armstrong Wed, 4 Aug 2006 10:45:00 -0500 landscape-client (0.2.4-1) unstable; urgency=low * New upstream release. * User management should now be working. -- Christopher Armstrong Wed, 3 Aug 2006 15:12:00 -0500 landscape-client (0.2.3-1) unstable; urgency=low * New upstream release. * Fixed a bug in the init script preventing landscape-client from starting after reboots. -- Christopher Armstrong Wed, 26 Jul 2006 15:47:00 -0500 landscape-client (0.2.2-1) unstable; urgency=low * New upstream release. -- Christopher Armstrong Thu, 6 Jul 2006 18:38:00 -0500 landscape-client (0.2.1-1) unstable; urgency=low * Added support for debconf. -- Gustavo Niemeyer Wed, 5 Jul 2006 14:13:25 -0300 landscape-client (0.2.0-1) unstable; urgency=low * Initial (internal) release. -- Christopher Armstrong Wed, 28 Jun 2006 14:32:30 -0500 landscape-client (0.1) dapper; urgency=low * Empty placeholder package -- Matt Zimmerman Fri, 26 May 2006 06:37:55 -0700 debian/landscape-common.postinst0000644000000000000000000000755313404451062014224 0ustar #!/bin/sh set -e # summary of how this script can be called: # * `configure' # * `abort-upgrade' # * `abort-remove' `in-favour' # # * `abort-remove' # * `abort-deconfigure' `in-favour' # `removing' # # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package . /usr/share/debconf/confmodule trap "db_stop || true" EXIT HUP INT QUIT TERM PACKAGE=landscape-common # Use the default installed Python. Running just "python" might run # something from /usr/local/bin, which doesn't necessarily support # running the landscape client. PYTHON=/usr/bin/python case "$1" in configure) db_get $PACKAGE/sysinfo # Choices: # * Do not display sysinfo on login # * Cache sysinfo in /etc/motd # * Run sysinfo on every login SYSINFO="${RET:-Cache sysinfo in /etc/motd}" WRAPPER=/usr/share/landscape/landscape-sysinfo.wrapper PROFILE_LOCATION=/etc/profile.d/50-landscape-sysinfo.sh UPDATE_MOTD_LOCATION=/etc/update-motd.d/50-landscape-sysinfo if [ "$RET" = "Cache sysinfo in /etc/motd" ]; then rm -f $PROFILE_LOCATION 2>/dev/null || true ln -sf $WRAPPER $UPDATE_MOTD_LOCATION /usr/sbin/update-motd 2>/dev/null || true elif [ "$RET" = "Run sysinfo on every login" ]; then rm -f $UPDATE_MOTD_LOCATION 2>/dev/null || true /usr/sbin/update-motd 2>/dev/null || true ln -sf $WRAPPER $PROFILE_LOCATION else rm -f $UPDATE_MOTD_LOCATION 2>/dev/null || true /usr/sbin/update-motd 2>/dev/null || true rm -f $PROFILE_LOCATION || true fi # 0.9.1 introduces non-backwards compatible changes. This detects # whether or not the data is in the current format. If not, all # existing data is removed. DATA_DIR=/var/lib/landscape if [ -d $DATA_DIR/data ]; then rm -rf $DATA_DIR/* elif [ -f $DATA_DIR/client/data.bpickle ]; then LAST_BYTE=`sed -n '$,$s/.*\(.\)/\1/p' $DATA_DIR/client/data.bpickle` if [ "$LAST_BYTE" = e ]; then rm -rf $DATA_DIR/* fi fi # Create landscape system user if ! getent passwd landscape >/dev/null; then adduser --quiet --system --group --disabled-password \ --home /var/lib/landscape --no-create-home landscape fi # Create landscape system group (for <= 1.0.29.1-0ubuntu0.9.04.0) if ! getent group landscape >/dev/null; then addgroup --quiet --system landscape fi # Ensure primary group is landscape (for <= 1.0.29.1-0ubuntu0.9.04.0) if ! usermod -g landscape landscape > /dev/null 2>&1; then echo "ERROR: usermod -g landscape landscape failed." fi # Fix prior ownerships, we exclude the custom-graph-scripts directory # because there might script-generated files that we want to preserve # the ownership of. if [ -d /var/lib/landscape/client ]; then find /var/lib/landscape/client/ -wholename /var/lib/landscape/client/custom-graph-scripts -prune -or -exec chown landscape {} \; > /dev/null 2>&1 fi [ -d /var/lib/landscape/.gnupg ] && chown -R landscape /var/lib/landscape/.gnupg || : chown -R landscape /var/log/landscape ;; abort-upgrade|abort-remove|abort-deconfigure) ;; *) echo "postinst called with unknown argument \`$1'" >&2 exit 1 ;; esac # dh_installdeb will replace this with shell code automatically # generated by other debhelper scripts. #DEBHELPER# exit 0 debian/source/0000755000000000000000000000000013404451062010465 5ustar debian/source/format0000644000000000000000000000001413404451062011673 0ustar 3.0 (quilt) debian/pycompat0000644000000000000000000000000213404451062010734 0ustar 2 debian/control0000644000000000000000000000655013404451062010576 0ustar Source: landscape-client Section: admin Priority: optional Maintainer: Ubuntu Developers XSBC-Original-Maintainer: Landscape Team Build-Depends: debhelper (>= 7), po-debconf, python-dev, lsb-release, gawk, python-twisted-core, python-distutils-extra Standards-Version: 3.8.2 XS-Python-Version: >= 2.4, << 2.8 Package: landscape-common Architecture: any Depends: ${python:Depends}, ${misc:Depends}, ${extra:Depends}, ${shlibs:Depends}, python-twisted-core, python-configobj, python-apt, ca-certificates, python-gdbm, lsb-release, lsb-base, adduser, bc, lshw, libpam-modules Suggests: ${extra:Suggests} # added/changed when we moved the sysinfo manpage from client to common # in r582. There is no accompaining "Breaks" because -client requires # the exact same version of -common, which will trigger the upgrade # and avoid the case discussed in # http://www.debian.org/doc/debian-policy/footnotes.html#f53 Replaces: landscape-client (<< 12.09+bzr582) Description: The Landscape administration system client - Common files Landscape is a web-based tool for managing Ubuntu systems. This package is necessary if you want your machine to be managed in a Landscape account. . This package provides the core libraries. XB-Python-Version: ${python:Versions} Package: landscape-client Architecture: any Depends: ${python:Depends}, ${misc:Depends}, ${extra:Depends}, ${shlibs:Depends}, python-twisted-web, python-pycurl, landscape-common (= ${binary:Version}) Suggests: ${extra:Suggests} Description: The Landscape administration system client Landscape is a web-based tool for managing Ubuntu systems. This package is necessary if you want your machine to be managed in a Landscape account. . This package provides the Landscape client and requires a Landscape account. XB-Python-Version: ${python:Versions} Package: landscape-client-ui Architecture: any Depends: ${python:Depends}, ${misc:Depends}, landscape-client (= ${binary:Version}), landscape-client-ui-install (= ${binary:Version}), python-gi, python-dbus, policykit-1, gir1.2-notify-0.7, gir1.2-gtk-3.0 # Added when we moved the /etc/dbus-1/system.d/landscape.conf file from -client to # -client-ui in r682. There is no accompaining "Breaks" because -client-ui requires # the exact same version of -client, which will trigger the upgrade # and avoid the case discussed in # http://www.debian.org/doc/debian-policy/footnotes.html#f53 Replaces: landscape-client (<< 13.05.1+bzr682) Description: The Landscape administration system client - UI configuration Landscape is a web-based tool for managing Ubuntu systems. . This package provides the Landscape client configuration UI. XB-Python-Version: ${python:Versions} Package: landscape-client-ui-install Architecture: any Depends: ${python:Depends}, ${misc:Depends}, python-gi, python-dbus, policykit-1, gir1.2-gtk-3.0, python-aptdaemon.gtk3widgets Description: The Landscape administration system client - UI installer Landscape is a web-based tool for managing Ubuntu systems. . This package provides an automatic installer for landscape-client-ui. XB-Python-Version: ${python:Versions} debian/landscape-common.postrm0000644000000000000000000000211613404451062013653 0ustar #!/bin/sh set -e # summary of how this script can be called: # * `remove' # * `purge' # * `upgrade' # * `failed-upgrade' # * `abort-install' # * `abort-install' # * `abort-upgrade' # * `disappear' # # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package case "$1" in purge) LOG_DIR=/var/log/landscape CONFIG_FILE=/etc/landscape/client.conf deluser --quiet --system landscape >/dev/null || true rm -f "${LOG_DIR}/sysinfo.log"* rm -f "${CONFIG_FILE}" ;; remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) ;; *) echo "postrm called with unknown argument \`$1'" >&2 exit 1 ;; esac # dh_installdeb will replace this with shell code automatically # generated by other debhelper scripts. #DEBHELPER# exit 0 debian/landscape-common.prerm0000644000000000000000000000206513404451062013457 0ustar #!/bin/sh # prerm script for landscape-common # # see: dh_installdeb(1) set -e # summary of how this script can be called: # * `remove' # * `upgrade' # * `failed-upgrade' # * `remove' `in-favour' # * `deconfigure' `in-favour' # `removing' # # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package case "$1" in remove|upgrade|deconfigure) rm -f /etc/update-motd.d/50-landscape-sysinfo 2>/dev/null || true /usr/sbin/update-motd 2>/dev/null || true rm -f /etc/profile.d/landscape-sysinfo.sh 2>/dev/null || true ;; failed-upgrade) ;; *) echo "prerm called with unknown argument \`$1'" >&2 exit 1 ;; esac # dh_installdeb will replace this with shell code automatically # generated by other debhelper scripts. #DEBHELPER# exit 0 debian/po/0000755000000000000000000000000013404451207007604 5ustar debian/po/templates.pot0000644000000000000000000001430613404451062012331 0ustar # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: landscape-client\n" "Report-Msgid-Bugs-To: landscape-client@packages.debian.org\n" "POT-Creation-Date: 2012-05-28 16:40-0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #. Type: string #. Description #: ../landscape-client.templates:1001 msgid "Computer Title:" msgstr "" #. Type: string #. Description #: ../landscape-client.templates:1001 msgid "" "Descriptive text to identify this computer uniquely in the Landscape user " "interface." msgstr "" #. Type: string #. Description #: ../landscape-client.templates:2001 msgid "Account Name:" msgstr "" #. Type: string #. Description #: ../landscape-client.templates:2001 msgid "" "Short lowercase identifier of the Landscape account this computer will be " "assigned." msgstr "" #. Type: password #. Description #: ../landscape-client.templates:3001 msgid "Registration Key:" msgstr "" #. Type: password #. Description #: ../landscape-client.templates:3001 msgid "" "Client registration key for the given Landscape account. Only needed if the " "given account is requesting a client registration key." msgstr "" #. Type: string #. Description #: ../landscape-client.templates:4001 msgid "Landscape Server URL:" msgstr "" #. Type: string #. Description #: ../landscape-client.templates:4001 msgid "The server URL to connect to." msgstr "" #. Type: string #. Description #: ../landscape-client.templates:5001 msgid "Message Exchange Interval:" msgstr "" #. Type: string #. Description #: ../landscape-client.templates:5001 msgid "" "Interval, in seconds, between normal message exchanges with the Landscape " "server." msgstr "" #. Type: string #. Description #: ../landscape-client.templates:6001 msgid "Urgent Message Exchange Interval:" msgstr "" #. Type: string #. Description #: ../landscape-client.templates:6001 msgid "" "Interval, in seconds, between urgent message exchanges with the Landscape " "server." msgstr "" #. Type: string #. Description #: ../landscape-client.templates:7001 msgid "Landscape PingServer URL:" msgstr "" #. Type: string #. Description #: ../landscape-client.templates:7001 msgid "The URL to perform lightweight exchange initiation with." msgstr "" #. Type: string #. Description #: ../landscape-client.templates:8001 msgid "Ping Interval:" msgstr "" #. Type: string #. Description #: ../landscape-client.templates:8001 msgid "" "Interval, in seconds, between client ping exchanges with the Landscape " "server." msgstr "" #. Type: string #. Description #: ../landscape-client.templates:9001 msgid "HTTP proxy (blank for none):" msgstr "" #. Type: string #. Description #: ../landscape-client.templates:9001 msgid "The URL of the HTTP proxy, if one is needed." msgstr "" #. Type: string #. Description #: ../landscape-client.templates:10001 msgid "HTTPS proxy (blank for none):" msgstr "" #. Type: string #. Description #: ../landscape-client.templates:10001 msgid "The URL of the HTTPS proxy, if one is needed." msgstr "" #. Type: string #. Description #: ../landscape-client.templates:11001 msgid "Initial tags for first registration:" msgstr "" #. Type: string #. Description #: ../landscape-client.templates:11001 msgid "" "Comma separated list of tags which will be assigned to this computer on its " "first registration. Once the machine is registered, these tags can only be " "changed using the Landscape server." msgstr "" #. Type: boolean #. Description #: ../landscape-client.templates:12001 msgid "Register this system with the Landscape server?" msgstr "" #. Type: boolean #. Description #: ../landscape-client.templates:12001 msgid "" "Register this system with a preexisting Landscape account. Please go to " "http://landscape.canonical.com if you need a Landscape account." msgstr "" #. Type: select #. Choices #. Translators beware! the following three strings form a single #. Choices menu. - Every one of these strings has to fit in a standard #. 80 characters console, as the fancy screen setup takes up some space #. try to keep below ~71 characters. #. DO NOT USE commas (,) in Choices translations otherwise #. this will break the choices shown to users #: ../landscape-common.templates:1001 msgid "Do not display sysinfo on login" msgstr "" #. Type: select #. Choices #. Translators beware! the following three strings form a single #. Choices menu. - Every one of these strings has to fit in a standard #. 80 characters console, as the fancy screen setup takes up some space #. try to keep below ~71 characters. #. DO NOT USE commas (,) in Choices translations otherwise #. this will break the choices shown to users #: ../landscape-common.templates:1001 msgid "Cache sysinfo in /etc/motd" msgstr "" #. Type: select #. Choices #. Translators beware! the following three strings form a single #. Choices menu. - Every one of these strings has to fit in a standard #. 80 characters console, as the fancy screen setup takes up some space #. try to keep below ~71 characters. #. DO NOT USE commas (,) in Choices translations otherwise #. this will break the choices shown to users #: ../landscape-common.templates:1001 msgid "Run sysinfo on every login" msgstr "" #. Type: select #. Description #: ../landscape-common.templates:1002 msgid "landscape-sysinfo configuration:" msgstr "" #. Type: select #. Description #: ../landscape-common.templates:1002 msgid "" "Landscape includes a tool and a set of modules that can display system " "status, information, and statistics on login." msgstr "" #. Type: select #. Description #: ../landscape-common.templates:1002 msgid "" "This information can be gathered periodically (every 10 minutes) and " "automatically written to /etc/motd. The data could be a few minutes out-of-" "date." msgstr "" #. Type: select #. Description #: ../landscape-common.templates:1002 msgid "" "Or, this information can be gathered at login. The data will be more " "current, but might introduce a small delay at login." msgstr "" debian/po/POTFILES.in0000644000000000000000000000015213404451062011356 0ustar [type: gettext/rfc822deb] landscape-client.templates [type: gettext/rfc822deb] landscape-common.templates debian/copyright0000644000000000000000000000243113404451062011120 0ustar This package was debianized by * Jamshed Kakar on Wed, 28 Jun 2006 * Rick Clark in Aug 2008 * Dustin Kirkland in Aug 2008 Copyright (C) 2006-2008 Canonical Ltd. License: Upstream source is located at: https://code.launchpad.net/landscape-client Copyright: The packaging of this software and the programs in this package are distributed under the terms of the GNU General Public License, version 2 as distributed by the Free Software Foundation. License: Copyright (C) 2006-2008 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 2, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . On Debian systems, the complete text of the GNU General Public License can be found in `/usr/share/common-licenses/GPL-2'.