Merge "Change --patch to patch_id in Patch Orch legacy"

This commit is contained in:
Zuul 2024-07-30 21:35:19 +00:00 committed by Gerrit Code Review
commit 1e63839869
7 changed files with 117 additions and 49 deletions

View File

@ -13,15 +13,16 @@
# under the License.
#
from oslo_log import log
import json
from nfv_client.openstack import rest_api
from nfv_client.openstack import sw_update
from oslo_log import log
from dccommon import consts
from dccommon.drivers import base
from dccommon import exceptions
LOG = log.getLogger(__name__)
STRATEGY_NAME_FW_UPDATE = "fw-update"
@ -122,6 +123,20 @@ class VimClient(base.DriverBase):
"""Create orchestration strategy"""
url = self.endpoint
# TODO(nicodemos): Remove once sw-patch is deprecated
# Use the REST Api directly to the subcloud to create the strategy for legacy
# patch orchestration
if strategy_name == STRATEGY_NAME_SW_PATCH:
return self._create_strategy_sw_patch(
strategy_name,
default_instance_action,
storage_apply_type,
worker_apply_type,
max_parallel_worker_hosts,
alarm_restrictions,
)
strategy = sw_update.create_strategy(
self.token,
url,
@ -136,7 +151,7 @@ class VimClient(base.DriverBase):
username=self.username,
user_domain_name=self.user_domain_name,
tenant=self.tenant,
**kwargs
**kwargs,
)
if not strategy:
raise Exception("Strategy:(%s) creation failed" % strategy_name)
@ -144,6 +159,45 @@ class VimClient(base.DriverBase):
LOG.debug("Strategy created: %s" % strategy)
return strategy
# TODO(nicodemos): Delete this method once sw-patch is deprecated
def _create_strategy_sw_patch(
self,
strategy_name,
default_instance_action,
storage_apply_type,
worker_apply_type,
max_parallel_worker_hosts,
alarm_restrictions,
):
api_cmd = self.endpoint + "/api/orchestration/%s/strategy" % strategy_name
api_cmd_headers = dict()
api_cmd_headers["Content-Type"] = "application/json"
api_cmd_headers["X-User"] = self.username
api_cmd_headers["X-Tenant"] = self.tenant
api_cmd_headers["X-User-Domain-Name"] = self.user_domain_name
api_cmd_headers["X-Auth-Token"] = self.token
api_cmd_payload = dict()
api_cmd_payload["controller-apply-type"] = APPLY_TYPE_SERIAL
api_cmd_payload["swift-apply-type"] = APPLY_TYPE_IGNORE
api_cmd_payload["default-instance-action"] = default_instance_action
api_cmd_payload["storage-apply-type"] = storage_apply_type
api_cmd_payload["worker-apply-type"] = worker_apply_type
if max_parallel_worker_hosts is not None:
api_cmd_payload["max-parallel-worker-hosts"] = max_parallel_worker_hosts
api_cmd_payload["alarm-restrictions"] = alarm_restrictions
response = rest_api.request(
self.token, "POST", api_cmd, api_cmd_headers, json.dumps(api_cmd_payload)
)
# Check if the response is valid and update response value
if response.get("strategy"):
response["strategy"]["build-phase"]["response"] = "success"
return sw_update._get_strategy_object_from_response(response)
def get_strategy(self, strategy_name, raise_error_if_missing=True):
"""Get the current orchestration strategy"""

View File

@ -168,14 +168,19 @@ class SwUpdateStrategyController(object):
]:
pecan.abort(400, _("subcloud-apply-type invalid"))
patch_file = payload.get("patch")
if strategy_type == consts.SW_UPDATE_TYPE_PATCH:
if not patch_file:
message = (
f"patch parameter is required for {strategy_type} strategy."
)
pecan.abort(400, _(message))
elif not os.path.isfile(patch_file):
patch_id = payload.get("patch_id")
if strategy_type == consts.SW_UPDATE_TYPE_PATCH and not patch_id:
message = (
f"patch_id parameter is required for {strategy_type} strategy."
)
pecan.abort(400, _(message))
elif patch_id:
patch_file = (
f"{consts.PATCH_VAULT_DIR}/{consts.PATCHING_SW_VERSION}/"
f"{patch_id}.patch"
)
if not os.path.isfile(patch_file):
message = f"Patch file {patch_file} is missing."
pecan.abort(400, _(message))

View File

@ -343,7 +343,10 @@ EXTRA_ARGS_FORCE = "force"
# extra_args for patching
EXTRA_ARGS_UPLOAD_ONLY = "upload-only"
EXTRA_ARGS_PATCH = "patch"
EXTRA_ARGS_PATCH_ID = "patch_id"
# sw_version supported for patching
PATCHING_SW_VERSION = "22.12"
# extra_args for software
EXTRA_ARGS_RELEASE_ID = "release_id"

View File

@ -4,7 +4,6 @@
# SPDX-License-Identifier: Apache-2.0
#
import os
import time
from dcmanager.common import consts
@ -34,11 +33,6 @@ class UpdatingPatchesState(BaseState):
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.info_log(
strategy_step,
f"Patch {patch_file} will be uploaded to subcloud",
@ -51,9 +45,6 @@ class UpdatingPatchesState(BaseState):
def perform_state_action(self, strategy_step):
"""Update patches in this subcloud"""
self.info_log(strategy_step, "Updating patches")
extra_args = utils.get_sw_update_strategy_extra_args(self.context)
upload_only = extra_args.get(consts.EXTRA_ARGS_UPLOAD_ONLY)
patch_file = extra_args.get(consts.EXTRA_ARGS_PATCH)
# Retrieve all subcloud patches
try:
@ -65,8 +56,12 @@ class UpdatingPatchesState(BaseState):
subcloud_patch_ids = subcloud_patches.keys()
patch = os.path.basename(patch_file)
patch_id = os.path.splitext(patch)[0]
extra_args = utils.get_sw_update_strategy_extra_args(self.context)
patch_id = extra_args.get(consts.EXTRA_ARGS_PATCH_ID)
patch_file = (
f"{consts.PATCH_VAULT_DIR}/{consts.PATCHING_SW_VERSION}/"
f"{patch_id}.patch"
)
if patch_id in subcloud_patch_ids:
message = f"Patch {patch_id} is already present in the subcloud."
@ -74,6 +69,8 @@ class UpdatingPatchesState(BaseState):
else:
self.upload_patch(patch_file, strategy_step)
upload_only = extra_args.get(consts.EXTRA_ARGS_UPLOAD_ONLY)
if upload_only:
self.info_log(
strategy_step,
@ -85,7 +82,7 @@ class UpdatingPatchesState(BaseState):
# Apply the patch to the subcloud
self.info_log(
strategy_step,
f"Patch {patch_file} will be applied to subcloud",
f"Patch {patch_id} will be applied to subcloud",
)
self.get_patching_client(self.region_name).apply([patch_id])

View File

@ -38,5 +38,5 @@ class PatchStrategyValidator(StrategyValidationBase):
upload_only_bool = payload.get(consts.EXTRA_ARGS_UPLOAD_ONLY) == "true"
return {
consts.EXTRA_ARGS_UPLOAD_ONLY: upload_only_bool,
consts.EXTRA_ARGS_PATCH: payload.get(consts.EXTRA_ARGS_PATCH),
consts.EXTRA_ARGS_PATCH_ID: payload.get(consts.EXTRA_ARGS_PATCH_ID),
}

View File

@ -14,12 +14,12 @@ from dcmanager.tests.unit.common import fake_strategy
from dcmanager.tests.unit.orchestrator.states.patch.test_base import TestPatchState
SUBCLOUD_NO_USM_PATCHES = {
"DC.3": {
"stx-9.1": {
"sw_version": "sxt-9.0",
"repostate": "Available",
"patchstate": "Partial-Remove",
},
"DC.6": {
"stx-9.2": {
"sw_version": "sxt-9.0",
"repostate": "Applied",
"patchstate": "Partial-Apply",
@ -27,18 +27,20 @@ SUBCLOUD_NO_USM_PATCHES = {
}
SUBCLOUD_USM_PATCHES = {
"usm": {
"sw_version": "stx8",
"stx-9.1": {
"sw_version": "stx-9.0",
"repostate": "Available",
"patchstate": "Available",
},
"DC.3": {
"sw_version": "20.12",
"stx-usm-9.2": {
"sw_version": "stx-9.0",
"repostate": "Available",
"patchstate": "Partial-Remove",
},
}
DC_VAULT_PATCH_DIR = "/opt/dc-vault/patches/22.12/"
@mock.patch(
"dcmanager.orchestrator.states.patch.updating_patches.DEFAULT_MAX_QUERIES", 3
@ -67,44 +69,46 @@ class TestUpdatingPatchesStage(TestPatchState):
self.patching_client.apply = mock.MagicMock()
self.patching_client.query_hosts = mock.MagicMock()
def _create_fake_strategy(self, upload_only=False, patch_file=None):
def _create_fake_strategy(self, upload_only=False, patch_id=None):
extra_args = {
consts.EXTRA_ARGS_UPLOAD_ONLY: upload_only,
consts.EXTRA_ARGS_PATCH: patch_file,
consts.EXTRA_ARGS_PATCH_ID: patch_id,
}
return fake_strategy.create_fake_strategy(
self.ctx, self.DEFAULT_STRATEGY_TYPE, extra_args=extra_args
)
@mock.patch.object(os_path, "isfile")
def test_update_subcloud_patches_patch_file_success(self, mock_os_path_isfile):
"""Test update_patches where the API call succeeds patch parameter."""
def test_update_subcloud_patches_patch_id_success(self, mock_os_path_isfile):
"""Test update_patches where the API call succeeds with patch_id parameter."""
mock_os_path_isfile.return_value = True
self.patching_client.query.side_effect = [SUBCLOUD_NO_USM_PATCHES]
self._create_fake_strategy(patch_file="usm.patch")
self._create_fake_strategy(patch_id="stx-usm-9.2")
# invoke the strategy state operation on the orch thread
self.worker.perform_state_action(self.strategy_step)
self.patching_client.upload.assert_called_with(["usm.patch"])
self.patching_client.upload.assert_called_with(
[DC_VAULT_PATCH_DIR + "stx-usm-9.2.patch"]
)
call_args, _ = self.patching_client.apply.call_args_list[0]
self.assertItemsEqual(["usm"], call_args[0])
self.assertItemsEqual(["stx-usm-9.2"], call_args[0])
# On success, the state should transition to the next state
self.assert_step_updated(self.strategy_step.subcloud_id, self.success_state)
self.assert_step_details(self.strategy_step.subcloud_id, "")
def test_update_subcloud_patches_patch_file_no_upload(self):
"""Test update_patches where the API call patch parameter is not uploaded."""
def test_update_subcloud_patches_patch_id_no_upload(self):
"""Test update_patches where the API using patch_id parameter isn't uploaded."""
self.patching_client.query.side_effect = [SUBCLOUD_USM_PATCHES]
self._create_fake_strategy(patch_file="usm.patch")
self._create_fake_strategy(patch_id="stx-usm-9.2")
# invoke the strategy state operation on the orch thread
self.worker.perform_state_action(self.strategy_step)
@ -112,7 +116,7 @@ class TestUpdatingPatchesStage(TestPatchState):
self.patching_client.upload.assert_not_called()
call_args, _ = self.patching_client.apply.call_args_list[0]
self.assertItemsEqual(["usm"], call_args[0])
self.assertItemsEqual(["stx-usm-9.2"], call_args[0])
# On success, the state should transition to the next state
self.assert_step_updated(self.strategy_step.subcloud_id, self.success_state)
@ -120,21 +124,23 @@ class TestUpdatingPatchesStage(TestPatchState):
self.assert_step_details(self.strategy_step.subcloud_id, "")
@mock.patch.object(os_path, "isfile")
def test_update_subcloud_patches_patch_file_upload_only_success(
def test_update_subcloud_patches_patch_id_upload_only_success(
self, mock_os_path_isfile
):
"""Test update_patches where the API call succeeds with patch/upload only."""
"""Test update_patches where the API call succeeds with patch_id/upload only."""
mock_os_path_isfile.return_value = True
self.patching_client.query.side_effect = [SUBCLOUD_NO_USM_PATCHES]
self._create_fake_strategy(upload_only=True, patch_file="usm.patch")
self._create_fake_strategy(upload_only=True, patch_id="stx-usm-9.2")
# invoke the strategy state operation on the orch thread
self.worker.perform_state_action(self.strategy_step)
self.patching_client.upload.assert_called_with(["usm.patch"])
self.patching_client.upload.assert_called_with(
[DC_VAULT_PATCH_DIR + "stx-usm-9.2.patch"]
)
self.patching_client.apply.assert_not_called()
self.assert_step_details(self.strategy_step.subcloud_id, "")
@ -149,12 +155,12 @@ class TestUpdatingPatchesStage(TestPatchState):
def test_updating_subcloud_patches_fails_when_stopped(
self, mock_os_path_isfile, mock_base_stopped
):
"""Test finish strategy fails when stopped"""
"""Test update_patches state fails when stopped"""
mock_os_path_isfile.return_value = True
self.patching_client.query.side_effect = [SUBCLOUD_NO_USM_PATCHES]
self._create_fake_strategy(upload_only=True, patch_file="usm.patch")
self._create_fake_strategy(upload_only=True, patch_id="stx-usm-9.2")
mock_base_stopped.return_value = True

View File

@ -39,7 +39,10 @@ class TestPatchValidator(
return self.validator
def _get_build_extra_args_payload(self):
return {consts.EXTRA_ARGS_UPLOAD_ONLY: True, consts.EXTRA_ARGS_PATCH: None}
return {
consts.EXTRA_ARGS_UPLOAD_ONLY: True,
consts.EXTRA_ARGS_PATCH_ID: "stx-9.1",
}
def _get_expected_extra_args(self):