diff --git a/patch-alarm/patch-alarm/patch_alarm/patch_alarm_manager.py b/patch-alarm/patch-alarm/patch_alarm/patch_alarm_manager.py index fce55a4f..abf1dcbe 100644 --- a/patch-alarm/patch-alarm/patch_alarm/patch_alarm_manager.py +++ b/patch-alarm/patch-alarm/patch_alarm/patch_alarm_manager.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2014-2024 Wind River Systems, Inc. +Copyright (c) 2014-2025 Wind River Systems, Inc. SPDX-License-Identifier: Apache-2.0 @@ -22,6 +22,8 @@ import software.config as cfg from software.constants import ENABLE_DEV_CERTIFICATE_PATCH_IDENTIFIER from software.software_functions import configure_logging from software.software_functions import LOG +from software.software_functions import SW_VERSION +from software.utils import get_major_release_version ################### # CONSTANTS @@ -90,7 +92,7 @@ class PatchAlarmDaemon(object): self._handle_patch_alarms() self._get_handle_failed_hosts() - def _handle_patch_alarms(self): + def _handle_patch_alarms(self): # pylint: disable=too-many-branches url = "http://%s/v1/release" % self.api_addr try: @@ -112,6 +114,9 @@ class PatchAlarmDaemon(object): raise_dip_alarm = True elif rel_metadata['state'] == 'unavailable': raise_obs_alarm = True + if 'sw_version' in rel_metadata and \ + get_major_release_version(rel_metadata['sw_version']) == SW_VERSION: + raise_obs_alarm = False if 'release_id' in rel_metadata and ENABLE_DEV_CERTIFICATE_PATCH_IDENTIFIER in rel_metadata['release_id']: raise_cert_alarm = True diff --git a/software/scripts/deploy-precheck b/software/scripts/deploy-precheck index 4b09393a..81bdff47 100644 --- a/software/scripts/deploy-precheck +++ b/software/scripts/deploy-precheck @@ -3,7 +3,7 @@ # # vim: tabstop=4 shiftwidth=4 softtabstop=4 # -# Copyright (c) 2023-2024 Wind River Systems, Inc. +# Copyright (c) 2023-2025 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -32,6 +32,7 @@ RC_SUCCESS = 0 RC_UNHEALTHY = 3 STATE_AVAILABLE = 'available' STATE_DEPLOYED = 'deployed' +STATE_UNAVAILABLE = 'unavailable' SYSTEM_MODE_SIMPLEX = "simplex" # the space needed is derived from the sum of snapshot sizes # defined in lvm_snapshot.LvmSnapshotManager.LOGICAL_VOLUMES @@ -88,18 +89,19 @@ class HealthCheck(object): # and implement patch precheck for subcloud def _check_required_patches_state(self, required_patches, allow_available=False): """ - Check if the required patches are in 'deployed' state, if allow_available is - True, the required_patches can be in 'available' state as well. + Check if the required patches are in 'deployed' or 'unavailable' state, + if allow_available is True, the required_patches can be in 'available' state as well. :param required_patches: list of patches to be checked :param allow_available: boolean if allow available patches :return: boolean indicating success/failure and list of patches - that are not in the 'deployed' or 'available' state + that are not in the 'deployed', 'unavailable' or 'available' state """ success = True releases = self._config.get("releases", "") releases_in_allowed_states = [] for release in json.loads(releases): if release['state'] == STATE_DEPLOYED or \ + release['state'] == STATE_UNAVAILABLE or \ (allow_available and release['state'] == STATE_AVAILABLE): releases_in_allowed_states.append(release) @@ -413,7 +415,7 @@ class PatchHealthCheck(HealthCheck): health_ok = True output = "" allow_available_patches = True - states_required = "deployed or available" + states_required = "deployed, unavailable or available" # check required patches for target release required_patches = self._get_required_patches() @@ -421,7 +423,7 @@ class PatchHealthCheck(HealthCheck): # 24.09.300 patch requires all previous patches to be deployed if self._target_release == "24.09.300": allow_available_patches = False - states_required = "deployed" + states_required = "deployed or unavailable" success, missing_patches = self._check_required_patches_state(required_patches, allow_available_patches) output += 'Required patches are %s: [%s]\n' \ diff --git a/software/software/constants.py b/software/software/constants.py index e57217f0..d6129bf1 100644 --- a/software/software/constants.py +++ b/software/software/constants.py @@ -222,6 +222,8 @@ COMMIT_TAG = "commit" COMMIT1_TAG = "commit1" NUMBER_OF_COMMITS_TAG = "number_of_commits" OSTREE_TAG = "ostree" +PREPATCHED_ISO_TAG = "prepatched_iso" +REQUIRES_TAG = "requires" # Flags INSTALL_LOCAL_FLAG = "/opt/software/.install_local" diff --git a/software/software/release_data.py b/software/software/release_data.py index 96beb334..b575c53b 100644 --- a/software/software/release_data.py +++ b/software/software/release_data.py @@ -1,7 +1,7 @@ # # SPDX-License-Identifier: Apache-2.0 # -# Copyright (c) 2024 Wind River Systems, Inc. +# Copyright (c) 2024-2025 Wind River Systems, Inc. # import os @@ -299,9 +299,10 @@ class SWReleaseCollection(object): @property def running_release(self): latest = None - for rel in self.iterate_releases_by_state(states.DEPLOYED): - if latest is None or rel.version_obj > latest.version_obj: - latest = rel + for state in (states.DEPLOYED, states.UNAVAILABLE): + for rel in self.iterate_releases_by_state(state): + if latest is None or rel.version_obj > latest.version_obj: + latest = rel return latest diff --git a/software/software/software_controller.py b/software/software/software_controller.py index 9cf80655..92207cf4 100644 --- a/software/software/software_controller.py +++ b/software/software/software_controller.py @@ -2019,11 +2019,11 @@ class PatchController(PatchService): :return: List of releases in the order for applying """ - deployed_releases_id = [] + deployed_or_unavailable_releases_id = [] preinstalled_patches = [] for rel in self.release_collection.iterate_releases(): - if rel.state == states.DEPLOYED: - deployed_releases_id.append(rel.id) + if rel.state == states.DEPLOYED or rel.state == states.UNAVAILABLE: + deployed_or_unavailable_releases_id.append(rel.id) if rel.prepatched_iso: preinstalled_patches = rel.preinstalled_patches @@ -2037,7 +2037,7 @@ class PatchController(PatchService): to_apply_releases = [ rel_id for rel_id in release_dependencies if f"-{running_release_sw_version}." in rel_id and - rel_id not in deployed_releases_id + preinstalled_patches + rel_id not in deployed_or_unavailable_releases_id + preinstalled_patches ] to_apply_releases.sort() @@ -3450,6 +3450,12 @@ class PatchController(PatchService): "'software deploy show'\n" % deployment_list elif operation == constants.REMOVE: + if deploy_release.state == states.UNAVAILABLE: + msg = "Cannot go back to an unavailable release" + LOG.error(msg) + msg_error += msg + "\n" + return dict(info=msg_info, warning=msg_warning, error=msg_error) + collect_current_load_for_hosts(deploy_sw_version, hostname=hostname) create_deploy_hosts(hostname=hostname) deployment_list = self.release_remove_order(deployment, running_release.id, running_release.sw_version) @@ -3477,8 +3483,8 @@ class PatchController(PatchService): LOG.error(msg) msg_error += msg + "\n" unremovable_verification = False - elif release.state == states.COMMITTED: - msg = "Release %s is committed and cannot be removed" % release_id + elif release.state == states.COMMITTED or release.state == states.UNAVAILABLE: + msg = "Release %s is %s and cannot be removed" % (release_id, release.state) LOG.error(msg) msg_error += msg + "\n" unremovable_verification = False @@ -3697,6 +3703,47 @@ class PatchController(PatchService): if release.sw_version == major_release: unavailable_releases.append(release.id) ReleaseState(release_ids=unavailable_releases).replaced() + else: + # After deploy a patch, delete previous unavailable releases + # from current major release, if they exist (subcloud prestage case) + unavailable_releases = list(self.release_collection.iterate_releases_by_state(states.UNAVAILABLE)) + if unavailable_releases: + for unavailable in unavailable_releases: + if unavailable.sw_version == major_release: + LOG.info("Deleting unavailable %s from current major release" % unavailable.id) + + try: + os.remove("%s/%s-metadata.xml" % (states.UNAVAILABLE_DIR, unavailable.id)) + except OSError: + LOG.error("Error when deleting %s metadata") + + reload_release_data() + + # Change the lowest deployed release metadata to prepatched + # Applied releases are all from current major release wich commits are deployed in sysroot + applied_releases_states = {states.DEPLOYED, states.DEPLOYING, states.COMMITTED} + for release in self.release_collection.iterate_releases(): + if release.sw_version == major_release and release.state in applied_releases_states: + LOG.info("Changing %s metadata to prepatched" % release.id) + + metadata_dir = states.RELEASE_STATE_TO_DIR_MAP[release.state] + metadata_file = "%s/%s-metadata.xml" % (metadata_dir, release.id) + + tree = ET.parse(metadata_file) + root = tree.getroot() + + self.add_text_tag_to_xml(root, constants.PREPATCHED_ISO_TAG, "Y") + requires_tag = root.find(constants.REQUIRES_TAG) + if requires_tag is not None: + root.remove(requires_tag) + + ET.indent(tree, ' ') + with open(metadata_file, "wb") as outfile: + tree = ET.tostring(root) + outfile.write(tree) + + # Run just for the first (lowest) release + break # Set deploying releases to deployed state. deploying_release_state.deploy_completed()