Skip to content

Feature: [Ubuntu, no-CVM] Adding reboot, hibernate checks and aligning all pre-reqs together#363

Open
rane-rajasi wants to merge 3 commits into
masterfrom
rarane/certupdate/canonical_specialcases
Open

Feature: [Ubuntu, no-CVM] Adding reboot, hibernate checks and aligning all pre-reqs together#363
rane-rajasi wants to merge 3 commits into
masterfrom
rarane/certupdate/canonical_specialcases

Conversation

@rane-rajasi

Copy link
Copy Markdown
Contributor

This PR defines all pre-requisites for updating certificates in Canonical VMs, some of which were already existing and are now re-aligned in appropriate sections and some are added as new.

The pre-reqs for certificate updates are:

  1. Should not be a CVM
  2. Hibernation should be turned off. If it is enabled, we report an error and do not update any certificates
  3. Should not contain latest certificates already
  4. System uptime should not be more than 7 days, if it is, we issue a reboot if permitted"""

Pending:

  • In-VM testing

@rane-rajasi rane-rajasi requested review from a team, kjohn-msft, michellemcdaniel and najams and removed request for a team July 10, 2026 22:43
@rane-rajasi rane-rajasi requested a review from kjohn-msft as a code owner July 10, 2026 22:43
@rane-rajasi rane-rajasi requested a review from najams as a code owner July 10, 2026 22:43
@rane-rajasi rane-rajasi requested review from vbonu16 and yashnap July 10, 2026 22:43
@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.75510% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.05%. Comparing base (81ec7e5) to head (c7c8b47).

Files with missing lines Patch % Lines
...rc/core/src/package_managers/Dnf5PackageManager.py 50.00% 2 Missing ⚠️
src/core/src/package_managers/PackageManager.py 90.00% 2 Missing ⚠️
...rc/core/src/package_managers/TdnfPackageManager.py 50.00% 2 Missing ⚠️
src/core/src/package_managers/YumPackageManager.py 50.00% 2 Missing ⚠️
.../core/src/package_managers/ZypperPackageManager.py 50.00% 2 Missing ⚠️
src/core/tests/Test_AptitudePackageManager.py 99.44% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #363      +/-   ##
==========================================
- Coverage   94.08%   94.05%   -0.04%     
==========================================
  Files         109      109              
  Lines       20099    20351     +252     
==========================================
+ Hits        18911    19141     +230     
- Misses       1188     1210      +22     
Flag Coverage Δ
python27 94.05% <97.75%> (-0.04%) ⬇️
python312 94.05% <97.75%> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the UEFI certificate update flow (used during default patching) by enforcing additional pre-requisites (CVM exclusion, hibernation-off, latest-certs check, and uptime-based reboot handling) and updating unit tests accordingly.

Changes:

  • Adds prerequisite gating in PatchInstaller before attempting UEFI certificate updates, including reboot/hibernate/latest-certs checks.
  • Adds Apt-specific implementations for uptime and hibernation detection to support the new prerequisites.
  • Expands test coverage to validate prerequisite branching and status/error recording behavior.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/core/src/core_logic/PatchInstaller.py Adds certificate-update prerequisite checks and records status/errors for prerequisite failures and update failures.
src/core/src/package_managers/AptitudePackageManager.py Implements uptime-based reboot check and hibernation-state detection for cert-update prerequisites.
src/core/src/package_managers/PackageManager.py Refactors certificate update entrypoints and adds shared mokutil availability gating for cert checks.
src/core/src/package_managers/YumPackageManager.py Adds stub implementations for new certificate prerequisite APIs.
src/core/src/package_managers/TdnfPackageManager.py Adds stub implementations for new certificate prerequisite APIs.
src/core/src/package_managers/ZypperPackageManager.py Adds stub implementations for new certificate prerequisite APIs.
src/core/src/package_managers/Dnf5PackageManager.py Adds stub implementations for new certificate prerequisite APIs.
src/core/tests/Test_PatchInstaller.py Adds/updates tests to validate prerequisite ordering and installation-status error recording.
src/core/tests/Test_AptitudePackageManager.py Adds/updates tests for mokutil/cert-check behavior and uptime-based reboot decisioning.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

self.status_handler.add_error_to_status(error_msg, Constants.PatchOperationErrorCodes.CERTIFICATE_UPDATE)
raise Exception(error_msg, "[{0}]".format(Constants.ERROR_ADDED_TO_STATUS))
def ensure_mokutil_available_for_cert_checks(self):
# type: (bool) -> bool
Comment on lines 531 to +534
def are_latest_certs_present(self):
if not self.ensure_mokutil_available_for_cert_checks():
return False

Comment on lines +984 to +986
if code != self.apt_exitcode_ok:
self.composite_logger.log_debug('[APM][Certs] Unable to determine hibernation state for cert update. Assuming disabled. [Code={0}][Output={1}]'.format(str(code), out))
return False
@@ -1130,5 +1130,13 @@ def try_install_mokutil(self):
def try_update_certs(self):
""" Attempts to update certificate status """
pass
@@ -773,5 +773,13 @@ def try_install_mokutil(self):
def try_update_certs(self):
""" Attempts to update certificate status """
pass
@@ -882,5 +882,13 @@ def try_install_mokutil(self):
def try_update_certs(self):
""" Attempts to update certificate status """
pass
@@ -718,4 +718,12 @@ def try_install_mokutil(self):
def try_update_certs(self):
""" Attempts to update certificate status """
pass
try:
is_confidential_vm, detection_details = self.env_layer.detect_confidential_vm()
except Exception as e:
self.composite_logger.log_warning("Unable to determine whether the VM is a Confidential VM before attempting the UEFI certificate update. Continuing with patch installation... [Error: {0}]".format(str(e)))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code works, but log is ambiguous (true, when non-determinable).

And the function name thus becomes misleading. What you can only know for sure then is is_definitely_not_cvm.

If it's cvm, false, if non-determinable, false. And log for reason why it's false should be clear.

Calling function then handles the additional negation appropriately.

Comment on lines +105 to +107
self.get_uptime_seconds_cmd = "cat /proc/uptime"
self.get_hibernation_state_cmd = "cat /sys/power/disk"
self.pre_cert_reboot_uptime_threshold_seconds = 7 * 24 * 60 * 60

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for this PR - these aren't specific to this class. Ignore for the PR but note on what needs to be done later.

Comment on lines +963 to +974
def should_reboot_before_cert_update(self):
# type: () -> bool
"""Return True when certificate update should be preceded by a reboot."""
uptime_seconds = self.__get_vm_uptime_seconds()
if uptime_seconds is None:
# Best-effort check: do not block cert flow if uptime cannot be determined.
return False

requires_reboot = uptime_seconds > self.pre_cert_reboot_uptime_threshold_seconds
self.composite_logger.log_debug("[APM][Certs] Pre-cert reboot check. [UptimeInSeconds={0}][ThresholdInSeconds={1}][RequiresReboot={2}]"
.format(str(uptime_seconds), str(self.pre_cert_reboot_uptime_threshold_seconds), str(requires_reboot)))
return requires_reboot

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a specific reason why this function exists - the nuance for why is lost in comments (or absence thereof).


def can_continue_cert_update_after_reboot_check(self):
# type: () -> bool
"""Attempts pre-cert reboot when required.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it's the naming of this function or something else, but it feels like this function is doing too many things: saying it's ok to do the cert update and also doing the reboot. I'd prefer if it was either made more clear in the function name that this will attempt to perform an update if required or to have the reboot side effect moved to a different function.

@kjohn-msft kjohn-msft left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review code coverage + minor comments

return self.package_install_expected_avg_time_in_seconds

# region Update certificates in factory defaults
def should_reboot_before_cert_update(self):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if this could be renamed to "is_reboot_required_before_cert_update"

@kjohn-msft kjohn-msft added the feature New feature or request label Jul 10, 2026
if cmd.find(self.runtime.package_manager.fwupd_refresh_cmd) > -1:
return 1, "Error"
return 0, ""
self.assertTrue(cmd.find(self.runtime.package_manager.fwupd_refresh_cmd) > -1)
return True

def mock_run_command_output_uptime_above_threshold(self, cmd, no_output=False, chk_err=True):
self.assertTrue(cmd.find(self.runtime.package_manager.get_uptime_seconds_cmd) > -1)
return 0, "700000.12 123.45"

def mock_run_command_output_uptime_below_threshold(self, cmd, no_output=False, chk_err=True):
self.assertTrue(cmd.find(self.runtime.package_manager.get_uptime_seconds_cmd) > -1)

def mock_run_command_output_uptime_cmd_fails(self, cmd, no_output=False, chk_err=True):
"""Mock run_command_output to simulate uptime command failure"""
self.assertTrue(cmd.find(self.runtime.package_manager.get_uptime_seconds_cmd) > -1)

def mock_run_command_output_uptime_empty_output(self, cmd, no_output=False, chk_err=True):
"""Mock run_command_output to simulate uptime command returning empty output"""
self.assertTrue(cmd.find(self.runtime.package_manager.get_uptime_seconds_cmd) > -1)

def mock_run_command_output_uptime_invalid_output(self, cmd, no_output=False, chk_err=True):
"""Mock run_command_output to simulate uptime command returning unparseable output"""
self.assertTrue(cmd.find(self.runtime.package_manager.get_uptime_seconds_cmd) > -1)

def mock_run_command_output_hibernation_enabled(self, cmd, no_output=False, chk_err=True):
"""Mock run_command_output to simulate hibernation enabled state"""
self.assertTrue(cmd.find(self.runtime.package_manager.get_hibernation_state_cmd) > -1)

def mock_run_command_output_hibernation_disabled(self, cmd, no_output=False, chk_err=True):
"""Mock run_command_output to simulate hibernation disabled state"""
self.assertTrue(cmd.find(self.runtime.package_manager.get_hibernation_state_cmd) > -1)

def mock_run_command_output_hibernation_cmd_fails(self, cmd, no_output=False, chk_err=True):
"""Mock run_command_output to simulate hibernation check command failure"""
self.assertTrue(cmd.find(self.runtime.package_manager.get_hibernation_state_cmd) > -1)

def mock_run_command_output_hibernation_empty_output(self, cmd, no_output=False, chk_err=True):
"""Mock run_command_output to simulate empty output for hibernation check"""
self.assertTrue(cmd.find(self.runtime.package_manager.get_hibernation_state_cmd) > -1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants