Patches from current release in unavailable state
This change: 1) Allow a patch to be applied on top of an unavailable patch 2) Does not allow the system to go back to an unavailable patch 3) Does not raise obsolete alarm for unavailable patches in the current major release 4) After applying a patch on top of an unavailable patch, remove all the unavailable patches 5) Add the prepatched_iso and remove the requires tags from the lowest sysroot deployed patch Test-Plan: PASS: If a patch from the current major release is unavailable, does not raise the obsolete release alarm PASS: Apply a patch on top of an unavailable patch - Check the deployment entity is correct - Check the ostree commit is correct - Check the unavailable patch was deleted - Check the lowest applied patch contains the prepatched_iso and no requires tags in the metadata PASS: Should not allow removing a patch on top of an unavailable patch PASS: Should not allow removal of an unavailable patch PASS: Upgrade from 22.12 to 24.09.300 (with changes) Closes-Bug: 2119980 Change-Id: Iaf83a55af4c33a9d38eb569c7d614662d60d7a36 Signed-off-by: Lindley Vieira <lindley.vieira@windriver.com>
This commit is contained in:
@@ -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
|
||||
|
||||
|
@@ -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' \
|
||||
|
@@ -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"
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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()
|
||||
|
Reference in New Issue
Block a user