Files
distcloud/distributedcloud/dcmanager/orchestrator/states/patch/updating_patches.py
Hugo Brito 33549facb2 Add patch file support to patch orchestration
This commit adds a new parameter (patch) to the patch orchestration,
allowing the upload and apply of a specific patch file to a subcloud.
This change is essencial for enabling the new USM feature on subclouds
running older version.

Test Plan:

PASS: Fail if perform patch orchestation using --patch parameter
      with the subcloud and systemcontroller with the same version.
PASS: Perform patch orchestration using --patch parameter
- The patch should be uploaded, applied and installed to the subcloud
PASS: Perform patch orchestration using --patch and --upload-only
- The patch should be uploaded to the subcloud

Obs.:
1. Tests were performed without the patch being applied to the
systemcontroller
2. Tests were performed with subcloud in-sync and out-of-sync

Story: 2010676
Task: 50012

Change-Id: I7eb2940c708668b17ff93977b5622c3cff4cb3da
Signed-off-by: Hugo Brito <hugo.brito@windriver.com>
2024-05-09 15:31:25 -03:00

198 lines
8.5 KiB
Python

#
# Copyright (c) 2023-2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import os
import time
from dccommon.drivers.openstack import patching_v1
from dcmanager.common import consts
from dcmanager.common.exceptions import StrategyStoppedException
from dcmanager.orchestrator.states.base import BaseState
# Max time: 1 minute = 6 queries x 10 seconds between
DEFAULT_MAX_QUERIES = 6
DEFAULT_SLEEP_DURATION = 10
class UpdatingPatchesState(BaseState):
"""Patch orchestration state for updating patches"""
def __init__(self, region_name):
super(UpdatingPatchesState, self).__init__(
next_state=consts.STRATEGY_STATE_CREATING_VIM_PATCH_STRATEGY,
region_name=region_name)
self.max_queries = DEFAULT_MAX_QUERIES
self.sleep_duration = DEFAULT_SLEEP_DURATION
self.region_one_patches = None
self.region_one_applied_patch_ids = None
def set_job_data(self, job_data):
"""Store an orch_thread job data object"""
self.region_one_patches = job_data.region_one_patches
self.region_one_applied_patch_ids = job_data.region_one_applied_patch_ids
self.extra_args = job_data.extra_args
def upload_patch(self, patch_file, strategy_step):
"""Upload a patch file to the subcloud"""
if not os.path.isfile(patch_file):
message = f"Patch file {patch_file} is missing"
self.error_log(strategy_step, message)
raise Exception(message)
self.get_patching_client(self.region_name).upload([patch_file])
if self.stopped():
self.info_log(strategy_step, "Exiting because task is stopped")
raise StrategyStoppedException()
def perform_state_action(self, strategy_step):
"""Update patches in this subcloud"""
self.info_log(strategy_step, "Updating patches")
upload_only = self.extra_args.get(consts.EXTRA_ARGS_UPLOAD_ONLY)
patch_file = self.extra_args.get(consts.EXTRA_ARGS_PATCH)
# Retrieve all subcloud patches
try:
subcloud_patches = self.get_patching_client(self.region_name).query()
except Exception:
message = ("Cannot retrieve subcloud patches. Please see logs for "
"details.")
self.exception_log(strategy_step, message)
raise Exception(message)
subcloud_patch_ids = subcloud_patches.keys()
# If a patch file is provided, upload and apply without checking RegionOne
# patches
if patch_file:
self.info_log(
strategy_step,
f"Patch {patch_file} will be uploaded and applied to subcloud"
)
patch = os.path.basename(patch_file)
patch_id = os.path.splitext(patch)[0]
# raise Exception(subcloud_patch_ids)
if patch_id in subcloud_patch_ids:
message = f"Patch {patch_id} is already present in subcloud."
self.info_log(strategy_step, message)
else:
self.upload_patch(patch_file, strategy_step)
if upload_only:
self.info_log(
strategy_step,
f"{consts.EXTRA_ARGS_UPLOAD_ONLY} option enabled, skipping "
f"execution. Forward to state: {consts.STRATEGY_STATE_COMPLETE}",
)
return consts.STRATEGY_STATE_COMPLETE
self.get_patching_client(self.region_name).apply([patch_id])
else:
patches_to_upload = []
patches_to_apply = []
patches_to_remove = []
# RegionOne applied patches not present on the subcloud needs to
# be uploaded and applied to the subcloud
for patch_id in self.region_one_applied_patch_ids:
if patch_id not in subcloud_patch_ids:
self.info_log(strategy_step, "Patch %s missing from subloud " %
patch_id)
patches_to_upload.append(patch_id)
patches_to_apply.append(patch_id)
# Check that all applied patches in subcloud match RegionOne
if not upload_only:
for patch_id in subcloud_patch_ids:
repostate = subcloud_patches[patch_id]["repostate"]
if repostate == patching_v1.PATCH_STATE_APPLIED:
if patch_id not in self.region_one_applied_patch_ids:
self.info_log(strategy_step,
"Patch %s will be removed from subcloud " %
patch_id)
patches_to_remove.append(patch_id)
elif repostate == patching_v1.PATCH_STATE_COMMITTED:
if patch_id not in self.region_one_applied_patch_ids:
message = ("Patch %s is committed in subcloud but "
"not applied in SystemController" % patch_id)
self.warn_log(strategy_step, message)
raise Exception(message)
elif repostate == patching_v1.PATCH_STATE_AVAILABLE:
if patch_id in self.region_one_applied_patch_ids:
patches_to_apply.append(patch_id)
else:
# This patch is in an invalid state
message = ("Patch %s in subcloud is in an unexpected state: "
"%s" % (patch_id, repostate))
self.warn_log(strategy_step, message)
raise Exception(message)
if patches_to_upload:
self.info_log(strategy_step, "Uploading patches %s to subcloud" %
patches_to_upload)
for patch in patches_to_upload:
patch_sw_version = self.region_one_patches[patch]["sw_version"]
patch_file = "%s/%s/%s.patch" % (consts.PATCH_VAULT_DIR,
patch_sw_version, patch)
self.upload_patch(patch_file, strategy_step)
if upload_only:
self.info_log(strategy_step, "%s option enabled, skipping forward"
" to state:(%s)" % (consts.EXTRA_ARGS_UPLOAD_ONLY,
consts.STRATEGY_STATE_COMPLETE))
return consts.STRATEGY_STATE_COMPLETE
if patches_to_remove:
self.info_log(strategy_step, "Removing patches %s from subcloud" %
patches_to_remove)
self.get_patching_client(self.region_name).remove(patches_to_remove)
if patches_to_apply:
self.info_log(strategy_step, "Applying patches %s to subcloud" %
patches_to_apply)
self.get_patching_client(self.region_name).apply(patches_to_apply)
# Now that we have applied/removed/uploaded patches, we need to give
# the patch controller on this subcloud time to determine whether
# each host on that subcloud is patch current.
wait_count = 0
while True:
subcloud_hosts = self.get_patching_client(self.region_name).\
query_hosts()
self.debug_log(strategy_step,
"query_hosts for subcloud returned %s" %
subcloud_hosts)
for host in subcloud_hosts:
if host["interim_state"]:
# This host is not yet ready.
self.debug_log(strategy_step,
"Host %s in subcloud in interim state" %
host["hostname"])
break
else:
# All hosts in the subcloud are updated
break
wait_count += 1
if wait_count >= self.max_queries:
# We have waited too long.
# We log a warning but do not fail the step
message = ("Applying patches to subcloud "
"taking too long to recover. "
"Continuing..")
self.warn_log(strategy_step, message)
break
if self.stopped():
self.info_log(strategy_step, "Exiting because task is stopped")
raise StrategyStoppedException()
# Delay between queries
time.sleep(self.sleep_duration)
return self.next_state