Merge ~jansdhillon/ubuntu/+source/landscape-client:lp2099283-package-reporter-fixes-focal into ubuntu/+source/landscape-client:ubuntu/focal-devel

Proposed by Jan-Yaeger Dhillon
Status: Needs review
Proposed branch: ~jansdhillon/ubuntu/+source/landscape-client:lp2099283-package-reporter-fixes-focal
Merge into: ubuntu/+source/landscape-client:ubuntu/focal-devel
Diff against target: 1578 lines (+1550/-0)
4 files modified
debian/changelog (+7/-0)
debian/patches/package-reporter-high-cpu.patch (+110/-0)
debian/patches/parse-lsb-output.patch (+1431/-0)
debian/patches/series (+2/-0)
Reviewer Review Type Date Requested Status
David McLain Pending
Ubuntu Sponsors Pending
Canonical Server Reporter Pending
git-ubuntu import Pending
Review via email: mp+482538@code.launchpad.net
To post a comment you must log in.
f3a75a7... by jansdhillon <email address hidden>

fix lp question number to avoid confusion with a separate bug

bef4e42... by jansdhillon <email address hidden>

update LP numbers in patches/changelog

Unmerged commits

bef4e42... by jansdhillon <email address hidden>

update LP numbers in patches/changelog

f3a75a7... by jansdhillon <email address hidden>

fix lp question number to avoid confusion with a separate bug

934a18a... by jansdhillon <email address hidden>

add lp403745-package-reporter-high-cpu.patch, lp2031036-parse-lsb-output.patch, and update changelog

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/debian/changelog b/debian/changelog
2index 86246ae..ce8c1af 100644
3--- a/debian/changelog
4+++ b/debian/changelog
5@@ -1,3 +1,10 @@
6+landscape-client (23.02-0ubuntu1~20.04.6) focal; urgency=medium
7+
8+ * d/p/package-reporter-high-cpu.patch: backport fix to reduce CPU usage of package-reporter by avoiding creating Origin objects from the Python apt package. (LP: #2099283)
9+ * d/p/parse-lsb-output.patch: backport fix for error raised by package-reporter when parsing lsb output and add support for lsb modules. (LP: #2031036)
10+
11+ -- Jan-Yaeger Dhillon <jan.dhillon@canonical.com> Mon, 10 Mar 2025 16:50:42 -0700
12+
13 landscape-client (23.02-0ubuntu1~20.04.5) focal; urgency=medium
14
15 * d/p/0002-fix-locale-error.path: revert previous fix for #1827857, as
16diff --git a/debian/patches/package-reporter-high-cpu.patch b/debian/patches/package-reporter-high-cpu.patch
17new file mode 100644
18index 0000000..d0ea0f1
19--- /dev/null
20+++ b/debian/patches/package-reporter-high-cpu.patch
21@@ -0,0 +1,110 @@
22+Description: Reduce CPU usage of package-reporter (LP: #2099283)
23+Author: Jan-Yaeger Dhillon, jan.dhillon@canonical.com
24+Origin: backport, https://github.com/canonical/landscape-client/commit/f5a6d8a924097a37e70ec25522bd748a341212bf
25+Bug-Ubuntu: https://answers.launchpad.net/landscape-client/+question/403745
26+Reviewed-by: Kevin Nasto, kevin.nasto@canonical.com
27+Last-Update: 2025-03-09
28+Index: landscape-client/landscape/client/package/reporter.py
29+===================================================================
30+--- landscape-client.orig/landscape/client/package/reporter.py
31++++ landscape-client/landscape/client/package/reporter.py
32+@@ -638,48 +638,53 @@ class PackageReporter(PackageTaskHandler
33+ backports_archive = "{}-backports".format(lsb["code-name"])
34+ security_archive = "{}-security".format(lsb["code-name"])
35+
36+- for package in self._facade.get_packages():
37++ for package_version in self._facade.get_packages():
38++ # Get archives from the list of PackageFiles
39++ # for the given package version rather than using
40++ # package_version.origins from the Python apt package.
41++ # We only want to check the archives, and creating
42++ # Origins using package_version.origins is expensive.
43++ # See /usr/lib/python3/dist-packages/apt/package.py
44++ archives = [
45++ # Ex. jammy-backports
46++ package_file.archive
47++ for package_file, _ in package_version._cand.file_list
48++ ]
49++
50+ # Don't include package versions from the official backports
51+ # archive. The backports archive is enabled by default since
52+ # xenial with a pinning policy of 100. Ideally we would
53+ # support pinning, but we don't yet. In the mean time, we
54+ # ignore backports, so that packages don't get automatically
55+ # upgraded to the backports version.
56+- backport_origins = [
57+- origin for origin in package.origins
58+- if origin.archive == backports_archive]
59+- if backport_origins and (
60+- len(backport_origins) == len(package.origins)):
61++ if all(archive == backports_archive for archive in archives):
62+ # Ignore the version if it's only in the official
63+ # backports archive. If it's somewhere else as well,
64+ # e.g. a PPA, we assume it was added manually and the
65+ # user wants to get updates from it.
66+ continue
67+- hash = self._facade.get_package_hash(package)
68++ hash = self._facade.get_package_hash(package_version)
69+ id = self._store.get_hash_id(hash)
70+ if id is not None:
71+- if self._facade.is_package_installed(package):
72++ if self._facade.is_package_installed(package_version):
73+ current_installed.add(id)
74+- if self._facade.is_package_available(package):
75++ if self._facade.is_package_available(package_version):
76+ current_available.add(id)
77+- if self._facade.is_package_autoremovable(package):
78++ if self._facade.is_package_autoremovable(package_version):
79+ current_autoremovable.add(id)
80+ else:
81+ current_available.add(id)
82+
83+ # Are there any packages that this package is an upgrade for?
84+- if self._facade.is_package_upgrade(package):
85++ if self._facade.is_package_upgrade(package_version):
86+ current_upgrades.add(id)
87+
88+ # Is this package present in the security pocket?
89+- security_origins = any(
90+- origin for origin in package.origins
91+- if origin.archive == security_archive)
92+- if security_origins:
93++ if security_archive in archives:
94+ current_security.add(id)
95+
96+- for package in self._facade.get_locked_packages():
97+- hash = self._facade.get_package_hash(package)
98++ for package_version in self._facade.get_locked_packages():
99++ hash = self._facade.get_package_hash(package_version)
100+ id = self._store.get_hash_id(hash)
101+ if id is not None:
102+ current_locked.add(id)
103+Index: landscape-client/landscape/client/package/tests/test_reporter.py
104+===================================================================
105+--- landscape-client.orig/landscape/client/package/tests/test_reporter.py
106++++ landscape-client/landscape/client/package/tests/test_reporter.py
107+@@ -1067,6 +1067,24 @@ class PackageReporterAptTest(LandscapeTe
108+ result = self.reporter.detect_packages_changes()
109+ return result.addCallback(got_result)
110+
111++ def test_compute_packages_changes_package_origins_not_called(self):
112++ """
113++ Archive info is extracted directly from the package versions
114++ and the apt.package.Version.origins property is not called
115++ """
116++ with mock.patch("apt.package.Version.origins") as version_origins_mock:
117++ self.successResultOf(self.reporter._compute_packages_changes())
118++ version_origins_mock.assert_not_called()
119++
120++ def test_compute_packages_changes_origins_not_created(self):
121++ """
122++ Archive info is extracted directly from the package versions
123++ and no Origins are created (expensive find_index() is not called)
124++ """
125++ with mock.patch("apt.package.Origin.__init__") as origin_mock:
126++ self.successResultOf(self.reporter._compute_packages_changes())
127++ origin_mock.assert_not_called()
128++
129+ def test_detect_packages_changes_with_backports_others(self):
130+ """
131+ Packages coming from backport archives that aren't named like
132diff --git a/debian/patches/parse-lsb-output.patch b/debian/patches/parse-lsb-output.patch
133new file mode 100644
134index 0000000..b852e19
135--- /dev/null
136+++ b/debian/patches/parse-lsb-output.patch
137@@ -0,0 +1,1431 @@
138+Description: Fix package-reporter error when parsing lsb output and support lsb modules (LP: #2031036)
139+Origin: backport, https://github.com/canonical/landscape-client/commit/75742a35074a9a8296ea3d3f445a80df1764404d
140+Bug-Ubuntu: https://bugs.launchpad.net/landscape-client/+bug/2031036
141+Index: landscape-client/landscape/client/monitor/computerinfo.py
142+===================================================================
143+--- landscape-client.orig/landscape/client/monitor/computerinfo.py
144++++ landscape-client/landscape/client/monitor/computerinfo.py
145+@@ -1,13 +1,16 @@
146+-import os
147+ import logging
148+-from twisted.internet.defer import inlineCallbacks, returnValue
149++import os
150+
151++from twisted.internet.defer import inlineCallbacks
152++from twisted.internet.defer import returnValue
153++
154++from landscape.client.monitor.plugin import MonitorPlugin
155++from landscape.lib.cloud import fetch_ec2_meta_data
156+ from landscape.lib.fetch import fetch_async
157+ from landscape.lib.fs import read_text_file
158+-from landscape.lib.lsb_release import LSB_RELEASE_FILENAME, parse_lsb_release
159+-from landscape.lib.cloud import fetch_ec2_meta_data
160+ from landscape.lib.network import get_fqdn
161+-from landscape.client.monitor.plugin import MonitorPlugin
162++from landscape.lib.os_release import OS_RELEASE_FILENAME
163++from landscape.lib.os_release import parse_os_release
164+
165+ METADATA_RETRY_MAX = 3 # Number of retries to get EC2 meta-data
166+
167+@@ -22,13 +25,17 @@ class ComputerInfo(MonitorPlugin):
168+ persist_name = "computer-info"
169+ scope = "computer"
170+
171+- def __init__(self, get_fqdn=get_fqdn,
172+- meminfo_filename="/proc/meminfo",
173+- lsb_release_filename=LSB_RELEASE_FILENAME,
174+- root_path="/", fetch_async=fetch_async):
175++ def __init__(
176++ self,
177++ get_fqdn=get_fqdn,
178++ meminfo_filename="/proc/meminfo",
179++ os_release_filename=OS_RELEASE_FILENAME,
180++ root_path="/",
181++ fetch_async=fetch_async,
182++ ):
183+ self._get_fqdn = get_fqdn
184+ self._meminfo_filename = meminfo_filename
185+- self._lsb_release_filename = lsb_release_filename
186++ self._os_release_filename = os_release_filename
187+ self._root_path = root_path
188+ self._cloud_instance_metadata = None
189+ self._cloud_retries = 0
190+@@ -125,17 +132,17 @@ class ComputerInfo(MonitorPlugin):
191+ def _get_distribution_info(self):
192+ """Get details about the distribution."""
193+ message = {}
194+- message.update(parse_lsb_release(self._lsb_release_filename))
195++ message.update(parse_os_release(self._os_release_filename))
196+ return message
197+
198+ @inlineCallbacks
199+ def _create_cloud_instance_metadata_message(self):
200+ """Fetch cloud metadata and insert it in a message."""
201+ message = None
202+- if (self._cloud_instance_metadata is None and
203+- self._cloud_retries < METADATA_RETRY_MAX
204+- ):
205+-
206++ if (
207++ self._cloud_instance_metadata is None
208++ and self._cloud_retries < METADATA_RETRY_MAX
209++ ):
210+ self._cloud_instance_metadata = yield self._fetch_ec2_meta_data()
211+ message = self._cloud_instance_metadata
212+ returnValue(message)
213+Index: landscape-client/landscape/client/monitor/tests/test_computerinfo.py
214+===================================================================
215+--- landscape-client.orig/landscape/client/monitor/tests/test_computerinfo.py
216++++ landscape-client/landscape/client/monitor/tests/test_computerinfo.py
217+@@ -1,19 +1,32 @@
218+-import mock
219+ import os
220+ import re
221++from unittest import mock
222+
223+-from twisted.internet.defer import succeed, fail, inlineCallbacks
224+-
225+-from landscape.lib.fetch import HTTPCodeError, PyCurlError
226++from twisted.internet.defer import fail
227++from twisted.internet.defer import inlineCallbacks
228++from twisted.internet.defer import succeed
229++
230++from landscape.client.monitor.computerinfo import ComputerInfo
231++from landscape.client.monitor.computerinfo import METADATA_RETRY_MAX
232++from landscape.client.tests.helpers import LandscapeTest
233++from landscape.client.tests.helpers import MonitorHelper
234++from landscape.lib.fetch import HTTPCodeError
235++from landscape.lib.fetch import PyCurlError
236+ from landscape.lib.fs import create_text_file
237+-from landscape.client.monitor.computerinfo import (
238+- ComputerInfo, METADATA_RETRY_MAX)
239+-from landscape.client.tests.helpers import LandscapeTest, MonitorHelper
240+-
241+-SAMPLE_LSB_RELEASE = "DISTRIB_ID=Ubuntu\n" \
242+- "DISTRIB_RELEASE=6.06\n" \
243+- "DISTRIB_CODENAME=dapper\n" \
244+- "DISTRIB_DESCRIPTION=\"Ubuntu 6.06.1 LTS\"\n"
245++
246++SAMPLE_OS_RELEASE = """PRETTY_NAME="Ubuntu 22.04.3 LTS"
247++NAME="Ubuntu"
248++VERSION_ID="22.04"
249++VERSION="22.04.3 LTS (Jammy Jellyfish)"
250++VERSION_CODENAME=codename
251++ID=ubuntu
252++ID_LIKE=debian
253++HOME_URL="https://www.ubuntu.com/"
254++SUPPORT_URL="https://help.ubuntu.com/"
255++BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
256++PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
257++UBUNTU_CODENAME=codename
258++"""
259+
260+
261+ def get_fqdn():
262+@@ -21,7 +34,6 @@ def get_fqdn():
263+
264+
265+ class ComputerInfoTest(LandscapeTest):
266+-
267+ helpers = [MonitorHelper]
268+
269+ sample_memory_info = """
270+@@ -52,7 +64,7 @@ VmallocChunk: 107432 kB
271+
272+ def setUp(self):
273+ LandscapeTest.setUp(self)
274+- self.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
275++ self.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
276+ self.query_results = {}
277+
278+ def fetch_stub(url, **kwargs):
279+@@ -214,16 +226,16 @@ VmallocChunk: 107432 kB
280+ the distribution data reported by the plugin.
281+ """
282+ self.mstore.set_accepted_types(["distribution-info"])
283+- plugin = ComputerInfo(lsb_release_filename=self.lsb_release_filename)
284++ plugin = ComputerInfo(os_release_filename=self.os_release_filename)
285+ self.monitor.add(plugin)
286+
287+ plugin.exchange()
288+ message = self.mstore.get_pending_messages()[0]
289+ self.assertEqual(message["type"], "distribution-info")
290+ self.assertEqual(message["distributor-id"], "Ubuntu")
291+- self.assertEqual(message["description"], "Ubuntu 6.06.1 LTS")
292+- self.assertEqual(message["release"], "6.06")
293+- self.assertEqual(message["code-name"], "dapper")
294++ self.assertEqual(message["description"], "Ubuntu 22.04.3 LTS")
295++ self.assertEqual(message["release"], "22.04")
296++ self.assertEqual(message["code-name"], "codename")
297+
298+ def test_distribution_reported_only_once(self):
299+ """
300+@@ -249,23 +261,25 @@ VmallocChunk: 107432 kB
301+ the server.
302+ """
303+ self.mstore.set_accepted_types(["distribution-info"])
304+- plugin = ComputerInfo(lsb_release_filename=self.lsb_release_filename)
305++ plugin = ComputerInfo(os_release_filename=self.os_release_filename)
306+ self.monitor.add(plugin)
307+
308+ plugin.exchange()
309+ message = self.mstore.get_pending_messages()[0]
310+ self.assertEqual(message["type"], "distribution-info")
311+ self.assertEqual(message["distributor-id"], "Ubuntu")
312+- self.assertEqual(message["description"], "Ubuntu 6.06.1 LTS")
313+- self.assertEqual(message["release"], "6.06")
314+- self.assertEqual(message["code-name"], "dapper")
315+-
316+- plugin._lsb_release_filename = self.makeFile("""\
317+-DISTRIB_ID=Ubuntu
318+-DISTRIB_RELEASE=6.10
319+-DISTRIB_CODENAME=edgy
320+-DISTRIB_DESCRIPTION="Ubuntu 6.10"
321+-""")
322++ self.assertEqual(message["description"], "Ubuntu 22.04.3 LTS")
323++ self.assertEqual(message["release"], "22.04")
324++ self.assertEqual(message["code-name"], "codename")
325++
326++ plugin._os_release_filename = self.makeFile(
327++ """\
328++NAME=Ubuntu
329++VERSION_ID=6.10
330++VERSION_CODENAME=edgy
331++PRETTY_NAME="Ubuntu 6.10"
332++""",
333++ )
334+ plugin.exchange()
335+ message = self.mstore.get_pending_messages()[1]
336+ self.assertEqual(message["type"], "distribution-info")
337+@@ -276,23 +290,25 @@ DISTRIB_DESCRIPTION="Ubuntu 6.10"
338+
339+ def test_unknown_distribution_key(self):
340+ self.mstore.set_accepted_types(["distribution-info"])
341+- lsb_release_filename = self.makeFile("""\
342+-DISTRIB_ID=Ubuntu
343+-DISTRIB_RELEASE=6.10
344+-DISTRIB_CODENAME=edgy
345+-DISTRIB_DESCRIPTION="Ubuntu 6.10"
346++ os_release_filename = self.makeFile(
347++ """\
348++NAME=Ubuntu
349++VERSION_ID=22.04
350++VERSION_CODENAME=codename
351++PRETTY_NAME="Ubuntu 22.04.3 LTS"
352+ DISTRIB_NEW_UNEXPECTED_KEY=ooga
353+-""")
354+- plugin = ComputerInfo(lsb_release_filename=lsb_release_filename)
355++""",
356++ )
357++ plugin = ComputerInfo(os_release_filename=os_release_filename)
358+ self.monitor.add(plugin)
359+
360+ plugin.exchange()
361+ message = self.mstore.get_pending_messages()[0]
362+ self.assertEqual(message["type"], "distribution-info")
363+ self.assertEqual(message["distributor-id"], "Ubuntu")
364+- self.assertEqual(message["description"], "Ubuntu 6.10")
365+- self.assertEqual(message["release"], "6.10")
366+- self.assertEqual(message["code-name"], "edgy")
367++ self.assertEqual(message["description"], "Ubuntu 22.04.3 LTS")
368++ self.assertEqual(message["release"], "22.04")
369++ self.assertEqual(message["code-name"], "codename")
370+
371+ def test_resynchronize(self):
372+ """
373+@@ -301,11 +317,13 @@ DISTRIB_NEW_UNEXPECTED_KEY=ooga
374+ """
375+ self.mstore.set_accepted_types(["distribution-info", "computer-info"])
376+ meminfo_filename = self.makeFile(self.sample_memory_info)
377+- plugin = ComputerInfo(get_fqdn=get_fqdn,
378+- meminfo_filename=meminfo_filename,
379+- lsb_release_filename=self.lsb_release_filename,
380+- root_path=self.makeDir(),
381+- fetch_async=self.fetch_func)
382++ plugin = ComputerInfo(
383++ get_fqdn=get_fqdn,
384++ meminfo_filename=meminfo_filename,
385++ os_release_filename=self.os_release_filename,
386++ root_path=self.makeDir(),
387++ fetch_async=self.fetch_func,
388++ )
389+ self.monitor.add(plugin)
390+ plugin.exchange()
391+ self.reactor.fire("resynchronize", scopes=["computer"])
392+@@ -314,12 +332,17 @@ DISTRIB_NEW_UNEXPECTED_KEY=ooga
393+ "timestamp": 0, "total-memory": 1510,
394+ "total-swap": 1584}
395+
396+- dist_info = {"type": "distribution-info",
397+- "code-name": "dapper", "description": "Ubuntu 6.06.1 LTS",
398+- "distributor-id": "Ubuntu", "release": "6.06"}
399+- self.assertMessages(self.mstore.get_pending_messages(),
400+- [computer_info, dist_info,
401+- computer_info, dist_info])
402++ dist_info = {
403++ "type": "distribution-info",
404++ "code-name": "codename",
405++ "description": "Ubuntu 22.04.3 LTS",
406++ "distributor-id": "Ubuntu",
407++ "release": "22.04",
408++ }
409++ self.assertMessages(
410++ self.mstore.get_pending_messages(),
411++ [computer_info, dist_info, computer_info, dist_info],
412++ )
413+
414+ def test_computer_info_call_on_accepted(self):
415+ plugin = ComputerInfo(fetch_async=self.fetch_func)
416+Index: landscape-client/landscape/client/package/releaseupgrader.py
417+===================================================================
418+--- landscape-client.orig/landscape/client/package/releaseupgrader.py
419++++ landscape-client/landscape/client/package/releaseupgrader.py
420+@@ -8,16 +8,22 @@ import tarfile
421+
422+ from twisted.internet.defer import succeed
423+
424++from landscape.client.manager.manager import FAILED
425++from landscape.client.manager.manager import SUCCEEDED
426++from landscape.client.package.reporter import find_reporter_command
427++from landscape.client.package.taskhandler import PackageTaskHandler
428++from landscape.client.package.taskhandler import (
429++ PackageTaskHandlerConfiguration,
430++)
431++from landscape.client.package.taskhandler import run_task_handler
432+ from landscape.lib.config import get_bindir
433+-from landscape.lib.fetch import url_to_filename, fetch_to_files
434+-from landscape.lib.lsb_release import parse_lsb_release, LSB_RELEASE_FILENAME
435+-from landscape.lib.gpg import gpg_verify
436++from landscape.lib.fetch import fetch_to_files
437++from landscape.lib.fetch import url_to_filename
438+ from landscape.lib.fs import read_text_file
439+-from landscape.client.package.taskhandler import (
440+- PackageTaskHandlerConfiguration, PackageTaskHandler, run_task_handler)
441++from landscape.lib.gpg import gpg_verify
442++from landscape.lib.os_release import OS_RELEASE_FILENAME
443++from landscape.lib.os_release import parse_os_release
444+ from landscape.lib.twisted_util import spawn_process
445+-from landscape.client.manager.manager import SUCCEEDED, FAILED
446+-from landscape.client.package.reporter import find_reporter_command
447+
448+
449+ class ReleaseUpgraderConfiguration(PackageTaskHandlerConfiguration):
450+@@ -37,7 +43,7 @@ class ReleaseUpgrader(PackageTaskHandler
451+ @cvar config_factory: The configuration class to use to build configuration
452+ objects to be passed to our constructor.
453+ @cvar queue_name: The queue we pick tasks from.
454+- @cvar lsb_release_filename: The path to the LSB data on the file system.
455++ @cvar os_release_filename: The path to the OS data on the file system.
456+ @cvar landscape_ppa_url: The URL of the Landscape PPA, if it is present
457+ in the computer's sources.list it won't be commented out.
458+ @cvar logs_directory: Path to the directory holding the upgrade-tool logs.
459+@@ -47,7 +53,7 @@ class ReleaseUpgrader(PackageTaskHandler
460+
461+ config_factory = ReleaseUpgraderConfiguration
462+ queue_name = "release-upgrader"
463+- lsb_release_filename = LSB_RELEASE_FILENAME
464++ os_release_filename = OS_RELEASE_FILENAME
465+ landscape_ppa_url = "http://ppa.launchpad.net/landscape/trunk/ubuntu/"
466+ logs_directory = "/var/log/dist-upgrade"
467+ logs_limit = 100000 # characters
468+@@ -73,8 +79,8 @@ class ReleaseUpgrader(PackageTaskHandler
469+ """
470+ target_code_name = message["code-name"]
471+ operation_id = message["operation-id"]
472+- lsb_release_info = parse_lsb_release(self.lsb_release_filename)
473+- current_code_name = lsb_release_info["code-name"]
474++ os_release_info = parse_os_release(self.os_release_filename)
475++ current_code_name = os_release_info["code-name"]
476+
477+ if target_code_name == current_code_name:
478+ message = self.make_operation_result_message(
479+Index: landscape-client/landscape/client/package/reporter.py
480+===================================================================
481+--- landscape-client.orig/landscape/client/package/reporter.py
482++++ landscape-client/landscape/client/package/reporter.py
483+@@ -11,19 +11,28 @@ import apt_pkg
484+ import re
485+
486+ from twisted.internet.defer import (
487+- Deferred, succeed, inlineCallbacks, returnValue)
488++ Deferred,
489++ succeed,
490++ inlineCallbacks,
491++ returnValue,
492++)
493+
494+ from landscape.lib import bpickle
495+ from landscape.lib.apt.package.store import (
496+- UnknownHashIDRequest, FakePackageStore)
497++ UnknownHashIDRequest,
498++ FakePackageStore,
499++)
500+ from landscape.lib.config import get_bindir
501+ from landscape.lib.sequenceranges import sequence_to_ranges
502+ from landscape.lib.twisted_util import gather_results, spawn_process
503+ from landscape.lib.fetch import fetch_async
504+ from landscape.lib.fs import touch_file, create_binary_file
505+-from landscape.lib.lsb_release import parse_lsb_release, LSB_RELEASE_FILENAME
506++from landscape.lib.os_release import parse_os_release
507+ from landscape.client.package.taskhandler import (
508+- PackageTaskHandlerConfiguration, PackageTaskHandler, run_task_handler)
509++ PackageTaskHandlerConfiguration,
510++ PackageTaskHandler,
511++ run_task_handler,
512++)
513+
514+
515+ HASH_ID_REQUEST_TIMEOUT = 7200
516+@@ -116,7 +125,6 @@ class PackageReporter(PackageTaskHandler
517+ """
518+
519+ def fetch_it(hash_id_db_filename):
520+-
521+ if hash_id_db_filename is None:
522+ # Couldn't determine which hash=>id database to fetch,
523+ # just ignore the failure and go on
524+@@ -161,11 +169,9 @@ class PackageReporter(PackageTaskHandler
525+ return result
526+
527+ def _get_hash_id_db_base_url(self):
528+-
529+ base_url = self._config.get("package_hash_id_url")
530+
531+ if not base_url:
532+-
533+ if not self._config.get("url"):
534+ # We really have no idea where to download from
535+ return None
536+@@ -264,12 +270,13 @@ class PackageReporter(PackageTaskHandler
537+
538+ @return: a deferred returning (out, err, code)
539+ """
540+- if (self._config.force_apt_update or
541+- self._apt_sources_have_changed() or
542+- self._apt_update_timeout_expired(self._config.apt_update_interval)
543+- ) and \
544+- not self._is_release_upgrader_running():
545+-
546++ if (
547++ self._config.force_apt_update
548++ or self._apt_sources_have_changed()
549++ or self._apt_update_timeout_expired(
550++ self._config.apt_update_interval,
551++ )
552++ ) and not self._is_release_upgrader_running():
553+ accepted_apt_errors = (
554+ "Problem renaming the file /var/cache/apt/srcpkgcache.bin",
555+ "Problem renaming the file /var/cache/apt/pkgcache.bin")
556+@@ -412,7 +419,6 @@ class PackageReporter(PackageTaskHandler
557+ self._store.clear_autoremovable()
558+
559+ def _handle_unknown_packages(self, hashes):
560+-
561+ self._facade.ensure_channels_reloaded()
562+
563+ hashes = set(hashes)
564+@@ -633,9 +639,9 @@ class PackageReporter(PackageTaskHandler
565+ current_locked = set()
566+ current_autoremovable = set()
567+ current_security = set()
568+- lsb = parse_lsb_release(LSB_RELEASE_FILENAME)
569+- backports_archive = "{}-backports".format(lsb["code-name"])
570+- security_archive = "{}-security".format(lsb["code-name"])
571++ os_release_info = parse_os_release()
572++ backports_archive = "{}-backports".format(os_release_info["code-name"])
573++ security_archive = "{}-security".format(os_release_info["code-name"])
574+
575+ for package_version in self._facade.get_packages():
576+ # Get archives from the list of PackageFiles
577+Index: landscape-client/landscape/client/package/taskhandler.py
578+===================================================================
579+--- landscape-client.orig/landscape/client/package/taskhandler.py
580++++ landscape-client/landscape/client/package/taskhandler.py
581+@@ -1,16 +1,22 @@
582++import logging
583+ import os
584+ import re
585+-import logging
586+
587+-from twisted.internet.defer import succeed, Deferred, maybeDeferred
588++from twisted.internet.defer import Deferred
589++from twisted.internet.defer import maybeDeferred
590++from twisted.internet.defer import succeed
591+
592+-from landscape.lib.apt.package.store import PackageStore, InvalidHashIdDb
593+-from landscape.lib.lock import lock_path, LockError
594+-from landscape.lib.log import log_failure
595+-from landscape.lib.lsb_release import LSB_RELEASE_FILENAME, parse_lsb_release
596+-from landscape.client.reactor import LandscapeReactor
597+-from landscape.client.deployment import Configuration, init_logging
598+ from landscape.client.broker.amp import RemoteBrokerConnector
599++from landscape.client.deployment import Configuration
600++from landscape.client.deployment import init_logging
601++from landscape.client.reactor import LandscapeReactor
602++from landscape.lib.apt.package.store import InvalidHashIdDb
603++from landscape.lib.apt.package.store import PackageStore
604++from landscape.lib.lock import lock_path
605++from landscape.lib.lock import LockError
606++from landscape.lib.log import log_failure
607++from landscape.lib.os_release import OS_RELEASE_FILENAME
608++from landscape.lib.os_release import parse_os_release
609+
610+
611+ class PackageTaskError(Exception):
612+@@ -69,7 +75,6 @@ class LazyRemoteBroker(object):
613+ self._remote = None
614+
615+ def __getattr__(self, method):
616+-
617+ if self._remote:
618+ return getattr(self._remote, method)
619+
620+@@ -85,12 +90,11 @@ class LazyRemoteBroker(object):
621+ return wrapper
622+
623+
624+-class PackageTaskHandler(object):
625+-
626++class PackageTaskHandler:
627+ config_factory = PackageTaskHandlerConfiguration
628+
629+ queue_name = "default"
630+- lsb_release_filename = LSB_RELEASE_FILENAME
631++ os_release_filename = OS_RELEASE_FILENAME
632+ package_store_class = PackageStore
633+
634+ # This file is touched after every succesful 'apt-get update' run if the
635+@@ -173,7 +177,6 @@ class PackageTaskHandler(object):
636+ """
637+
638+ def use_it(hash_id_db_filename):
639+-
640+ if hash_id_db_filename is None:
641+ # Couldn't determine which hash=>id database to use,
642+ # just ignore the failure and go on
643+@@ -206,7 +209,6 @@ class PackageTaskHandler(object):
644+ """
645+
646+ def got_server_uuid(server_uuid):
647+-
648+ warning = "Couldn't determine which hash=>id database to use: %s"
649+
650+ if server_uuid is None:
651+@@ -214,15 +216,17 @@ class PackageTaskHandler(object):
652+ return None
653+
654+ try:
655+- lsb_release_info = parse_lsb_release(self.lsb_release_filename)
656+- except IOError as error:
657++ os_release_info = parse_os_release(self.os_release_filename)
658++ except OSError as error:
659+ logging.warning(warning % str(error))
660+ return None
661+ try:
662+- codename = lsb_release_info["code-name"]
663++ codename = os_release_info["code-name"]
664+ except KeyError:
665+- logging.warning(warning % "missing code-name key in %s" %
666+- self.lsb_release_filename)
667++ logging.warning(
668++ warning
669++ % f"missing code-name key in {self.os_release_filename}",
670++ )
671+ return None
672+
673+ arch = self._facade.get_arch()
674+Index: landscape-client/landscape/client/package/tests/test_releaseupgrader.py
675+===================================================================
676+--- landscape-client.orig/landscape/client/package/tests/test_releaseupgrader.py
677++++ landscape-client/landscape/client/package/tests/test_releaseupgrader.py
678+@@ -1,20 +1,28 @@
679+-import mock
680+ import os
681+ import signal
682+ import tarfile
683+ import unittest
684++from unittest import mock
685+
686+ from twisted.internet import reactor
687+-from twisted.internet.defer import succeed, fail, Deferred
688+-
689++from twisted.internet.defer import Deferred
690++from twisted.internet.defer import fail
691++from twisted.internet.defer import succeed
692++
693++from landscape.client.manager.manager import FAILED
694++from landscape.client.manager.manager import SUCCEEDED
695++from landscape.client.package.releaseupgrader import main
696++from landscape.client.package.releaseupgrader import ReleaseUpgrader
697++from landscape.client.package.releaseupgrader import (
698++ ReleaseUpgraderConfiguration,
699++)
700++from landscape.client.tests.helpers import BrokerServiceHelper
701++from landscape.client.tests.helpers import LandscapeTest
702+ from landscape.lib.apt.package.store import PackageStore
703+-from landscape.lib.gpg import InvalidGPGSignature
704+ from landscape.lib.fetch import HTTPCodeError
705+-from landscape.lib.testing import LogKeeperHelper, EnvironSaverHelper
706+-from landscape.client.package.releaseupgrader import (
707+- ReleaseUpgrader, ReleaseUpgraderConfiguration, main)
708+-from landscape.client.tests.helpers import LandscapeTest, BrokerServiceHelper
709+-from landscape.client.manager.manager import SUCCEEDED, FAILED
710++from landscape.lib.gpg import InvalidGPGSignature
711++from landscape.lib.testing import EnvironSaverHelper
712++from landscape.lib.testing import LogKeeperHelper
713+
714+
715+ class ReleaseUpgraderConfigurationTest(unittest.TestCase):
716+@@ -31,9 +39,7 @@ class ReleaseUpgraderConfigurationTest(u
717+
718+
719+ class ReleaseUpgraderTest(LandscapeTest):
720+-
721+- helpers = [LogKeeperHelper,
722+- EnvironSaverHelper, BrokerServiceHelper]
723++ helpers = [LogKeeperHelper, EnvironSaverHelper, BrokerServiceHelper]
724+
725+ def setUp(self):
726+ super(ReleaseUpgraderTest, self).setUp()
727+@@ -307,7 +313,6 @@ class ReleaseUpgraderTest(LandscapeTest)
728+ deferred = Deferred()
729+
730+ def do_test():
731+-
732+ result = self.upgrader.upgrade("karmic", 100)
733+
734+ def check_result(ignored):
735+@@ -356,10 +361,12 @@ class ReleaseUpgraderTest(LandscapeTest)
736+ deferred = Deferred()
737+
738+ def do_test():
739+-
740+- result = self.upgrader.upgrade("karmic", 100,
741+- allow_third_party=True,
742+- debug=True)
743++ result = self.upgrader.upgrade(
744++ "karmic",
745++ 100,
746++ allow_third_party=True,
747++ debug=True,
748++ )
749+
750+ def check_result(ignored):
751+ result_text = (u"=== Standard output ===\n\n"
752+@@ -402,7 +409,6 @@ class ReleaseUpgraderTest(LandscapeTest)
753+ deferred = Deferred()
754+
755+ def do_test():
756+-
757+ result = self.upgrader.upgrade("karmic", 100)
758+
759+ def check_result(ignored):
760+@@ -458,7 +464,6 @@ class ReleaseUpgraderTest(LandscapeTest)
761+ deferred = Deferred()
762+
763+ def do_test():
764+-
765+ result = self.upgrader.upgrade("karmic", 100)
766+
767+ def kill_child(how):
768+@@ -650,8 +655,9 @@ class ReleaseUpgraderTest(LandscapeTest)
769+ self.upgrader.upgrade = upgrade
770+ self.upgrader.finish = finish
771+
772+- self.upgrader.lsb_release_filename = self.makeFile(
773+- "DISTRIB_CODENAME=jaunty\n")
774++ self.upgrader.os_release_filename = self.makeFile(
775++ "VERSION_CODENAME=jaunty\n",
776++ )
777+
778+ message = {"type": "release-upgrade",
779+ "code-name": "karmic",
780+@@ -674,8 +680,9 @@ class ReleaseUpgraderTest(LandscapeTest)
781+ The L{ReleaseUpgrader.handle_release_upgrade} method reports a
782+ failure if the system is already running the desired release.
783+ """
784+- self.upgrader.lsb_release_filename = self.makeFile(
785+- "DISTRIB_CODENAME=karmic\n")
786++ self.upgrader.os_release_filename = self.makeFile(
787++ "VERSION_CODENAME=karmic\n",
788++ )
789+
790+ message = {"type": "release-upgrade",
791+ "code-name": "karmic",
792+@@ -703,8 +710,9 @@ class ReleaseUpgraderTest(LandscapeTest)
793+ The L{ReleaseUpgrader.handle_release_upgrade} method reports a
794+ failure if any of the helper method errbacks.
795+ """
796+- self.upgrader.lsb_release_filename = self.makeFile(
797+- "DISTRIB_CODENAME=jaunty\n")
798++ self.upgrader.os_release_filename = self.makeFile(
799++ "VERSION_CODENAME=jaunty\n",
800++ )
801+
802+ calls = []
803+
804+Index: landscape-client/landscape/client/package/tests/test_reporter.py
805+===================================================================
806+--- landscape-client.orig/landscape/client/package/tests/test_reporter.py
807++++ landscape-client/landscape/client/package/tests/test_reporter.py
808+@@ -46,7 +46,6 @@ class PackageReporterConfigurationTest(L
809+
810+
811+ class PackageReporterAptTest(LandscapeTest):
812+-
813+ helpers = [AptFacadeHelper, SimpleRepositoryHelper, BrokerServiceHelper]
814+
815+ Facade = AptFacade
816+@@ -124,10 +123,10 @@ class PackageReporterAptTest(LandscapeTe
817+ return deferred.addCallback(got_result)
818+
819+ def test_set_package_ids_with_unknown_request_id(self):
820+-
821+- self.store.add_task("reporter",
822+- {"type": "package-ids", "ids": [123, 456],
823+- "request-id": 123})
824++ self.store.add_task(
825++ "reporter",
826++ {"type": "package-ids", "ids": [123, 456], "request-id": 123},
827++ )
828+
829+ # Nothing bad should happen.
830+ return self.reporter.handle_tasks()
831+@@ -299,7 +298,6 @@ class PackageReporterAptTest(LandscapeTe
832+ return_value=succeed(b"hash-ids"))
833+ @mock.patch("logging.info", return_value=None)
834+ def test_fetch_hash_id_db(self, logging_mock, mock_fetch_async):
835+-
836+ # Assume package_hash_id_url is set
837+ self.config.data_path = self.makeDir()
838+ self.config.package_hash_id_url = "http://fake.url/path/"
839+@@ -310,7 +308,7 @@ class PackageReporterAptTest(LandscapeTe
840+ # Fake uuid, codename and arch
841+ message_store = self.broker_service.message_store
842+ message_store.set_server_uuid("uuid")
843+- self.reporter.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
844++ self.reporter.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
845+ self.facade.set_arch("arch")
846+
847+ # Let's say fetch_async is successful
848+@@ -346,7 +344,7 @@ class PackageReporterAptTest(LandscapeTe
849+ # Fake uuid, codename and arch
850+ message_store = self.broker_service.message_store
851+ message_store.set_server_uuid("uuid")
852+- self.reporter.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
853++ self.reporter.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
854+ self.facade.set_arch("arch")
855+
856+ # Let's say fetch_async is successful
857+@@ -362,7 +360,6 @@ class PackageReporterAptTest(LandscapeTe
858+
859+ @mock.patch("landscape.client.package.reporter.fetch_async")
860+ def test_fetch_hash_id_db_does_not_download_twice(self, mock_fetch_async):
861+-
862+ # Let's say that the hash=>id database is already there
863+ self.config.package_hash_id_url = "http://fake.url/path/"
864+ self.config.data_path = self.makeDir()
865+@@ -374,7 +371,7 @@ class PackageReporterAptTest(LandscapeTe
866+ # Fake uuid, codename and arch
867+ message_store = self.broker_service.message_store
868+ message_store.set_server_uuid("uuid")
869+- self.reporter.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
870++ self.reporter.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
871+ self.facade.set_arch("arch")
872+
873+ result = self.reporter.fetch_hash_id_db()
874+@@ -407,28 +404,27 @@ class PackageReporterAptTest(LandscapeTe
875+
876+ @mock.patch("logging.warning", return_value=None)
877+ def test_fetch_hash_id_db_undetermined_codename(self, logging_mock):
878+-
879+ # Fake uuid
880+ message_store = self.broker_service.message_store
881+ message_store.set_server_uuid("uuid")
882+
883+ # Undetermined codename
884+- self.reporter.lsb_release_filename = self.makeFile("Foo=bar")
885++ self.reporter.os_release_filename = self.makeFile("Foo=bar")
886+
887+ result = self.reporter.fetch_hash_id_db()
888+
889+ logging_mock.assert_called_once_with(
890+ "Couldn't determine which hash=>id database to use: "
891+- "missing code-name key in %s" % self.reporter.lsb_release_filename)
892++ f"missing code-name key in {self.reporter.os_release_filename}",
893++ )
894+ return result
895+
896+ @mock.patch("logging.warning", return_value=None)
897+ def test_fetch_hash_id_db_undetermined_arch(self, logging_mock):
898+-
899+ # Fake uuid and codename
900+ message_store = self.broker_service.message_store
901+ message_store.set_server_uuid("uuid")
902+- self.reporter.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
903++ self.reporter.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
904+
905+ # Undetermined arch
906+ self.facade.set_arch("")
907+@@ -453,7 +449,7 @@ class PackageReporterAptTest(LandscapeTe
908+ # Fake uuid, codename and arch
909+ message_store = self.broker_service.message_store
910+ message_store.set_server_uuid("uuid")
911+- self.reporter.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
912++ self.reporter.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
913+ self.facade.set_arch("arch")
914+
915+ # Check fetch_async is called with the default url
916+@@ -474,8 +470,10 @@ class PackageReporterAptTest(LandscapeTe
917+ return_value=fail(FetchError("fetch error")))
918+ @mock.patch("logging.warning", return_value=None)
919+ def test_fetch_hash_id_db_with_download_error(
920+- self, logging_mock, mock_fetch_async):
921+-
922++ self,
923++ logging_mock,
924++ mock_fetch_async,
925++ ):
926+ # Assume package_hash_id_url is set
927+ self.config.data_path = self.makeDir()
928+ self.config.package_hash_id_url = "http://fake.url/path/"
929+@@ -483,7 +481,7 @@ class PackageReporterAptTest(LandscapeTe
930+ # Fake uuid, codename and arch
931+ message_store = self.broker_service.message_store
932+ message_store.set_server_uuid("uuid")
933+- self.reporter.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
934++ self.reporter.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
935+ self.facade.set_arch("arch")
936+
937+ # Let's say fetch_async fails
938+@@ -507,7 +505,6 @@ class PackageReporterAptTest(LandscapeTe
939+
940+ @mock.patch("logging.warning", return_value=None)
941+ def test_fetch_hash_id_db_with_undetermined_url(self, logging_mock):
942+-
943+ # We don't know where to fetch the hash=>id database from
944+ self.config.url = None
945+ self.config.package_hash_id_url = None
946+@@ -515,7 +512,7 @@ class PackageReporterAptTest(LandscapeTe
947+ # Fake uuid, codename and arch
948+ message_store = self.broker_service.message_store
949+ message_store.set_server_uuid("uuid")
950+- self.reporter.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
951++ self.reporter.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
952+ self.facade.set_arch("arch")
953+
954+ result = self.reporter.fetch_hash_id_db()
955+@@ -546,7 +543,7 @@ class PackageReporterAptTest(LandscapeTe
956+ # Fake uuid, codename and arch
957+ message_store = self.broker_service.message_store
958+ message_store.set_server_uuid("uuid")
959+- self.reporter.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
960++ self.reporter.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
961+ self.facade.set_arch("arch")
962+
963+ # Check fetch_async is called with the default url
964+@@ -1004,10 +1001,12 @@ class PackageReporterAptTest(LandscapeTe
965+ """Packages versions coming from security are reported as such."""
966+ message_store = self.broker_service.message_store
967+ message_store.set_accepted_types(["packages"])
968+- lsb = parse_lsb_release(LSB_RELEASE_FILENAME)
969++ os_release_info = parse_os_release(OS_RELEASE_FILENAME)
970+ release_path = os.path.join(self.repository_dir, "Release")
971+ with open(release_path, "w") as release:
972+- release.write("Suite: {}-security".format(lsb["code-name"]))
973++ release.write(
974++ "Suite: {}-security".format(os_release_info["code-name"]),
975++ )
976+
977+ self.store.set_hash_ids({HASH1: 1, HASH2: 2, HASH3: 3})
978+
979+@@ -1051,10 +1050,12 @@ class PackageReporterAptTest(LandscapeTe
980+ message_store = self.broker_service.message_store
981+ message_store.set_accepted_types(["packages"])
982+
983+- lsb = parse_lsb_release(LSB_RELEASE_FILENAME)
984++ os_release_info = parse_os_release(OS_RELEASE_FILENAME)
985+ release_path = os.path.join(self.repository_dir, "Release")
986+ with open(release_path, "w") as release:
987+- release.write("Suite: {}-backports".format(lsb["code-name"]))
988++ release.write(
989++ "Suite: {}-backports".format(os_release_info["code-name"]),
990++ )
991+
992+ self.store.set_hash_ids({HASH1: 1, HASH2: 2, HASH3: 3})
993+
994+@@ -1125,10 +1126,12 @@ class PackageReporterAptTest(LandscapeTe
995+ os.remove(os.path.join(other_backport_dir, "Packages"))
996+ self.facade.add_channel_deb_dir(other_backport_dir)
997+
998+- lsb = parse_lsb_release(LSB_RELEASE_FILENAME)
999++ os_release_info = parse_os_release(OS_RELEASE_FILENAME)
1000+ official_release_path = os.path.join(self.repository_dir, "Release")
1001+ with open(official_release_path, "w") as release:
1002+- release.write("Suite: {}-backports".format(lsb["code-name"]))
1003++ release.write(
1004++ "Suite: {}-backports".format(os_release_info["code-name"]),
1005++ )
1006+ unofficial_release_path = os.path.join(other_backport_dir, "Release")
1007+ with open(unofficial_release_path, "w") as release:
1008+ release.write("Suite: my-personal-backports")
1009+@@ -1315,8 +1318,8 @@ class PackageReporterAptTest(LandscapeTe
1010+ deferred = self.reporter.run()
1011+ self.reactor.advance(0)
1012+ with mock.patch(
1013+- "landscape.client.package.taskhandler.parse_lsb_release",
1014+- side_effect=lambda _: {"code-name": "codename"}
1015++ "landscape.client.package.taskhandler.parse_os_release",
1016++ side_effect=lambda _: {"code-name": "codename"},
1017+ ):
1018+ yield deferred
1019+
1020+@@ -1999,7 +2002,6 @@ class PackageReporterAptTest(LandscapeTe
1021+
1022+
1023+ class GlobalPackageReporterAptTest(LandscapeTest):
1024+-
1025+ helpers = [AptFacadeHelper, SimpleRepositoryHelper, BrokerServiceHelper]
1026+
1027+ def setUp(self):
1028+@@ -2049,7 +2051,6 @@ class GlobalPackageReporterAptTest(Lands
1029+
1030+
1031+ class FakePackageReporterTest(LandscapeTest):
1032+-
1033+ helpers = [EnvironSaverHelper, BrokerServiceHelper]
1034+
1035+ def setUp(self):
1036+Index: landscape-client/landscape/client/package/tests/test_taskhandler.py
1037+===================================================================
1038+--- landscape-client.orig/landscape/client/package/tests/test_taskhandler.py
1039++++ landscape-client/landscape/client/package/tests/test_taskhandler.py
1040+@@ -1,23 +1,31 @@
1041+ import os
1042+-from subprocess import CalledProcessError
1043+-
1044+-from mock import patch, Mock, ANY
1045+-
1046+-from twisted.internet.defer import Deferred, fail, succeed
1047++from unittest.mock import ANY
1048++from unittest.mock import Mock
1049++from unittest.mock import patch
1050++
1051++from twisted.internet.defer import Deferred
1052++from twisted.internet.defer import fail
1053++from twisted.internet.defer import succeed
1054+
1055++from landscape.client.broker.amp import RemoteBrokerConnector
1056++from landscape.client.package.taskhandler import LazyRemoteBroker
1057++from landscape.client.package.taskhandler import PackageTaskHandler
1058++from landscape.client.package.taskhandler import (
1059++ PackageTaskHandlerConfiguration,
1060++)
1061++from landscape.client.package.taskhandler import run_task_handler
1062++from landscape.client.tests.helpers import BrokerServiceHelper
1063++from landscape.client.tests.helpers import LandscapeTest
1064+ from landscape.lib.apt.package.facade import AptFacade
1065+-from landscape.lib.apt.package.store import HashIdStore, PackageStore
1066++from landscape.lib.apt.package.store import HashIdStore
1067++from landscape.lib.apt.package.store import PackageStore
1068+ from landscape.lib.apt.package.testing import AptFacadeHelper
1069+ from landscape.lib.lock import lock_path
1070+-from landscape.lib.testing import EnvironSaverHelper, FakeReactor
1071+-from landscape.client.broker.amp import RemoteBrokerConnector
1072+-from landscape.client.package.taskhandler import (
1073+- PackageTaskHandlerConfiguration, PackageTaskHandler, run_task_handler,
1074+- LazyRemoteBroker)
1075+-from landscape.client.tests.helpers import LandscapeTest, BrokerServiceHelper
1076++from landscape.lib.testing import EnvironSaverHelper
1077++from landscape.lib.testing import FakeReactor
1078+
1079+
1080+-SAMPLE_LSB_RELEASE = "DISTRIB_CODENAME=codename\n"
1081++SAMPLE_OS_RELEASE = "VERSION_CODENAME=codename\n"
1082+
1083+
1084+ class PackageTaskHandlerConfigurationTest(LandscapeTest):
1085+@@ -34,7 +42,6 @@ class PackageTaskHandlerConfigurationTes
1086+
1087+
1088+ class PackageTaskHandlerTest(LandscapeTest):
1089+-
1090+ helpers = [AptFacadeHelper, EnvironSaverHelper, BrokerServiceHelper]
1091+
1092+ def setUp(self):
1093+@@ -46,7 +53,6 @@ class PackageTaskHandlerTest(LandscapeTe
1094+ self.store, self.facade, self.remote, self.config, self.reactor)
1095+
1096+ def test_use_hash_id_db(self):
1097+-
1098+ # We don't have this hash=>id mapping
1099+ self.assertEqual(self.store.get_hash_id(b"hash"), None)
1100+
1101+@@ -60,7 +66,7 @@ class PackageTaskHandlerTest(LandscapeTe
1102+ # Fake uuid, codename and arch
1103+ message_store = self.broker_service.message_store
1104+ message_store.set_server_uuid("uuid")
1105+- self.handler.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
1106++ self.handler.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
1107+ self.facade.set_arch("arch")
1108+
1109+ # Attach the hash=>id database to our store
1110+@@ -75,13 +81,12 @@ class PackageTaskHandlerTest(LandscapeTe
1111+
1112+ @patch("logging.warning")
1113+ def test_use_hash_id_db_undetermined_codename(self, logging_mock):
1114+-
1115+ # Fake uuid
1116+ message_store = self.broker_service.message_store
1117+ message_store.set_server_uuid("uuid")
1118+
1119+ # Undetermined codename
1120+- self.handler.lsb_release_filename = self.makeFile("Foo=bar")
1121++ self.handler.os_release_filename = self.makeFile("Foo=bar")
1122+
1123+ # Go!
1124+ result = self.handler.use_hash_id_db()
1125+@@ -89,30 +94,29 @@ class PackageTaskHandlerTest(LandscapeTe
1126+ # The failure should be properly logged
1127+ logging_mock.assert_called_with(
1128+ "Couldn't determine which hash=>id database to use: "
1129+- "missing code-name key in %s" % self.handler.lsb_release_filename)
1130++ f"missing code-name key in {self.handler.os_release_filename}",
1131++ )
1132+
1133+ return result
1134+
1135+ @patch("logging.warning")
1136+- def test_use_hash_id_db_wit_non_existing_lsb_release(self, logging_mock):
1137+-
1138++ def test_use_hash_id_db_with_non_existing_os_release(self, logging_mock):
1139+ # Fake uuid
1140+ message_store = self.broker_service.message_store
1141+ message_store.set_server_uuid("uuid")
1142+
1143+- # Undetermined codename
1144+- self.handler.lsb_release_filename = self.makeFile()
1145++ # Undetermined os release filename
1146++ self.handler.os_release_filename = ""
1147+
1148+ # Go!
1149+- with patch("landscape.lib.lsb_release.check_output") as co_mock:
1150+- co_mock.side_effect = CalledProcessError(127, "")
1151+- result = self.handler.use_hash_id_db()
1152++ result = self.handler.use_hash_id_db()
1153+
1154+ # The failure should be properly logged
1155+ logging_mock.assert_called_with(
1156+ "Couldn't determine which hash=>id database to use: "
1157+- "[Errno 2] No such file or directory: '%s'" %
1158+- self.handler.lsb_release_filename)
1159++ "[Errno 2] No such file or directory: "
1160++ f"'{self.handler.os_release_filename}'",
1161++ )
1162+
1163+ return result
1164+
1165+@@ -165,11 +169,10 @@ class PackageTaskHandlerTest(LandscapeTe
1166+
1167+ @patch("logging.warning")
1168+ def test_use_hash_id_db_undetermined_arch(self, logging_mock):
1169+-
1170+ # Fake uuid and codename
1171+ message_store = self.broker_service.message_store
1172+ message_store.set_server_uuid("uuid")
1173+- self.handler.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
1174++ self.handler.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
1175+
1176+ # Undetermined arch
1177+ self.facade.set_arch(None)
1178+@@ -185,14 +188,13 @@ class PackageTaskHandlerTest(LandscapeTe
1179+ return result
1180+
1181+ def test_use_hash_id_db_database_not_found(self):
1182+-
1183+ # Clean path, we don't have an appropriate hash=>id database
1184+ self.config.data_path = self.makeDir()
1185+
1186+ # Fake uuid, codename and arch
1187+ message_store = self.broker_service.message_store
1188+ message_store.set_server_uuid("uuid")
1189+- self.handler.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
1190++ self.handler.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
1191+ self.facade.set_arch("arch")
1192+
1193+ # Let's try
1194+@@ -207,7 +209,6 @@ class PackageTaskHandlerTest(LandscapeTe
1195+
1196+ @patch("logging.warning")
1197+ def test_use_hash_id_with_invalid_database(self, logging_mock):
1198+-
1199+ # Let's say the appropriate database is actually garbage
1200+ self.config.data_path = self.makeDir()
1201+ os.makedirs(os.path.join(self.config.data_path, "package", "hash-id"))
1202+@@ -218,7 +219,7 @@ class PackageTaskHandlerTest(LandscapeTe
1203+ # Fake uuid, codename and arch
1204+ message_store = self.broker_service.message_store
1205+ message_store.set_server_uuid("uuid")
1206+- self.handler.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
1207++ self.handler.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
1208+ self.facade.set_arch("arch")
1209+
1210+ # Try to attach it
1211+@@ -362,7 +363,6 @@ class PackageTaskHandlerTest(LandscapeTe
1212+ reactor_mock.run.side_effect = lambda: call_when_running[0]()
1213+
1214+ def assert_task_handler(ignored):
1215+-
1216+ store, facade, broker, config, reactor = handler_args
1217+
1218+ # Verify the arguments passed to the reporter constructor.
1219+@@ -405,7 +405,6 @@ class PackageTaskHandlerTest(LandscapeTe
1220+ return result.addCallback(assert_task_handler)
1221+
1222+ def test_run_task_handler_when_already_locked(self):
1223+-
1224+ lock_path(os.path.join(self.data_path, "package", "default.lock"))
1225+
1226+ try:
1227+@@ -453,7 +452,6 @@ class PackageTaskHandlerTest(LandscapeTe
1228+
1229+
1230+ class LazyRemoteBrokerTest(LandscapeTest):
1231+-
1232+ helpers = [BrokerServiceHelper]
1233+
1234+ def test_wb_is_lazy(self):
1235+Index: landscape-client/landscape/lib/lsb_release.py
1236+===================================================================
1237+--- landscape-client.orig/landscape/lib/lsb_release.py
1238++++ /dev/null
1239+@@ -1,59 +0,0 @@
1240+-"""Get information from /usr/bin/lsb_release."""
1241+-import os
1242+-from subprocess import CalledProcessError, check_output
1243+-
1244+-LSB_RELEASE = "/usr/bin/lsb_release"
1245+-LSB_RELEASE_FILENAME = "/etc/lsb_release"
1246+-LSB_RELEASE_FILE_KEYS = {
1247+- "DISTRIB_ID": "distributor-id",
1248+- "DISTRIB_DESCRIPTION": "description",
1249+- "DISTRIB_RELEASE": "release",
1250+- "DISTRIB_CODENAME": "code-name",
1251+-}
1252+-
1253+-
1254+-def parse_lsb_release(lsb_release_filename=None):
1255+- """
1256+- Returns a C{dict} holding information about the system LSB release.
1257+- Reads from C{lsb_release_filename} if it exists, else calls
1258+- C{LSB_RELEASE}
1259+- """
1260+- if lsb_release_filename and os.path.exists(lsb_release_filename):
1261+- return parse_lsb_release_file(lsb_release_filename)
1262+-
1263+- with open(os.devnull, 'w') as FNULL:
1264+- try:
1265+- lsb_info = check_output([LSB_RELEASE, "-as"], stderr=FNULL)
1266+- except (CalledProcessError, FileNotFoundError):
1267+- # Fall back to reading file, even if it doesn't exist.
1268+- return parse_lsb_release_file(lsb_release_filename)
1269+- else:
1270+- dist, desc, release, code_name, _ = lsb_info.decode().split("\n")
1271+-
1272+- return {
1273+- "distributor-id": dist,
1274+- "release": release,
1275+- "code-name": code_name,
1276+- "description": desc,
1277+- }
1278+-
1279+-
1280+-def parse_lsb_release_file(filename):
1281+- """
1282+- Returns a C{dict} holding information about the system LSB release
1283+- by attempting to parse C{filename}.
1284+-
1285+- @raises: A FileNotFoundError if C{filename} does not exist.
1286+- """
1287+- info = {}
1288+-
1289+- with open(filename) as fd:
1290+- for line in fd:
1291+- key, value = line.split("=")
1292+-
1293+- if key in LSB_RELEASE_FILE_KEYS:
1294+- key = LSB_RELEASE_FILE_KEYS[key.strip()]
1295+- value = value.strip().strip('"')
1296+- info[key] = value
1297+-
1298+- return info
1299+Index: landscape-client/landscape/lib/os_release.py
1300+===================================================================
1301+--- /dev/null
1302++++ landscape-client/landscape/lib/os_release.py
1303+@@ -0,0 +1,43 @@
1304++"""Get information from os-release."""
1305++import os
1306++
1307++OS_RELEASE_FILENAME = "/etc/os-release"
1308++OS_RELEASE_FILENAME_FALLBACK = "/usr/lib/os-release"
1309++OS_RELEASE_FILE_KEYS = {
1310++ "NAME": "distributor-id",
1311++ "PRETTY_NAME": "description",
1312++ "VERSION_ID": "release",
1313++ "VERSION_CODENAME": "code-name",
1314++}
1315++
1316++
1317++def parse_os_release(os_release_filename=None):
1318++ """
1319++ Returns a C{dict} holding information about the system LSB release
1320++ by attempting to parse C{os_release_filename} if specified. If no
1321++ filename is provided /etc/os-release will be used or
1322++ /usr/lib/os-release as a fallback as indicated in os-release
1323++ at Freedesktop.org
1324++
1325++ @raises: A FileNotFoundError if C{filename} does not exist.
1326++ """
1327++ info = {}
1328++
1329++ if os_release_filename is None:
1330++ os_release_filename = OS_RELEASE_FILENAME
1331++ if not os.path.exists(os_release_filename) or not os.access(
1332++ os_release_filename,
1333++ os.R_OK,
1334++ ):
1335++ os_release_filename = OS_RELEASE_FILENAME_FALLBACK
1336++
1337++ with open(os_release_filename) as fd:
1338++ for line in fd:
1339++ key, value = line.split("=")
1340++
1341++ if key in OS_RELEASE_FILE_KEYS:
1342++ key = OS_RELEASE_FILE_KEYS[key.strip()]
1343++ value = value.strip().strip('"')
1344++ info[key] = value
1345++
1346++ return info
1347+Index: landscape-client/landscape/lib/tests/test_lsb_release.py
1348+===================================================================
1349+--- landscape-client.orig/landscape/lib/tests/test_lsb_release.py
1350++++ /dev/null
1351+@@ -1,75 +0,0 @@
1352+-import unittest
1353+-from subprocess import CalledProcessError
1354+-from unittest import mock
1355+-
1356+-from landscape.lib import testing
1357+-from landscape.lib.lsb_release import parse_lsb_release
1358+-
1359+-
1360+-class LsbReleaseTest(testing.FSTestCase, unittest.TestCase):
1361+-
1362+- def test_parse_lsb_release(self):
1363+- with mock.patch("landscape.lib.lsb_release.check_output") as co_mock:
1364+- co_mock.return_value = (b"Ubuntu\nUbuntu 22.04.1 LTS\n22.04\njammy"
1365+- b"\n")
1366+- lsb_release = parse_lsb_release()
1367+-
1368+- self.assertEqual(lsb_release,
1369+- {"distributor-id": "Ubuntu",
1370+- "description": "Ubuntu 22.04.1 LTS",
1371+- "release": "22.04",
1372+- "code-name": "jammy"})
1373+-
1374+- def test_parse_lsb_release_debian(self):
1375+- with mock.patch("landscape.lib.lsb_release.check_output") as co_mock:
1376+- co_mock.return_value = (b"Debian\nDebian GNU/Linux 11 (bullseye)\n"
1377+- b"11\nbullseye\n")
1378+- lsb_release = parse_lsb_release()
1379+-
1380+- self.assertEqual(lsb_release,
1381+- {"distributor-id": "Debian",
1382+- "description": "Debian GNU/Linux 11 (bullseye)",
1383+- "release": "11",
1384+- "code-name": "bullseye"})
1385+-
1386+- def test_parse_lsb_release_file(self):
1387+- """
1388+- L{parse_lsb_release} returns a C{dict} holding information from
1389+- the given LSB release file.
1390+- """
1391+- lsb_release_filename = self.makeFile("DISTRIB_ID=Ubuntu\n"
1392+- "DISTRIB_RELEASE=6.06\n"
1393+- "DISTRIB_CODENAME=dapper\n"
1394+- "DISTRIB_DESCRIPTION="
1395+- "\"Ubuntu 6.06.1 LTS\"\n")
1396+-
1397+- with mock.patch("landscape.lib.lsb_release.check_output") as co_mock:
1398+- co_mock.side_effect = CalledProcessError(127, "")
1399+- lsb_release = parse_lsb_release(lsb_release_filename)
1400+-
1401+- self.assertEqual(lsb_release,
1402+- {"distributor-id": "Ubuntu",
1403+- "description": "Ubuntu 6.06.1 LTS",
1404+- "release": "6.06",
1405+- "code-name": "dapper"})
1406+-
1407+- def test_parse_lsb_release_file_with_missing_or_extra_fields(self):
1408+- """
1409+- L{parse_lsb_release} ignores lines not matching the map of
1410+- known keys, and returns only keys with an actual value.
1411+- """
1412+- lsb_release_filename = self.makeFile("DISTRIB_ID=Ubuntu\n"
1413+- "FOO=Bar\n")
1414+-
1415+- with mock.patch("landscape.lib.lsb_release.check_output") as co_mock:
1416+- co_mock.side_effect = CalledProcessError(127, "")
1417+- lsb_release = parse_lsb_release(lsb_release_filename)
1418+-
1419+- self.assertEqual(lsb_release, {"distributor-id": "Ubuntu"})
1420+-
1421+- def test_parse_lsb_release_file_not_found(self):
1422+- with mock.patch("landscape.lib.lsb_release.check_output") as co_mock:
1423+- co_mock.side_effect = CalledProcessError(127, "")
1424+-
1425+- self.assertRaises(FileNotFoundError, parse_lsb_release,
1426+- "TheresNoWayThisFileExists")
1427+Index: landscape-client/landscape/lib/tests/test_os_release.py
1428+===================================================================
1429+--- /dev/null
1430++++ landscape-client/landscape/lib/tests/test_os_release.py
1431+@@ -0,0 +1,137 @@
1432++import unittest
1433++from unittest import mock
1434++
1435++from landscape.lib import testing
1436++from landscape.lib.os_release import parse_os_release
1437++
1438++SAMPLE_OS_RELEASE = """PRETTY_NAME="Ubuntu 22.04.3 LTS"
1439++NAME="Ubuntu"
1440++VERSION_ID="22.04"
1441++VERSION="22.04.3 LTS (Jammy Jellyfish)"
1442++VERSION_CODENAME=codename
1443++ID=ubuntu
1444++ID_LIKE=debian
1445++HOME_URL="https://www.ubuntu.com/"
1446++SUPPORT_URL="https://help.ubuntu.com/"
1447++BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
1448++PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
1449++UBUNTU_CODENAME=codename
1450++"""
1451++
1452++
1453++class OsReleaseTest(testing.FSTestCase, unittest.TestCase):
1454++ def test_parse_os_release(self):
1455++ """
1456++ L{parse_os_release} ignores lines not matching the map of
1457++ known keys, and returns only keys with an actual value. By
1458++ default it reads from OS_RELEASE_FILENAME if no other path
1459++ is provided.
1460++ """
1461++
1462++ os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
1463++
1464++ with mock.patch(
1465++ "landscape.lib.os_release.OS_RELEASE_FILENAME",
1466++ new=os_release_filename,
1467++ ):
1468++ os_release = parse_os_release()
1469++
1470++ self.assertEqual(
1471++ os_release,
1472++ {
1473++ "code-name": "codename",
1474++ "description": "Ubuntu 22.04.3 LTS",
1475++ "distributor-id": "Ubuntu",
1476++ "release": "22.04",
1477++ },
1478++ )
1479++
1480++ def test_parse_os_release_no_etc(self):
1481++ """
1482++ L{parse_os_release} ignores lines not matching the map of
1483++ known keys, and returns only keys with an actual value. By
1484++ default it reads from OS_RELEASE_FILENAME if no other path
1485++ is provided and it should read from SAMPLE_OS_RELEASE_FALLBACK
1486++ path if there OS_RELEASE_FILENAME doesn't exists.
1487++ """
1488++
1489++ os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
1490++
1491++ with mock.patch("os.path.exists") as co_mock:
1492++ co_mock.return_value = False
1493++
1494++ with mock.patch(
1495++ "landscape.lib.os_release.OS_RELEASE_FILENAME_FALLBACK",
1496++ new=os_release_filename,
1497++ ):
1498++ os_release = parse_os_release()
1499++
1500++ self.assertEqual(
1501++ os_release,
1502++ {
1503++ "code-name": "codename",
1504++ "description": "Ubuntu 22.04.3 LTS",
1505++ "distributor-id": "Ubuntu",
1506++ "release": "22.04",
1507++ },
1508++ )
1509++
1510++ def test_parse_os_release_no_perms(self):
1511++ """
1512++ L{parse_os_release} ignores lines not matching the map of
1513++ known keys, and returns only keys with an actual value. By
1514++ default it reads from OS_RELEASE_FILENAME if no other path
1515++ is provided and it should read from SAMPLE_OS_RELEASE_FALLBACK
1516++ path if there OS_RELEASE_FILENAME is not readable.
1517++ """
1518++
1519++ os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
1520++
1521++ with mock.patch("os.access") as co_mock:
1522++ co_mock.return_value = False
1523++
1524++ with mock.patch(
1525++ "landscape.lib.os_release.OS_RELEASE_FILENAME_FALLBACK",
1526++ new=os_release_filename,
1527++ ):
1528++ os_release = parse_os_release()
1529++
1530++ self.assertEqual(
1531++ os_release,
1532++ {
1533++ "code-name": "codename",
1534++ "description": "Ubuntu 22.04.3 LTS",
1535++ "distributor-id": "Ubuntu",
1536++ "release": "22.04",
1537++ },
1538++ )
1539++
1540++ def test_parse_os_release_with_file(self):
1541++ """
1542++ L{parse_os_release} returns a C{dict} holding information from
1543++ the given OS release file.
1544++ """
1545++ os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
1546++ os_release = parse_os_release(os_release_filename)
1547++
1548++ self.assertEqual(
1549++ os_release,
1550++ {
1551++ "distributor-id": "Ubuntu",
1552++ "description": "Ubuntu 22.04.3 LTS",
1553++ "release": "22.04",
1554++ "code-name": "codename",
1555++ },
1556++ )
1557++
1558++ def test_parse_os_release_with_file_not_found(self):
1559++ """
1560++ L{parse_os_release} should fail with FileNotFound
1561++ if given OS release file doesn't exists.
1562++ """
1563++
1564++ self.assertRaises(
1565++ FileNotFoundError,
1566++ parse_os_release,
1567++ "TheresNoWayThisFileExists",
1568++ )
1569diff --git a/debian/patches/series b/debian/patches/series
1570index 1d2050f..f0e339b 100644
1571--- a/debian/patches/series
1572+++ b/debian/patches/series
1573@@ -1,3 +1,5 @@
1574 0001-start-service-during-config.patch
1575 add-non-filtered-interfaces-to-the-api-response.patch
1576 0002-fix-locale-error.patch
1577+package-reporter-high-cpu.patch
1578+parse-lsb-output.patch

Subscribers

People subscribed via source and target branches