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

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

fix lp question number to avoid confusion with a separate bug

f473b7a... by jansdhillon <email address hidden>

update LP numbers in patches/changelog

Revision history for this message
Andreas Hasenack (ahasenack) wrote :

Simple d/changelog fix, but a bigger question inline.

review: Needs Fixing
Revision history for this message
Andreas Hasenack (ahasenack) :

Unmerged commits

f473b7a... by jansdhillon <email address hidden>

update LP numbers in patches/changelog

07e8936... by jansdhillon <email address hidden>

fix lp question number to avoid confusion with a separate bug

ea43999... 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 941e2a5..157efde 100644
3--- a/debian/changelog
4+++ b/debian/changelog
5@@ -1,3 +1,10 @@
6+landscape-client (23.02-0ubuntu1~22.04.5) jammy; 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:45 -0700
12+
13 landscape-client (23.02-0ubuntu1~22.04.4) jammy; urgency=medium
14
15 * d/p/add-non-filtered-interfaces-to-the-api-response.patch: include
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..ac99551
135--- /dev/null
136+++ b/debian/patches/parse-lsb-output.patch
137@@ -0,0 +1,1515 @@
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+@@ -12,19 +12,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+@@ -117,7 +126,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+@@ -162,11 +170,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+@@ -265,12 +271,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+@@ -413,7 +420,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+@@ -634,9 +640,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+@@ -1,35 +1,61 @@
809+ import locale
810+-import sys
811+ import os
812+-import time
813+-import apt_pkg
814+-import mock
815+ import shutil
816+ import subprocess
817++import sys
818++import time
819++from unittest import mock
820+
821+-from twisted.internet.defer import Deferred, succeed, fail, inlineCallbacks
822++import apt_pkg
823+ from twisted.internet import reactor
824++from twisted.internet.defer import Deferred
825++from twisted.internet.defer import fail
826++from twisted.internet.defer import inlineCallbacks
827++from twisted.internet.defer import succeed
828+
829+-
830++from landscape.client.package import reporter
831++from landscape.client.package.reporter import FakeGlobalReporter
832++from landscape.client.package.reporter import FakeReporter
833++from landscape.client.package.reporter import find_reporter_command
834++from landscape.client.package.reporter import HASH_ID_REQUEST_TIMEOUT
835++from landscape.client.package.reporter import main
836++from landscape.client.package.reporter import PackageReporter
837++from landscape.client.package.reporter import PackageReporterConfiguration
838++from landscape.client.tests.helpers import BrokerServiceHelper
839++from landscape.client.tests.helpers import LandscapeTest
840+ from landscape.lib import bpickle
841+ from landscape.lib.apt.package.facade import AptFacade
842+-from landscape.lib.apt.package.store import (
843+- PackageStore, UnknownHashIDRequest, FakePackageStore)
844+-from landscape.lib.apt.package.testing import (
845+- AptFacadeHelper, SimpleRepositoryHelper,
846+- HASH1, HASH2, HASH3, PKGNAME1)
847+-from landscape.lib.fs import create_text_file, touch_file
848++from landscape.lib.apt.package.store import FakePackageStore
849++from landscape.lib.apt.package.store import PackageStore
850++from landscape.lib.apt.package.store import UnknownHashIDRequest
851++from landscape.lib.apt.package.testing import AptFacadeHelper
852++from landscape.lib.apt.package.testing import HASH1
853++from landscape.lib.apt.package.testing import HASH2
854++from landscape.lib.apt.package.testing import HASH3
855++from landscape.lib.apt.package.testing import PKGNAME1
856++from landscape.lib.apt.package.testing import SimpleRepositoryHelper
857+ from landscape.lib.fetch import FetchError
858+-from landscape.lib.lsb_release import parse_lsb_release, LSB_RELEASE_FILENAME
859+-from landscape.lib.testing import EnvironSaverHelper, FakeReactor
860+-from landscape.client.package.reporter import (
861+- PackageReporter, HASH_ID_REQUEST_TIMEOUT, main, find_reporter_command,
862+- PackageReporterConfiguration, FakeGlobalReporter, FakeReporter)
863+-from landscape.client.package import reporter
864+-from landscape.client.tests.helpers import LandscapeTest, BrokerServiceHelper
865+-
866+-
867+-SAMPLE_LSB_RELEASE = "DISTRIB_CODENAME=codename\n"
868++from landscape.lib.fs import create_text_file
869++from landscape.lib.fs import touch_file
870++from landscape.lib.os_release import OS_RELEASE_FILENAME
871++from landscape.lib.os_release import parse_os_release
872++from landscape.lib.testing import EnvironSaverHelper
873++from landscape.lib.testing import FakeReactor
874++
875++
876++SAMPLE_OS_RELEASE = """PRETTY_NAME="Ubuntu 22.04.3 LTS"
877++NAME="Ubuntu"
878++VERSION_ID="22.04"
879++VERSION="22.04.3 LTS (Jammy Jellyfish)"
880++VERSION_CODENAME=codename
881++ID=ubuntu
882++ID_LIKE=debian
883++HOME_URL="https://www.ubuntu.com/"
884++SUPPORT_URL="https://help.ubuntu.com/"
885++BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
886++PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
887++UBUNTU_CODENAME=codename
888++"""
889+
890+
891+ class PackageReporterConfigurationTest(LandscapeTest):
892+@@ -47,7 +73,6 @@ class PackageReporterConfigurationTest(L
893+
894+
895+ class PackageReporterAptTest(LandscapeTest):
896+-
897+ helpers = [AptFacadeHelper, SimpleRepositoryHelper, BrokerServiceHelper]
898+
899+ Facade = AptFacade
900+@@ -125,10 +150,10 @@ class PackageReporterAptTest(LandscapeTe
901+ return deferred.addCallback(got_result)
902+
903+ def test_set_package_ids_with_unknown_request_id(self):
904+-
905+- self.store.add_task("reporter",
906+- {"type": "package-ids", "ids": [123, 456],
907+- "request-id": 123})
908++ self.store.add_task(
909++ "reporter",
910++ {"type": "package-ids", "ids": [123, 456], "request-id": 123},
911++ )
912+
913+ # Nothing bad should happen.
914+ return self.reporter.handle_tasks()
915+@@ -300,7 +325,6 @@ class PackageReporterAptTest(LandscapeTe
916+ return_value=succeed(b"hash-ids"))
917+ @mock.patch("logging.info", return_value=None)
918+ def test_fetch_hash_id_db(self, logging_mock, mock_fetch_async):
919+-
920+ # Assume package_hash_id_url is set
921+ self.config.data_path = self.makeDir()
922+ self.config.package_hash_id_url = "http://fake.url/path/"
923+@@ -311,7 +335,7 @@ class PackageReporterAptTest(LandscapeTe
924+ # Fake uuid, codename and arch
925+ message_store = self.broker_service.message_store
926+ message_store.set_server_uuid("uuid")
927+- self.reporter.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
928++ self.reporter.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
929+ self.facade.set_arch("arch")
930+
931+ # Let's say fetch_async is successful
932+@@ -347,7 +371,7 @@ class PackageReporterAptTest(LandscapeTe
933+ # Fake uuid, codename and arch
934+ message_store = self.broker_service.message_store
935+ message_store.set_server_uuid("uuid")
936+- self.reporter.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
937++ self.reporter.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
938+ self.facade.set_arch("arch")
939+
940+ # Let's say fetch_async is successful
941+@@ -363,7 +387,6 @@ class PackageReporterAptTest(LandscapeTe
942+
943+ @mock.patch("landscape.client.package.reporter.fetch_async")
944+ def test_fetch_hash_id_db_does_not_download_twice(self, mock_fetch_async):
945+-
946+ # Let's say that the hash=>id database is already there
947+ self.config.package_hash_id_url = "http://fake.url/path/"
948+ self.config.data_path = self.makeDir()
949+@@ -375,7 +398,7 @@ class PackageReporterAptTest(LandscapeTe
950+ # Fake uuid, codename and arch
951+ message_store = self.broker_service.message_store
952+ message_store.set_server_uuid("uuid")
953+- self.reporter.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
954++ self.reporter.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
955+ self.facade.set_arch("arch")
956+
957+ result = self.reporter.fetch_hash_id_db()
958+@@ -408,28 +431,27 @@ class PackageReporterAptTest(LandscapeTe
959+
960+ @mock.patch("logging.warning", return_value=None)
961+ def test_fetch_hash_id_db_undetermined_codename(self, logging_mock):
962+-
963+ # Fake uuid
964+ message_store = self.broker_service.message_store
965+ message_store.set_server_uuid("uuid")
966+
967+ # Undetermined codename
968+- self.reporter.lsb_release_filename = self.makeFile("Foo=bar")
969++ self.reporter.os_release_filename = self.makeFile("Foo=bar")
970+
971+ result = self.reporter.fetch_hash_id_db()
972+
973+ logging_mock.assert_called_once_with(
974+ "Couldn't determine which hash=>id database to use: "
975+- "missing code-name key in %s" % self.reporter.lsb_release_filename)
976++ f"missing code-name key in {self.reporter.os_release_filename}",
977++ )
978+ return result
979+
980+ @mock.patch("logging.warning", return_value=None)
981+ def test_fetch_hash_id_db_undetermined_arch(self, logging_mock):
982+-
983+ # Fake uuid and codename
984+ message_store = self.broker_service.message_store
985+ message_store.set_server_uuid("uuid")
986+- self.reporter.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
987++ self.reporter.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
988+
989+ # Undetermined arch
990+ self.facade.set_arch("")
991+@@ -454,7 +476,7 @@ class PackageReporterAptTest(LandscapeTe
992+ # Fake uuid, codename and arch
993+ message_store = self.broker_service.message_store
994+ message_store.set_server_uuid("uuid")
995+- self.reporter.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
996++ self.reporter.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
997+ self.facade.set_arch("arch")
998+
999+ # Check fetch_async is called with the default url
1000+@@ -475,8 +497,10 @@ class PackageReporterAptTest(LandscapeTe
1001+ return_value=fail(FetchError("fetch error")))
1002+ @mock.patch("logging.warning", return_value=None)
1003+ def test_fetch_hash_id_db_with_download_error(
1004+- self, logging_mock, mock_fetch_async):
1005+-
1006++ self,
1007++ logging_mock,
1008++ mock_fetch_async,
1009++ ):
1010+ # Assume package_hash_id_url is set
1011+ self.config.data_path = self.makeDir()
1012+ self.config.package_hash_id_url = "http://fake.url/path/"
1013+@@ -484,7 +508,7 @@ class PackageReporterAptTest(LandscapeTe
1014+ # Fake uuid, codename and arch
1015+ message_store = self.broker_service.message_store
1016+ message_store.set_server_uuid("uuid")
1017+- self.reporter.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
1018++ self.reporter.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
1019+ self.facade.set_arch("arch")
1020+
1021+ # Let's say fetch_async fails
1022+@@ -508,7 +532,6 @@ class PackageReporterAptTest(LandscapeTe
1023+
1024+ @mock.patch("logging.warning", return_value=None)
1025+ def test_fetch_hash_id_db_with_undetermined_url(self, logging_mock):
1026+-
1027+ # We don't know where to fetch the hash=>id database from
1028+ self.config.url = None
1029+ self.config.package_hash_id_url = None
1030+@@ -516,7 +539,7 @@ class PackageReporterAptTest(LandscapeTe
1031+ # Fake uuid, codename and arch
1032+ message_store = self.broker_service.message_store
1033+ message_store.set_server_uuid("uuid")
1034+- self.reporter.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
1035++ self.reporter.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
1036+ self.facade.set_arch("arch")
1037+
1038+ result = self.reporter.fetch_hash_id_db()
1039+@@ -547,7 +570,7 @@ class PackageReporterAptTest(LandscapeTe
1040+ # Fake uuid, codename and arch
1041+ message_store = self.broker_service.message_store
1042+ message_store.set_server_uuid("uuid")
1043+- self.reporter.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
1044++ self.reporter.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
1045+ self.facade.set_arch("arch")
1046+
1047+ # Check fetch_async is called with the default url
1048+@@ -1005,10 +1028,12 @@ class PackageReporterAptTest(LandscapeTe
1049+ """Packages versions coming from security are reported as such."""
1050+ message_store = self.broker_service.message_store
1051+ message_store.set_accepted_types(["packages"])
1052+- lsb = parse_lsb_release(LSB_RELEASE_FILENAME)
1053++ os_release_info = parse_os_release(OS_RELEASE_FILENAME)
1054+ release_path = os.path.join(self.repository_dir, "Release")
1055+ with open(release_path, "w") as release:
1056+- release.write("Suite: {}-security".format(lsb["code-name"]))
1057++ release.write(
1058++ "Suite: {}-security".format(os_release_info["code-name"]),
1059++ )
1060+
1061+ self.store.set_hash_ids({HASH1: 1, HASH2: 2, HASH3: 3})
1062+
1063+@@ -1052,10 +1077,12 @@ class PackageReporterAptTest(LandscapeTe
1064+ message_store = self.broker_service.message_store
1065+ message_store.set_accepted_types(["packages"])
1066+
1067+- lsb = parse_lsb_release(LSB_RELEASE_FILENAME)
1068++ os_release_info = parse_os_release(OS_RELEASE_FILENAME)
1069+ release_path = os.path.join(self.repository_dir, "Release")
1070+ with open(release_path, "w") as release:
1071+- release.write("Suite: {}-backports".format(lsb["code-name"]))
1072++ release.write(
1073++ "Suite: {}-backports".format(os_release_info["code-name"]),
1074++ )
1075+
1076+ self.store.set_hash_ids({HASH1: 1, HASH2: 2, HASH3: 3})
1077+
1078+@@ -1126,10 +1153,12 @@ class PackageReporterAptTest(LandscapeTe
1079+ os.remove(os.path.join(other_backport_dir, "Packages"))
1080+ self.facade.add_channel_deb_dir(other_backport_dir)
1081+
1082+- lsb = parse_lsb_release(LSB_RELEASE_FILENAME)
1083++ os_release_info = parse_os_release(OS_RELEASE_FILENAME)
1084+ official_release_path = os.path.join(self.repository_dir, "Release")
1085+ with open(official_release_path, "w") as release:
1086+- release.write("Suite: {}-backports".format(lsb["code-name"]))
1087++ release.write(
1088++ "Suite: {}-backports".format(os_release_info["code-name"]),
1089++ )
1090+ unofficial_release_path = os.path.join(other_backport_dir, "Release")
1091+ with open(unofficial_release_path, "w") as release:
1092+ release.write("Suite: my-personal-backports")
1093+@@ -1341,8 +1370,8 @@ class PackageReporterAptTest(LandscapeTe
1094+ deferred = self.reporter.run()
1095+ self.reactor.advance(0)
1096+ with mock.patch(
1097+- "landscape.client.package.taskhandler.parse_lsb_release",
1098+- side_effect=lambda _: {"code-name": "codename"}
1099++ "landscape.client.package.taskhandler.parse_os_release",
1100++ side_effect=lambda _: {"code-name": "codename"},
1101+ ):
1102+ yield deferred
1103+
1104+@@ -2025,7 +2054,6 @@ class PackageReporterAptTest(LandscapeTe
1105+
1106+
1107+ class GlobalPackageReporterAptTest(LandscapeTest):
1108+-
1109+ helpers = [AptFacadeHelper, SimpleRepositoryHelper, BrokerServiceHelper]
1110+
1111+ def setUp(self):
1112+@@ -2075,7 +2103,6 @@ class GlobalPackageReporterAptTest(Lands
1113+
1114+
1115+ class FakePackageReporterTest(LandscapeTest):
1116+-
1117+ helpers = [EnvironSaverHelper, BrokerServiceHelper]
1118+
1119+ def setUp(self):
1120+Index: landscape-client/landscape/client/package/tests/test_taskhandler.py
1121+===================================================================
1122+--- landscape-client.orig/landscape/client/package/tests/test_taskhandler.py
1123++++ landscape-client/landscape/client/package/tests/test_taskhandler.py
1124+@@ -1,23 +1,31 @@
1125+ import os
1126+-from subprocess import CalledProcessError
1127+-
1128+-from mock import patch, Mock, ANY
1129+-
1130+-from twisted.internet.defer import Deferred, fail, succeed
1131++from unittest.mock import ANY
1132++from unittest.mock import Mock
1133++from unittest.mock import patch
1134++
1135++from twisted.internet.defer import Deferred
1136++from twisted.internet.defer import fail
1137++from twisted.internet.defer import succeed
1138+
1139++from landscape.client.broker.amp import RemoteBrokerConnector
1140++from landscape.client.package.taskhandler import LazyRemoteBroker
1141++from landscape.client.package.taskhandler import PackageTaskHandler
1142++from landscape.client.package.taskhandler import (
1143++ PackageTaskHandlerConfiguration,
1144++)
1145++from landscape.client.package.taskhandler import run_task_handler
1146++from landscape.client.tests.helpers import BrokerServiceHelper
1147++from landscape.client.tests.helpers import LandscapeTest
1148+ from landscape.lib.apt.package.facade import AptFacade
1149+-from landscape.lib.apt.package.store import HashIdStore, PackageStore
1150++from landscape.lib.apt.package.store import HashIdStore
1151++from landscape.lib.apt.package.store import PackageStore
1152+ from landscape.lib.apt.package.testing import AptFacadeHelper
1153+ from landscape.lib.lock import lock_path
1154+-from landscape.lib.testing import EnvironSaverHelper, FakeReactor
1155+-from landscape.client.broker.amp import RemoteBrokerConnector
1156+-from landscape.client.package.taskhandler import (
1157+- PackageTaskHandlerConfiguration, PackageTaskHandler, run_task_handler,
1158+- LazyRemoteBroker)
1159+-from landscape.client.tests.helpers import LandscapeTest, BrokerServiceHelper
1160++from landscape.lib.testing import EnvironSaverHelper
1161++from landscape.lib.testing import FakeReactor
1162+
1163+
1164+-SAMPLE_LSB_RELEASE = "DISTRIB_CODENAME=codename\n"
1165++SAMPLE_OS_RELEASE = "VERSION_CODENAME=codename\n"
1166+
1167+
1168+ class PackageTaskHandlerConfigurationTest(LandscapeTest):
1169+@@ -34,7 +42,6 @@ class PackageTaskHandlerConfigurationTes
1170+
1171+
1172+ class PackageTaskHandlerTest(LandscapeTest):
1173+-
1174+ helpers = [AptFacadeHelper, EnvironSaverHelper, BrokerServiceHelper]
1175+
1176+ def setUp(self):
1177+@@ -46,7 +53,6 @@ class PackageTaskHandlerTest(LandscapeTe
1178+ self.store, self.facade, self.remote, self.config, self.reactor)
1179+
1180+ def test_use_hash_id_db(self):
1181+-
1182+ # We don't have this hash=>id mapping
1183+ self.assertEqual(self.store.get_hash_id(b"hash"), None)
1184+
1185+@@ -60,7 +66,7 @@ class PackageTaskHandlerTest(LandscapeTe
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+ # Attach the hash=>id database to our store
1194+@@ -75,13 +81,12 @@ class PackageTaskHandlerTest(LandscapeTe
1195+
1196+ @patch("logging.warning")
1197+ def test_use_hash_id_db_undetermined_codename(self, logging_mock):
1198+-
1199+ # Fake uuid
1200+ message_store = self.broker_service.message_store
1201+ message_store.set_server_uuid("uuid")
1202+
1203+ # Undetermined codename
1204+- self.handler.lsb_release_filename = self.makeFile("Foo=bar")
1205++ self.handler.os_release_filename = self.makeFile("Foo=bar")
1206+
1207+ # Go!
1208+ result = self.handler.use_hash_id_db()
1209+@@ -89,30 +94,29 @@ class PackageTaskHandlerTest(LandscapeTe
1210+ # The failure should be properly logged
1211+ logging_mock.assert_called_with(
1212+ "Couldn't determine which hash=>id database to use: "
1213+- "missing code-name key in %s" % self.handler.lsb_release_filename)
1214++ f"missing code-name key in {self.handler.os_release_filename}",
1215++ )
1216+
1217+ return result
1218+
1219+ @patch("logging.warning")
1220+- def test_use_hash_id_db_wit_non_existing_lsb_release(self, logging_mock):
1221+-
1222++ def test_use_hash_id_db_with_non_existing_os_release(self, logging_mock):
1223+ # Fake uuid
1224+ message_store = self.broker_service.message_store
1225+ message_store.set_server_uuid("uuid")
1226+
1227+- # Undetermined codename
1228+- self.handler.lsb_release_filename = self.makeFile()
1229++ # Undetermined os release filename
1230++ self.handler.os_release_filename = ""
1231+
1232+ # Go!
1233+- with patch("landscape.lib.lsb_release.check_output") as co_mock:
1234+- co_mock.side_effect = CalledProcessError(127, "")
1235+- result = self.handler.use_hash_id_db()
1236++ result = self.handler.use_hash_id_db()
1237+
1238+ # The failure should be properly logged
1239+ logging_mock.assert_called_with(
1240+ "Couldn't determine which hash=>id database to use: "
1241+- "[Errno 2] No such file or directory: '%s'" %
1242+- self.handler.lsb_release_filename)
1243++ "[Errno 2] No such file or directory: "
1244++ f"'{self.handler.os_release_filename}'",
1245++ )
1246+
1247+ return result
1248+
1249+@@ -165,11 +169,10 @@ class PackageTaskHandlerTest(LandscapeTe
1250+
1251+ @patch("logging.warning")
1252+ def test_use_hash_id_db_undetermined_arch(self, logging_mock):
1253+-
1254+ # Fake uuid and codename
1255+ message_store = self.broker_service.message_store
1256+ message_store.set_server_uuid("uuid")
1257+- self.handler.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
1258++ self.handler.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
1259+
1260+ # Undetermined arch
1261+ self.facade.set_arch(None)
1262+@@ -185,14 +188,13 @@ class PackageTaskHandlerTest(LandscapeTe
1263+ return result
1264+
1265+ def test_use_hash_id_db_database_not_found(self):
1266+-
1267+ # Clean path, we don't have an appropriate hash=>id database
1268+ self.config.data_path = self.makeDir()
1269+
1270+ # Fake uuid, codename and arch
1271+ message_store = self.broker_service.message_store
1272+ message_store.set_server_uuid("uuid")
1273+- self.handler.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
1274++ self.handler.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
1275+ self.facade.set_arch("arch")
1276+
1277+ # Let's try
1278+@@ -207,7 +209,6 @@ class PackageTaskHandlerTest(LandscapeTe
1279+
1280+ @patch("logging.warning")
1281+ def test_use_hash_id_with_invalid_database(self, logging_mock):
1282+-
1283+ # Let's say the appropriate database is actually garbage
1284+ self.config.data_path = self.makeDir()
1285+ os.makedirs(os.path.join(self.config.data_path, "package", "hash-id"))
1286+@@ -218,7 +219,7 @@ class PackageTaskHandlerTest(LandscapeTe
1287+ # Fake uuid, codename and arch
1288+ message_store = self.broker_service.message_store
1289+ message_store.set_server_uuid("uuid")
1290+- self.handler.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
1291++ self.handler.os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
1292+ self.facade.set_arch("arch")
1293+
1294+ # Try to attach it
1295+@@ -362,7 +363,6 @@ class PackageTaskHandlerTest(LandscapeTe
1296+ reactor_mock.run.side_effect = lambda: call_when_running[0]()
1297+
1298+ def assert_task_handler(ignored):
1299+-
1300+ store, facade, broker, config, reactor = handler_args
1301+
1302+ # Verify the arguments passed to the reporter constructor.
1303+@@ -405,7 +405,6 @@ class PackageTaskHandlerTest(LandscapeTe
1304+ return result.addCallback(assert_task_handler)
1305+
1306+ def test_run_task_handler_when_already_locked(self):
1307+-
1308+ lock_path(os.path.join(self.data_path, "package", "default.lock"))
1309+
1310+ try:
1311+@@ -453,7 +452,6 @@ class PackageTaskHandlerTest(LandscapeTe
1312+
1313+
1314+ class LazyRemoteBrokerTest(LandscapeTest):
1315+-
1316+ helpers = [BrokerServiceHelper]
1317+
1318+ def test_wb_is_lazy(self):
1319+Index: landscape-client/landscape/lib/lsb_release.py
1320+===================================================================
1321+--- landscape-client.orig/landscape/lib/lsb_release.py
1322++++ /dev/null
1323+@@ -1,59 +0,0 @@
1324+-"""Get information from /usr/bin/lsb_release."""
1325+-import os
1326+-from subprocess import CalledProcessError, check_output
1327+-
1328+-LSB_RELEASE = "/usr/bin/lsb_release"
1329+-LSB_RELEASE_FILENAME = "/etc/lsb_release"
1330+-LSB_RELEASE_FILE_KEYS = {
1331+- "DISTRIB_ID": "distributor-id",
1332+- "DISTRIB_DESCRIPTION": "description",
1333+- "DISTRIB_RELEASE": "release",
1334+- "DISTRIB_CODENAME": "code-name",
1335+-}
1336+-
1337+-
1338+-def parse_lsb_release(lsb_release_filename=None):
1339+- """
1340+- Returns a C{dict} holding information about the system LSB release.
1341+- Reads from C{lsb_release_filename} if it exists, else calls
1342+- C{LSB_RELEASE}
1343+- """
1344+- if lsb_release_filename and os.path.exists(lsb_release_filename):
1345+- return parse_lsb_release_file(lsb_release_filename)
1346+-
1347+- with open(os.devnull, 'w') as FNULL:
1348+- try:
1349+- lsb_info = check_output([LSB_RELEASE, "-as"], stderr=FNULL)
1350+- except (CalledProcessError, FileNotFoundError):
1351+- # Fall back to reading file, even if it doesn't exist.
1352+- return parse_lsb_release_file(lsb_release_filename)
1353+- else:
1354+- dist, desc, release, code_name, _ = lsb_info.decode().split("\n")
1355+-
1356+- return {
1357+- "distributor-id": dist,
1358+- "release": release,
1359+- "code-name": code_name,
1360+- "description": desc,
1361+- }
1362+-
1363+-
1364+-def parse_lsb_release_file(filename):
1365+- """
1366+- Returns a C{dict} holding information about the system LSB release
1367+- by attempting to parse C{filename}.
1368+-
1369+- @raises: A FileNotFoundError if C{filename} does not exist.
1370+- """
1371+- info = {}
1372+-
1373+- with open(filename) as fd:
1374+- for line in fd:
1375+- key, value = line.split("=")
1376+-
1377+- if key in LSB_RELEASE_FILE_KEYS:
1378+- key = LSB_RELEASE_FILE_KEYS[key.strip()]
1379+- value = value.strip().strip('"')
1380+- info[key] = value
1381+-
1382+- return info
1383+Index: landscape-client/landscape/lib/os_release.py
1384+===================================================================
1385+--- /dev/null
1386++++ landscape-client/landscape/lib/os_release.py
1387+@@ -0,0 +1,43 @@
1388++"""Get information from os-release."""
1389++import os
1390++
1391++OS_RELEASE_FILENAME = "/etc/os-release"
1392++OS_RELEASE_FILENAME_FALLBACK = "/usr/lib/os-release"
1393++OS_RELEASE_FILE_KEYS = {
1394++ "NAME": "distributor-id",
1395++ "PRETTY_NAME": "description",
1396++ "VERSION_ID": "release",
1397++ "VERSION_CODENAME": "code-name",
1398++}
1399++
1400++
1401++def parse_os_release(os_release_filename=None):
1402++ """
1403++ Returns a C{dict} holding information about the system LSB release
1404++ by attempting to parse C{os_release_filename} if specified. If no
1405++ filename is provided /etc/os-release will be used or
1406++ /usr/lib/os-release as a fallback as indicated in os-release
1407++ at Freedesktop.org
1408++
1409++ @raises: A FileNotFoundError if C{filename} does not exist.
1410++ """
1411++ info = {}
1412++
1413++ if os_release_filename is None:
1414++ os_release_filename = OS_RELEASE_FILENAME
1415++ if not os.path.exists(os_release_filename) or not os.access(
1416++ os_release_filename,
1417++ os.R_OK,
1418++ ):
1419++ os_release_filename = OS_RELEASE_FILENAME_FALLBACK
1420++
1421++ with open(os_release_filename) as fd:
1422++ for line in fd:
1423++ key, value = line.split("=")
1424++
1425++ if key in OS_RELEASE_FILE_KEYS:
1426++ key = OS_RELEASE_FILE_KEYS[key.strip()]
1427++ value = value.strip().strip('"')
1428++ info[key] = value
1429++
1430++ return info
1431+Index: landscape-client/landscape/lib/tests/test_lsb_release.py
1432+===================================================================
1433+--- landscape-client.orig/landscape/lib/tests/test_lsb_release.py
1434++++ /dev/null
1435+@@ -1,75 +0,0 @@
1436+-import unittest
1437+-from subprocess import CalledProcessError
1438+-from unittest import mock
1439+-
1440+-from landscape.lib import testing
1441+-from landscape.lib.lsb_release import parse_lsb_release
1442+-
1443+-
1444+-class LsbReleaseTest(testing.FSTestCase, unittest.TestCase):
1445+-
1446+- def test_parse_lsb_release(self):
1447+- with mock.patch("landscape.lib.lsb_release.check_output") as co_mock:
1448+- co_mock.return_value = (b"Ubuntu\nUbuntu 22.04.1 LTS\n22.04\njammy"
1449+- b"\n")
1450+- lsb_release = parse_lsb_release()
1451+-
1452+- self.assertEqual(lsb_release,
1453+- {"distributor-id": "Ubuntu",
1454+- "description": "Ubuntu 22.04.1 LTS",
1455+- "release": "22.04",
1456+- "code-name": "jammy"})
1457+-
1458+- def test_parse_lsb_release_debian(self):
1459+- with mock.patch("landscape.lib.lsb_release.check_output") as co_mock:
1460+- co_mock.return_value = (b"Debian\nDebian GNU/Linux 11 (bullseye)\n"
1461+- b"11\nbullseye\n")
1462+- lsb_release = parse_lsb_release()
1463+-
1464+- self.assertEqual(lsb_release,
1465+- {"distributor-id": "Debian",
1466+- "description": "Debian GNU/Linux 11 (bullseye)",
1467+- "release": "11",
1468+- "code-name": "bullseye"})
1469+-
1470+- def test_parse_lsb_release_file(self):
1471+- """
1472+- L{parse_lsb_release} returns a C{dict} holding information from
1473+- the given LSB release file.
1474+- """
1475+- lsb_release_filename = self.makeFile("DISTRIB_ID=Ubuntu\n"
1476+- "DISTRIB_RELEASE=6.06\n"
1477+- "DISTRIB_CODENAME=dapper\n"
1478+- "DISTRIB_DESCRIPTION="
1479+- "\"Ubuntu 6.06.1 LTS\"\n")
1480+-
1481+- with mock.patch("landscape.lib.lsb_release.check_output") as co_mock:
1482+- co_mock.side_effect = CalledProcessError(127, "")
1483+- lsb_release = parse_lsb_release(lsb_release_filename)
1484+-
1485+- self.assertEqual(lsb_release,
1486+- {"distributor-id": "Ubuntu",
1487+- "description": "Ubuntu 6.06.1 LTS",
1488+- "release": "6.06",
1489+- "code-name": "dapper"})
1490+-
1491+- def test_parse_lsb_release_file_with_missing_or_extra_fields(self):
1492+- """
1493+- L{parse_lsb_release} ignores lines not matching the map of
1494+- known keys, and returns only keys with an actual value.
1495+- """
1496+- lsb_release_filename = self.makeFile("DISTRIB_ID=Ubuntu\n"
1497+- "FOO=Bar\n")
1498+-
1499+- with mock.patch("landscape.lib.lsb_release.check_output") as co_mock:
1500+- co_mock.side_effect = CalledProcessError(127, "")
1501+- lsb_release = parse_lsb_release(lsb_release_filename)
1502+-
1503+- self.assertEqual(lsb_release, {"distributor-id": "Ubuntu"})
1504+-
1505+- def test_parse_lsb_release_file_not_found(self):
1506+- with mock.patch("landscape.lib.lsb_release.check_output") as co_mock:
1507+- co_mock.side_effect = CalledProcessError(127, "")
1508+-
1509+- self.assertRaises(FileNotFoundError, parse_lsb_release,
1510+- "TheresNoWayThisFileExists")
1511+Index: landscape-client/landscape/lib/tests/test_os_release.py
1512+===================================================================
1513+--- /dev/null
1514++++ landscape-client/landscape/lib/tests/test_os_release.py
1515+@@ -0,0 +1,137 @@
1516++import unittest
1517++from unittest import mock
1518++
1519++from landscape.lib import testing
1520++from landscape.lib.os_release import parse_os_release
1521++
1522++SAMPLE_OS_RELEASE = """PRETTY_NAME="Ubuntu 22.04.3 LTS"
1523++NAME="Ubuntu"
1524++VERSION_ID="22.04"
1525++VERSION="22.04.3 LTS (Jammy Jellyfish)"
1526++VERSION_CODENAME=codename
1527++ID=ubuntu
1528++ID_LIKE=debian
1529++HOME_URL="https://www.ubuntu.com/"
1530++SUPPORT_URL="https://help.ubuntu.com/"
1531++BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
1532++PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
1533++UBUNTU_CODENAME=codename
1534++"""
1535++
1536++
1537++class OsReleaseTest(testing.FSTestCase, unittest.TestCase):
1538++ def test_parse_os_release(self):
1539++ """
1540++ L{parse_os_release} ignores lines not matching the map of
1541++ known keys, and returns only keys with an actual value. By
1542++ default it reads from OS_RELEASE_FILENAME if no other path
1543++ is provided.
1544++ """
1545++
1546++ os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
1547++
1548++ with mock.patch(
1549++ "landscape.lib.os_release.OS_RELEASE_FILENAME",
1550++ new=os_release_filename,
1551++ ):
1552++ os_release = parse_os_release()
1553++
1554++ self.assertEqual(
1555++ os_release,
1556++ {
1557++ "code-name": "codename",
1558++ "description": "Ubuntu 22.04.3 LTS",
1559++ "distributor-id": "Ubuntu",
1560++ "release": "22.04",
1561++ },
1562++ )
1563++
1564++ def test_parse_os_release_no_etc(self):
1565++ """
1566++ L{parse_os_release} ignores lines not matching the map of
1567++ known keys, and returns only keys with an actual value. By
1568++ default it reads from OS_RELEASE_FILENAME if no other path
1569++ is provided and it should read from SAMPLE_OS_RELEASE_FALLBACK
1570++ path if there OS_RELEASE_FILENAME doesn't exists.
1571++ """
1572++
1573++ os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
1574++
1575++ with mock.patch("os.path.exists") as co_mock:
1576++ co_mock.return_value = False
1577++
1578++ with mock.patch(
1579++ "landscape.lib.os_release.OS_RELEASE_FILENAME_FALLBACK",
1580++ new=os_release_filename,
1581++ ):
1582++ os_release = parse_os_release()
1583++
1584++ self.assertEqual(
1585++ os_release,
1586++ {
1587++ "code-name": "codename",
1588++ "description": "Ubuntu 22.04.3 LTS",
1589++ "distributor-id": "Ubuntu",
1590++ "release": "22.04",
1591++ },
1592++ )
1593++
1594++ def test_parse_os_release_no_perms(self):
1595++ """
1596++ L{parse_os_release} ignores lines not matching the map of
1597++ known keys, and returns only keys with an actual value. By
1598++ default it reads from OS_RELEASE_FILENAME if no other path
1599++ is provided and it should read from SAMPLE_OS_RELEASE_FALLBACK
1600++ path if there OS_RELEASE_FILENAME is not readable.
1601++ """
1602++
1603++ os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
1604++
1605++ with mock.patch("os.access") as co_mock:
1606++ co_mock.return_value = False
1607++
1608++ with mock.patch(
1609++ "landscape.lib.os_release.OS_RELEASE_FILENAME_FALLBACK",
1610++ new=os_release_filename,
1611++ ):
1612++ os_release = parse_os_release()
1613++
1614++ self.assertEqual(
1615++ os_release,
1616++ {
1617++ "code-name": "codename",
1618++ "description": "Ubuntu 22.04.3 LTS",
1619++ "distributor-id": "Ubuntu",
1620++ "release": "22.04",
1621++ },
1622++ )
1623++
1624++ def test_parse_os_release_with_file(self):
1625++ """
1626++ L{parse_os_release} returns a C{dict} holding information from
1627++ the given OS release file.
1628++ """
1629++ os_release_filename = self.makeFile(SAMPLE_OS_RELEASE)
1630++ os_release = parse_os_release(os_release_filename)
1631++
1632++ self.assertEqual(
1633++ os_release,
1634++ {
1635++ "distributor-id": "Ubuntu",
1636++ "description": "Ubuntu 22.04.3 LTS",
1637++ "release": "22.04",
1638++ "code-name": "codename",
1639++ },
1640++ )
1641++
1642++ def test_parse_os_release_with_file_not_found(self):
1643++ """
1644++ L{parse_os_release} should fail with FileNotFound
1645++ if given OS release file doesn't exists.
1646++ """
1647++
1648++ self.assertRaises(
1649++ FileNotFoundError,
1650++ parse_os_release,
1651++ "TheresNoWayThisFileExists",
1652++ )
1653diff --git a/debian/patches/series b/debian/patches/series
1654index 6c61034..8b5cb58 100644
1655--- a/debian/patches/series
1656+++ b/debian/patches/series
1657@@ -1,2 +1,4 @@
1658 0001-start-service-during-config.patch
1659 add-non-filtered-interfaces-to-the-api-response.patch
1660+package-reporter-high-cpu.patch
1661+parse-lsb-output.patch

Subscribers

People subscribed via source and target branches