Skip to content

Commit 1e997cc

Browse files
authored
Bugfix: Mitigate external Ubuntu Pro Client issues (#308)
**Background:** For VMs that are Ubuntu Pro client capable, there are 2 sets of issues that manifest as required security updates not getting installed on machines_ (some machines). 1. Canonical is aware of packages normally seen as updates in the default flow not being shown as required in pro client scans. There is an explanation for this but the way we have taken a tight dependency on pro client when it's functional causes these updates no to get installed. 2. There are cases where wide swathes of security updates are not getting detected by pro client. It was not clear if this was a pro client issue in the past or an issue with our code. The additional code that went in November helped identify that this was a pro client issue when newer reports came in: #273 Both problems listed above are being resolved by not fully relying on pro client and using a combined overlay of the default scanning mechanism with whatever pro client reports. This is the 'best of both worlds' approach. Extensive logging additions will help further reviews with Canonical on pro client behaviors without affecting any customer while a multi-stage resolution is ironed out.
1 parent d28c9cc commit 1e997cc

File tree

4 files changed

+46
-28
lines changed

4 files changed

+46
-28
lines changed

src/core/src/bootstrap/Constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ class EnvLayer(EnumBackport):
378378
class UbuntuProClientSettings(EnumBackport):
379379
FEATURE_ENABLED = True
380380
MINIMUM_PYTHON_VERSION_REQUIRED = (3, 5) # using tuple as we can compare this with sys.version_info. The comparison will happen in the same order. Major version checked first. Followed by Minor version.
381-
MAX_OS_MAJOR_VERSION_SUPPORTED = 20
381+
MAX_OS_MAJOR_VERSION_SUPPORTED = 24
382382
MINIMUM_CLIENT_VERSION = "27.14.4"
383383

384384
class BufferMessage(EnumBackport):

src/core/src/package_managers/AptitudePackageManager.py

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -333,9 +333,9 @@ def get_all_updates(self, cached=False):
333333
self.composite_logger.log_debug("[APM-Pro] Get all updates : [DefaultAllPackagesCount={0}][UbuntuProClientQuerySuccess={1}][UbuntuProClientAllPackagesCount={2}]"
334334
.format(len(self.all_updates_cached), ubuntu_pro_client_all_updates_query_success, len(self.ubuntu_pro_client_all_updates_cached)))
335335
if len(pro_client_missed_updates) > 0: # not good, needs investigation
336-
self.composite_logger.log_debug("[APM-Pro][!] Pro client missed updates found. [Count={0}][Updates={1}]".format(len(pro_client_missed_updates), pro_client_missed_updates))
336+
self.composite_logger.log_debug("[APM-Pro][!] Pro Client missed updates found. [Count={0}][Updates={1}]".format(len(pro_client_missed_updates), pro_client_missed_updates))
337337
if len(all_updates_missed_updates) > 0: # interesting, for review
338-
self.composite_logger.log_debug("[APM-Pro] Pro client only updates found. [Count={0}][Updates={1}]".format(len(all_updates_missed_updates), all_updates_missed_updates))
338+
self.composite_logger.log_debug("[APM-Pro][*] Pro Client only updates found. [Count={0}][Updates={1}]".format(len(all_updates_missed_updates), all_updates_missed_updates))
339339

340340
if ubuntu_pro_client_all_updates_query_success: # this needs to be revisited based on logs above
341341
return self.ubuntu_pro_client_all_updates_cached, self.ubuntu_pro_client_all_updates_versions_cached
@@ -348,28 +348,46 @@ def get_security_updates(self):
348348
ubuntu_pro_client_security_packages = []
349349
ubuntu_pro_client_security_package_versions = []
350350

351-
self.composite_logger.log_verbose("[APM] Discovering 'security' packages...")
351+
# regular security updates check
352+
self.composite_logger.log_verbose("[APM] Discovering 'security' packages (default)...")
352353
source_parts, source_list = self.__get_custom_sources_to_spec(self.max_patch_publish_date, base_classification=Constants.PackageClassification.SECURITY)
353354
cmd = self.__generate_command_with_custom_sources(self.cmd_dist_upgrade_simulation_template, source_parts=source_parts, source_list=source_list)
354355
out = self.invoke_package_manager(cmd)
355356
security_packages, security_package_versions = self.extract_packages_and_versions(out)
356-
self.composite_logger.log_debug("[APM] Discovered 'security' packages. [Count={0}]".format(len(security_packages)))
357+
self.composite_logger.log_debug("[APM] Discovered 'security' packages (default). [Count={0}]".format(len(security_packages)))
357358

359+
# Query pro client if prerequisites are met
358360
if self.__pro_client_prereq_met:
361+
self.refresh_repo()
362+
self.composite_logger.log_verbose("[APM-Pro][Sec] Discovering 'security' packages (pro client)...")
359363
ubuntu_pro_client_security_updates_query_success, ubuntu_pro_client_security_packages, ubuntu_pro_client_security_package_versions = self.ubuntu_pro_client.get_security_updates()
360-
pro_client_missed_updates = list(set(security_packages) - set(ubuntu_pro_client_security_packages))
361-
sec_updates_missed_updates = list(set(ubuntu_pro_client_security_packages) - set(security_packages))
362-
self.composite_logger.log_debug("[APM-Pro][Sec] Get Security Updates : [DefaultSecurityPackagesCount={0}][UbuntuProClientQuerySuccess={1}][UbuntuProClientSecurityPackagesCount={2}]".format(len(security_packages), ubuntu_pro_client_security_updates_query_success, len(ubuntu_pro_client_security_packages)))
363-
if len(pro_client_missed_updates) > 0: # not good, needs investigation
364-
self.composite_logger.log_debug("[APM-Pro][Sec][!] Pro client missed updates found. [Count={0}][Updates={1}]".format(len(pro_client_missed_updates), pro_client_missed_updates))
365-
if len(sec_updates_missed_updates) > 0: # interesting, for review
366-
self.composite_logger.log_debug("[APM-Pro][Sec] Pro client only updates found. [Count={0}][Updates={1}]".format(len(sec_updates_missed_updates), sec_updates_missed_updates))
367364

368-
if ubuntu_pro_client_security_updates_query_success: # this needs to be revisited based on logs above
369-
return ubuntu_pro_client_security_packages, ubuntu_pro_client_security_package_versions
370-
else:
365+
# Only use non-pro client results if either pre-reqs are not met or if the query fails
366+
if not ubuntu_pro_client_security_updates_query_success:
367+
self.composite_logger.log_debug("[APM-Pro][Sec] Using non-Pro Client results only. [ProClientPreReq={0}][ProClientQuerySuccess={1}]".format(str(self.__pro_client_prereq_met), str(ubuntu_pro_client_security_updates_query_success)))
371368
return security_packages, security_package_versions
372369

370+
# Pro-client works - Cross-examine the results of queries
371+
pro_client_missed_updates = list(set(security_packages) - set(ubuntu_pro_client_security_packages))
372+
sec_updates_missed_updates = list(set(ubuntu_pro_client_security_packages) - set(security_packages))
373+
self.composite_logger.log_verbose("[APM-Pro][Sec] Pro Client to default package count comparison. [DefaultSecurityPackagesCount={0}][UbuntuProClientSecurityPackagesCount={1}]".format(len(security_packages), len(ubuntu_pro_client_security_packages)))
374+
if len(pro_client_missed_updates) > 0: # not good, needs investigation - incl. several pro client differences that are now known
375+
self.composite_logger.log_debug("[APM-Pro][Sec][!] Pro Client missed updates found. [Count={0}][Updates={1}]".format(len(pro_client_missed_updates), pro_client_missed_updates))
376+
if len(sec_updates_missed_updates) > 0: # interesting, for review
377+
self.composite_logger.log_debug("[APM-Pro][Sec][*] Pro Client-only updates found. [Count={0}][Updates={1}]".format(len(sec_updates_missed_updates), sec_updates_missed_updates))
378+
379+
# Use default security update list & versions as base, and adding pro client specific items on top
380+
complete_list = security_packages
381+
complete_version_list = security_package_versions # default security update list (incl. versions) supersedes due to reliability
382+
if len(sec_updates_missed_updates) > 0:
383+
for index in range(len(ubuntu_pro_client_security_packages)):
384+
if ubuntu_pro_client_security_packages[index] in sec_updates_missed_updates:
385+
complete_list.append(ubuntu_pro_client_security_packages[index])
386+
complete_version_list.append(ubuntu_pro_client_security_package_versions[index])
387+
self.composite_logger.log_debug("[APM-Pro][Sec][!] Added Pro Client-only packages to full security package list. [CombinedCount={0}][ProClientOnlyCount={1}][DefaultSecOnlyCount={2}]".format(len(complete_list),len(sec_updates_missed_updates),len(pro_client_missed_updates)))
388+
389+
return complete_list, complete_version_list
390+
373391
def get_security_esm_updates(self):
374392
"""Get missing security-esm updates."""
375393
ubuntu_pro_client_security_esm_updates_query_success = False

src/core/src/package_managers/UbuntuProClient.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def install_or_update_pro(self):
4242
run_command_success = True
4343
except Exception as error:
4444
run_command_exception = repr(error)
45-
self.composite_logger.log_debug("Ubuntu Pro Client installation: [InstallationSuccess={0}][Error={1}]".format(run_command_success, run_command_exception))
45+
self.composite_logger.log_debug("[APM][Pro] Ubuntu Pro Client installation: [InstallationSuccess={0}][Error={1}]".format(run_command_success, run_command_exception))
4646
return run_command_success
4747

4848
def is_pro_working(self):
@@ -59,7 +59,7 @@ def is_pro_working(self):
5959
# extract version from pro_client_verison 27.13.4~18.04.1 -> 27.13.4
6060
extracted_ubuntu_pro_client_version = self.version_comparator.extract_version_from_os_version_nums(ubuntu_pro_client_version)
6161

62-
self.composite_logger.log_debug("Ubuntu Pro Client current version: [ClientVersion={0}]".format(str(extracted_ubuntu_pro_client_version)))
62+
self.composite_logger.log_verbose("[APM][Pro] Ubuntu Pro Client current version: [ClientVersion={0}]".format(str(extracted_ubuntu_pro_client_version)))
6363

6464
# use custom comparator output 0 (equal), -1 (less), +1 (greater)
6565
is_minimum_ubuntu_pro_version_installed = self.version_comparator.compare_versions(extracted_ubuntu_pro_client_version, Constants.UbuntuProClientSettings.MINIMUM_CLIENT_VERSION) >= 0
@@ -70,7 +70,7 @@ def is_pro_working(self):
7070
except Exception as error:
7171
ubuntu_pro_client_exception = repr(error)
7272

73-
self.composite_logger.log_debug("Is Ubuntu Pro Client working debug flags: [Success={0}][UbuntuProClientVersion={1}][UbuntuProClientMinimumVersionInstalled={2}][IsAttached={3}][Error={4}]".format(is_ubuntu_pro_client_working, ubuntu_pro_client_version, is_minimum_ubuntu_pro_version_installed, self.is_ubuntu_pro_client_attached, ubuntu_pro_client_exception))
73+
self.composite_logger.log_debug("[APM][Pro] Is Ubuntu Pro Client working debug flags: [Success={0}][UbuntuProClientVersion={1}][UbuntuProClientMinimumVersionInstalled={2}][IsAttached={3}][Error={4}]".format(is_ubuntu_pro_client_working, ubuntu_pro_client_version, is_minimum_ubuntu_pro_version_installed, self.is_ubuntu_pro_client_attached, ubuntu_pro_client_exception))
7474
return is_ubuntu_pro_client_working
7575

7676
def log_ubuntu_pro_client_attached(self):
@@ -82,7 +82,7 @@ def log_ubuntu_pro_client_attached(self):
8282
is_ubuntu_pro_client_attached = json.loads(output)['summary']['ua']['attached']
8383
except Exception as error:
8484
ubuntu_pro_client_exception = repr(error)
85-
self.composite_logger.log_debug("Ubuntu Pro Client Attached Exception: [Exception={0}]".format(ubuntu_pro_client_exception))
85+
self.composite_logger.log_debug("[APM][Pro] Ubuntu Pro Client Attached Exception: [Exception={0}]".format(ubuntu_pro_client_exception))
8686
return is_ubuntu_pro_client_attached
8787

8888
def extract_packages_and_versions(self, updates):
@@ -121,22 +121,22 @@ def get_security_updates(self):
121121
security_criteria = ["standard-security"]
122122
security_updates_query_success, security_updates_exception, security_updates, security_updates_versions = self.get_filtered_updates(security_criteria)
123123

124-
self.composite_logger.log_debug("Ubuntu Pro Client get security updates : [SecurityUpdatesCount={0}][error={1}]".format(len(security_updates), security_updates_exception))
124+
self.composite_logger.log_debug("[APM][Pro] Ubuntu Pro Client get standard security updates : [SecurityUpdatesCount={0}][error={1}]".format(len(security_updates), security_updates_exception))
125125
return security_updates_query_success, security_updates, security_updates_versions
126126

127127
def get_security_esm_updates(self):
128128
"""query Ubuntu Pro Client to get security-esm updates."""
129129
security_esm_updates_query_success, security_esm_updates_exception, security_esm_updates, security_esm_updates_versions = self.get_filtered_updates(self.security_esm_criteria_strings)
130130

131-
self.composite_logger.log_debug("Ubuntu Pro Client get security-esm updates : [SecurityEsmUpdatesCount={0}][error={1}]".format(len(security_esm_updates),security_esm_updates_exception))
131+
self.composite_logger.log_debug("[APM][Pro] Ubuntu Pro Client get security-esm updates : [SecurityEsmUpdatesCount={0}][error={1}]".format(len(security_esm_updates),security_esm_updates_exception))
132132
return security_esm_updates_query_success, security_esm_updates, security_esm_updates_versions
133133

134134
def get_all_updates(self):
135135
"""query Ubuntu Pro Client to get all updates."""
136136
filter_criteria = []
137137
all_updates_query_success, all_updates_exception, all_updates, all_updates_versions = self.get_filtered_updates(filter_criteria)
138138

139-
self.composite_logger.log_debug("Ubuntu Pro Client get all updates: [AllUpdatesCount={0}][error={1}]".format(len(all_updates), all_updates_exception))
139+
self.composite_logger.log_debug("[APM][Pro] Ubuntu Pro Client get all updates: [AllUpdatesCount={0}][error={1}]".format(len(all_updates), all_updates_exception))
140140
return all_updates_query_success, all_updates, all_updates_versions
141141

142142
def get_ubuntu_pro_client_updates(self):
@@ -148,7 +148,7 @@ def get_other_updates(self):
148148
other_criteria = ["standard-updates"]
149149
other_updates_query_success, other_update_exception, other_updates, other_updates_versions = self.get_filtered_updates(other_criteria)
150150

151-
self.composite_logger.log_debug("Ubuntu Pro Client get other updates: [OtherUpdatesCount={0}][error = {1}]".format(len(other_updates), other_update_exception))
151+
self.composite_logger.log_debug("[APM][Pro] Ubuntu Pro Client get other updates: [OtherUpdatesCount={0}][error = {1}]".format(len(other_updates), other_update_exception))
152152
return other_updates_query_success, other_updates, other_updates_versions
153153

154154
def is_reboot_pending(self):
@@ -170,5 +170,5 @@ def is_reboot_pending(self):
170170
ubuntu_pro_client_api_success = False
171171
ubuntu_pro_client_exception = repr(error)
172172

173-
self.composite_logger.log_debug("Ubuntu Pro Client Reboot Required: [UbuntuProClientSuccess={0}][RebootRequiredFlag={1}][Error={2}]".format(ubuntu_pro_client_api_success, ubuntu_pro_client_reboot_required, ubuntu_pro_client_exception))
173+
self.composite_logger.log_debug("[APM][Pro] Ubuntu Pro Client Reboot Required: [UbuntuProClientSuccess={0}][RebootRequiredFlag={1}][Error={2}]".format(ubuntu_pro_client_api_success, ubuntu_pro_client_reboot_required, ubuntu_pro_client_exception))
174174
return ubuntu_pro_client_api_success, ubuntu_pro_client_reboot_required

src/core/tests/Test_AptitudePackageManager.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ def mock_read_with_retry_raise_exception(self):
4141
def mock_write_with_retry_raise_exception(self, file_path_or_handle, data, mode='a+'):
4242
raise Exception
4343

44-
def mock_linux_distribution_to_return_ubuntu_focal(self):
45-
return ['Ubuntu', '24.04', 'focal']
44+
def mock_linux_distribution_to_return_ubuntu_oracular(self):
45+
return ['Ubuntu', '26.04', 'oracular']
4646

4747
def mock_is_pro_working_return_true(self):
4848
return True
@@ -427,7 +427,7 @@ def test_is_pro_client_prereq_met_should_return_false_for_unsupported_os_version
427427
package_manager = self.container.get('package_manager')
428428
backup_envlayer_platform_linux_distribution = LegacyEnvLayerExtensions.LegacyPlatform.linux_distribution
429429
backup_package_manager_ubuntu_pro_client_is_pro_working = package_manager.ubuntu_pro_client.is_pro_working
430-
LegacyEnvLayerExtensions.LegacyPlatform.linux_distribution = self.mock_linux_distribution_to_return_ubuntu_focal
430+
LegacyEnvLayerExtensions.LegacyPlatform.linux_distribution = self.mock_linux_distribution_to_return_ubuntu_oracular
431431
package_manager.ubuntu_pro_client.is_pro_working = self.mock_is_pro_working_return_true
432432

433433
self.assertFalse(package_manager.check_pro_client_prerequisites())
@@ -563,7 +563,7 @@ def test_is_reboot_pending_test_raises_exception(self):
563563
def test_check_pro_client_prerequisites_should_return_false(self):
564564
package_manager = self.container.get('package_manager')
565565
backup_envlayer_platform_linux_distribution = LegacyEnvLayerExtensions.LegacyPlatform.linux_distribution
566-
LegacyEnvLayerExtensions.LegacyPlatform.linux_distribution = self.mock_linux_distribution_to_return_ubuntu_focal
566+
LegacyEnvLayerExtensions.LegacyPlatform.linux_distribution = self.mock_linux_distribution_to_return_ubuntu_oracular
567567
backup_ubuntu_pro_client_is_pro_working = package_manager.ubuntu_pro_client.is_pro_working
568568
package_manager.ubuntu_pro_client.is_pro_working = self.mock_is_pro_working_return_true
569569

0 commit comments

Comments
 (0)