diff -Nru landscape-client-16.03/debian/changelog landscape-client-16.03/debian/changelog --- landscape-client-16.03/debian/changelog 2017-03-22 17:37:27.000000000 +0100 +++ landscape-client-16.03/debian/changelog 2017-11-02 13:47:52.000000000 +0100 @@ -1,3 +1,10 @@ +landscape-client (16.03-0ubuntu4) bionic; urgency=medium + + * Check if ubuntu-release-upgrader is running before apt-update. + (LP: #1699179) + + -- Dariusz Gadomski Thu, 02 Nov 2017 13:47:52 +0100 + landscape-client (16.03-0ubuntu3) zesty; urgency=medium * Don't report packages that are coming from backports, so that Landscape diff -Nru landscape-client-16.03/debian/patches/check-release-upgrader-1699179.diff landscape-client-16.03/debian/patches/check-release-upgrader-1699179.diff --- landscape-client-16.03/debian/patches/check-release-upgrader-1699179.diff 1970-01-01 01:00:00.000000000 +0100 +++ landscape-client-16.03/debian/patches/check-release-upgrader-1699179.diff 2017-11-02 13:47:52.000000000 +0100 @@ -0,0 +1,169 @@ +Description: Check if ubuntu-release-upgrader is running before apt-update + Do an additional check before running apt-update to make sure there's + no release upgrader running in the background, as locking the apt + database may interrupt the upgrade. + . + landscape-client (16.03-0ubuntu4) bionic; urgency=medium + . + * Check if ubuntu-release-upgrader is running before apt-update. + (LP: #1699179) +Author: Dariusz Gadomski +Bug-Ubuntu: https://bugs.launchpad.net/bugs/1699179 + +--- +The information above should follow the Patch Tagging Guidelines, please +checkout http://dep.debian.net/deps/dep3/ to learn about the format. Here +are templates for supplementary fields that you might want to add: + +Origin: upstream, https://github.com/CanonicalLtd/landscape-client/commit/c926cfaf3ad1a8759f0b269540391bbfc20dc00a +Bug-Ubuntu: https://bugs.launchpad.net/bugs/1699179 + +--- landscape-client-16.03.orig/landscape/package/reporter.py ++++ landscape-client-16.03/landscape/package/reporter.py +@@ -5,6 +5,7 @@ import sys + import os + import glob + import apt_pkg ++import re + + from twisted.internet.defer import ( + Deferred, succeed, inlineCallbacks, returnValue) +@@ -24,6 +25,9 @@ from landscape.package.store import Unkn + 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): +@@ -195,6 +199,51 @@ class PackageReporter(PackageTaskHandler + 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): + """ +@@ -203,9 +252,11 @@ class PackageReporter(PackageTaskHandler + + @return: a deferred returning (out, err, code) + """ +- if (self._config.force_apt_update or self._apt_sources_have_changed() +- or self._apt_update_timeout_expired( +- self._config.apt_update_interval)): ++ if (self._config.force_apt_update or ++ self._apt_sources_have_changed() or ++ self._apt_update_timeout_expired(self._config.apt_update_interval) ++ ) and \ ++ not self._is_release_upgrader_running(): + + accepted_apt_errors = ( + "Problem renaming the file /var/cache/apt/srcpkgcache.bin", +@@ -253,7 +304,7 @@ class PackageReporter(PackageTaskHandler + "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)) + +--- landscape-client-16.03.orig/landscape/package/tests/test_reporter.py ++++ landscape-client-16.03/landscape/package/tests/test_reporter.py +@@ -3,6 +3,7 @@ import os + import time + import apt_pkg + import shutil ++import subprocess + + from twisted.internet.defer import Deferred, succeed, inlineCallbacks + from twisted.internet import reactor +@@ -1506,7 +1507,7 @@ class PackageReporterAptTest(LandscapeTe + 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() +@@ -1543,7 +1544,7 @@ class PackageReporterAptTest(LandscapeTe + 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() +@@ -1782,6 +1783,24 @@ class PackageReporterAptTest(LandscapeTe + 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): + diff -Nru landscape-client-16.03/debian/patches/series landscape-client-16.03/debian/patches/series --- landscape-client-16.03/debian/patches/series 2017-03-22 17:37:14.000000000 +0100 +++ landscape-client-16.03/debian/patches/series 2017-11-02 13:47:52.000000000 +0100 @@ -1,2 +1,3 @@ swift-changed-api-1563565.diff ignore-backports-1668583.diff +check-release-upgrader-1699179.diff