Add classes to orchestrator's strategy validation

This commit refactors dcmanager's orchestration logic to validate
strategies by replacing the conditionals for each strategy type, except
for prestage, by a validator class that is directly accessed.

Test plan:
1. PASS: Create a sw-deploy strategy in a subcloud with the endpoint
   type software in-sync and verify that a bad request is raised with
   the appropriate error message.
2. PASS: Create a sw-deploy strategy in a subcloud with the endpoint
   type software out-of-sync and verify that it finishes successfully.
3. PASS: Perform the same scenarios from 1 and 2 with the firmware
   strategy and endpoint type firmware.
4. PASS: Perform the same scenarios from 1 and 2 with the kubernetes
   strategy and endpoint type kubernetes.
5. PASS: Create a kubernetes strategy with the --force option and the
   kubernetes endpoint sync status as in-sync and verify that it
   finishes the creation successfully.
6. PASS: Perform the same scenarios from 1, 2 and 5 with the kube
   root-ca strategy and endpoint type kube root-ca.
7. PASS: Perform the same scenarios from 1 and 2 with the patch
   strategy and endpoint type patching.

Story: 2011106
Task: 50473

Change-Id: I683b19b3e3344d4800e7c3462daa88081aebe02d
Signed-off-by: Raphael Lima <Raphael.Lima@windriver.com>
This commit is contained in:
Raphael Lima
2024-06-27 12:35:09 -03:00
parent 07e8103ef9
commit b001c5ba0c
16 changed files with 543 additions and 57 deletions

View File

@@ -38,6 +38,24 @@ from dcmanager.orchestrator.kube_upgrade_orch_thread \
from dcmanager.orchestrator.patch_orch_thread import PatchOrchThread
from dcmanager.orchestrator.prestage_orch_thread import PrestageOrchThread
from dcmanager.orchestrator.software_orch_thread import SoftwareOrchThread
from dcmanager.orchestrator.validators.firmware_validator import (
FirmwareStrategyValidator
)
from dcmanager.orchestrator.validators.kube_root_ca_validator import (
KubeRootCaStrategyValidator
)
from dcmanager.orchestrator.validators.kubernetes_validator import (
KubernetesStrategyValidator
)
from dcmanager.orchestrator.validators.patch_validator import (
PatchStrategyValidator
)
from dcmanager.orchestrator.validators.prestage_validator import (
PrestageStrategyValidator
)
from dcmanager.orchestrator.validators.sw_deploy_validator import (
SoftwareDeployStrategyValidator
)
LOG = logging.getLogger(__name__)
@@ -89,6 +107,15 @@ class SwUpdateManager(manager.Manager):
self.strategy_lock, self.audit_rpc_client)
self.prestage_orch_thread.start()
self.strategy_validators = {
consts.SW_UPDATE_TYPE_SOFTWARE: SoftwareDeployStrategyValidator(),
consts.SW_UPDATE_TYPE_FIRMWARE: FirmwareStrategyValidator(),
consts.SW_UPDATE_TYPE_KUBERNETES: KubernetesStrategyValidator(),
consts.SW_UPDATE_TYPE_KUBE_ROOTCA_UPDATE: KubeRootCaStrategyValidator(),
consts.SW_UPDATE_TYPE_PATCH: PatchStrategyValidator(),
consts.SW_UPDATE_TYPE_PRESTAGE: PrestageStrategyValidator()
}
def stop(self):
# Stop (and join) the worker threads
@@ -313,7 +340,6 @@ class SwUpdateManager(manager.Manager):
for_sw_deploy = False
# Has the user specified a specific subcloud?
# todo(abailey): refactor this code to use classes
cloud_name = payload.get('cloud_name')
strategy_type = payload.get('type')
prestage_global_validated = False
@@ -326,62 +352,8 @@ class SwUpdateManager(manager.Manager):
resource='strategy',
msg=f'Subcloud {cloud_name} does not exist')
if strategy_type == consts.SW_UPDATE_TYPE_SOFTWARE:
subcloud_status = db_api.subcloud_status_get(
context, subcloud.id, dccommon_consts.ENDPOINT_TYPE_SOFTWARE)
if subcloud_status.sync_status == \
dccommon_consts.SYNC_STATUS_IN_SYNC:
raise exceptions.BadRequest(
resource='strategy',
msg=f'Subcloud {cloud_name} does not require deploy')
elif strategy_type == consts.SW_UPDATE_TYPE_FIRMWARE:
subcloud_status = db_api.subcloud_status_get(
context, subcloud.id, dccommon_consts.ENDPOINT_TYPE_FIRMWARE)
if subcloud_status.sync_status == \
dccommon_consts.SYNC_STATUS_IN_SYNC:
raise exceptions.BadRequest(
resource='strategy',
msg='Subcloud %s does not require firmware update'
% cloud_name)
elif strategy_type == consts.SW_UPDATE_TYPE_KUBERNETES:
if force:
# force means we do not care about the status
pass
else:
subcloud_status = db_api.subcloud_status_get(
context, subcloud.id,
dccommon_consts.ENDPOINT_TYPE_KUBERNETES)
if subcloud_status.sync_status == \
dccommon_consts.SYNC_STATUS_IN_SYNC:
raise exceptions.BadRequest(
resource='strategy',
msg='Subcloud %s does not require kubernetes update'
% cloud_name)
elif strategy_type == consts.SW_UPDATE_TYPE_KUBE_ROOTCA_UPDATE:
if force:
# force means we do not care about the status
pass
else:
subcloud_status = db_api.subcloud_status_get(
context, subcloud.id,
dccommon_consts.ENDPOINT_TYPE_KUBE_ROOTCA)
if subcloud_status.sync_status == \
dccommon_consts.SYNC_STATUS_IN_SYNC:
raise exceptions.BadRequest(
resource='strategy',
msg='Subcloud %s does not require kube rootca update'
% cloud_name)
elif strategy_type == consts.SW_UPDATE_TYPE_PATCH:
# Make sure subcloud requires patching
subcloud_status = db_api.subcloud_status_get(
context, subcloud.id, dccommon_consts.ENDPOINT_TYPE_PATCHING)
if subcloud_status.sync_status == \
dccommon_consts.SYNC_STATUS_IN_SYNC:
raise exceptions.BadRequest(
resource='strategy',
msg='Subcloud %s does not require patching' % cloud_name)
elif strategy_type == consts.SW_UPDATE_TYPE_PRESTAGE:
# TODO(rlima): move prestage to its validator
if strategy_type == consts.SW_UPDATE_TYPE_PRESTAGE:
# Do initial validation for subcloud
try:
prestage.global_prestage_validate(payload)
@@ -397,6 +369,12 @@ class SwUpdateManager(manager.Manager):
raise exceptions.BadRequest(resource='strategy',
msg=str(ex))
else:
self.strategy_validators[strategy_type].\
validate_strategy_requirements(
context, subcloud.id, subcloud.name, force
)
extra_args = None
# kube rootca update orchestration supports extra creation args
if strategy_type == consts.SW_UPDATE_TYPE_KUBE_ROOTCA_UPDATE:

View File

@@ -0,0 +1,50 @@
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Base class for dcmanager orchestrator's strategy validations.
"""
from oslo_log import log as logging
from dccommon import consts as dccommon_consts
from dcmanager.common import exceptions
from dcmanager.db import api as db_api
LOG = logging.getLogger(__name__)
class StrategyValidationBase(object):
"""Base class for strategy validation"""
def __init__(self):
self.accepts_force = False
def validate_strategy_requirements(
self, context, subcloud_id, subcloud_name, force=False
):
"""Validates the requirements for a strategy
:param context: request context object
:param subcloud_id: subcloud's id
:param subcloud_name: subcloud's name
:param force: if the strategy should be forced to execute
:raises BadRequest: if the requirements for the strategy are not met
"""
if self.accepts_force and force:
return
subcloud_status = db_api.subcloud_status_get(
context, subcloud_id, self.endpoint_type
)
if subcloud_status.sync_status == dccommon_consts.SYNC_STATUS_IN_SYNC:
raise exceptions.BadRequest(
resource='strategy', msg=(
f'Subcloud {subcloud_name} does not require '
f'{self.endpoint_type} update'
)
)

View File

@@ -0,0 +1,27 @@
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Class for firmware strategy validations.
It defines methods used in dcmanager orchestrator's to handle the strategy
by its type.
"""
from oslo_log import log as logging
from dccommon import consts as dccommon_consts
from dcmanager.orchestrator.validators.base import StrategyValidationBase
LOG = logging.getLogger(__name__)
class FirmwareStrategyValidator(StrategyValidationBase):
"""Class for firmware strategy validations"""
def __init__(self):
super().__init__()
self.endpoint_type = dccommon_consts.ENDPOINT_TYPE_FIRMWARE

View File

@@ -0,0 +1,28 @@
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Class for kube rootca strategy validations
It defines methods used in dcmanager orchestrator's to handle the strategy
by its type.
"""
from oslo_log import log as logging
from dccommon import consts as dccommon_consts
from dcmanager.orchestrator.validators.base import StrategyValidationBase
LOG = logging.getLogger(__name__)
class KubeRootCaStrategyValidator(StrategyValidationBase):
"""Class for kube rootca strategy validations"""
def __init__(self):
super().__init__()
self.endpoint_type = dccommon_consts.ENDPOINT_TYPE_KUBE_ROOTCA
self.accepts_force = True

View File

@@ -0,0 +1,28 @@
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Class for kubernetes strategy validations
It defines methods used in dcmanager orchestrator's to handle the strategy
by its type.
"""
from oslo_log import log as logging
from dccommon import consts as dccommon_consts
from dcmanager.orchestrator.validators.base import StrategyValidationBase
LOG = logging.getLogger(__name__)
class KubernetesStrategyValidator(StrategyValidationBase):
"""Class for kubernetes strategy validations"""
def __init__(self):
super().__init__()
self.endpoint_type = dccommon_consts.ENDPOINT_TYPE_KUBERNETES
self.accepts_force = True

View File

@@ -0,0 +1,27 @@
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Class for patch strategy validations
It defines methods used in dcmanager orchestrator's to handle the strategy
by its type.
"""
from oslo_log import log as logging
from dccommon import consts as dccommon_consts
from dcmanager.orchestrator.validators.base import StrategyValidationBase
LOG = logging.getLogger(__name__)
class PatchStrategyValidator(StrategyValidationBase):
"""Class for patch strategy validations"""
def __init__(self):
super().__init__()
self.endpoint_type = dccommon_consts.ENDPOINT_TYPE_PATCHING

View File

@@ -0,0 +1,28 @@
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Class for prestage strategy validations
It defines methods used in dcmanager orchestrator's to handle the strategy
by its type.
"""
from oslo_log import log as logging
from dcmanager.orchestrator.validators.base import StrategyValidationBase
LOG = logging.getLogger(__name__)
class PrestageStrategyValidator(StrategyValidationBase):
"""Class for prestage strategy validations"""
def __init__(self):
super().__init__()
self.endpoint_type = None
# TODO(rlima): move prestage validations here

View File

@@ -0,0 +1,27 @@
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Class for software deploy strategy validations
It defines methods used in dcmanager orchestrator's to handle the strategy
by its type.
"""
from oslo_log import log as logging
from dccommon import consts as dccommon_consts
from dcmanager.orchestrator.validators.base import StrategyValidationBase
LOG = logging.getLogger(__name__)
class SoftwareDeployStrategyValidator(StrategyValidationBase):
"""Class for software deploy strategy validations"""
def __init__(self):
super().__init__()
self.endpoint_type = dccommon_consts.ENDPOINT_TYPE_SOFTWARE

View File

@@ -175,6 +175,13 @@ class DCManagerTestCase(base.BaseTestCase):
self.mock_pecan_abort = mock_patch_object.start()
self.addCleanup(mock_patch_object.stop)
def _mock_db_api(self, target, wraps=None):
"""Mock db api's method"""
mock_patch_object = mock.patch.object(db_api, target, wraps=wraps)
self.mock_db_api = mock_patch_object.start()
self.addCleanup(mock_patch_object.stop)
def _mock_audit_rpc_client(self):
"""Mock rpc's manager audit client"""

View File

@@ -0,0 +1,34 @@
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Firmware strategy validation tests
"""
from dcmanager.db import api as db_api
from dcmanager.orchestrator.validators.firmware_validator import (
FirmwareStrategyValidator
)
from dcmanager.tests.base import DCManagerTestCase
from dcmanager.tests.unit.orchestrator.validators.validators_mixin import (
StrategyRequirementsMixin
)
class TestFirmwareValidator(DCManagerTestCase, StrategyRequirementsMixin):
"""Test class for firmware validator"""
def setUp(self):
super().setUp()
self._mock_db_api("subcloud_status_get", db_api.subcloud_status_get)
self.validator = FirmwareStrategyValidator()
def _get_mock_db_api(self):
return self.mock_db_api
def _get_validator(self):
return self.validator

View File

@@ -0,0 +1,34 @@
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Kube root-ca strategy validation tests
"""
from dcmanager.db import api as db_api
from dcmanager.orchestrator.validators.kube_root_ca_validator import (
KubeRootCaStrategyValidator
)
from dcmanager.tests.base import DCManagerTestCase
from dcmanager.tests.unit.orchestrator.validators.validators_mixin import (
StrategyRequirementsMixin
)
class TestKubeRootCaValidator(DCManagerTestCase, StrategyRequirementsMixin):
"""Test class for kube root-ca validator"""
def setUp(self):
super().setUp()
self._mock_db_api("subcloud_status_get", db_api.subcloud_status_get)
self.validator = KubeRootCaStrategyValidator()
def _get_mock_db_api(self):
return self.mock_db_api
def _get_validator(self):
return self.validator

View File

@@ -0,0 +1,34 @@
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Kubernetes strategy validation tests
"""
from dcmanager.db import api as db_api
from dcmanager.orchestrator.validators.kubernetes_validator import (
KubernetesStrategyValidator
)
from dcmanager.tests.base import DCManagerTestCase
from dcmanager.tests.unit.orchestrator.validators.validators_mixin import (
StrategyRequirementsMixin
)
class TestKubernetesValidator(DCManagerTestCase, StrategyRequirementsMixin):
"""Test class for kubernetes validator"""
def setUp(self):
super().setUp()
self._mock_db_api("subcloud_status_get", db_api.subcloud_status_get)
self.validator = KubernetesStrategyValidator()
def _get_mock_db_api(self):
return self.mock_db_api
def _get_validator(self):
return self.validator

View File

@@ -0,0 +1,34 @@
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Patch strategy validation tests
"""
from dcmanager.db import api as db_api
from dcmanager.orchestrator.validators.patch_validator import (
PatchStrategyValidator
)
from dcmanager.tests.base import DCManagerTestCase
from dcmanager.tests.unit.orchestrator.validators.validators_mixin import (
StrategyRequirementsMixin
)
class TestPatchValidator(DCManagerTestCase, StrategyRequirementsMixin):
"""Test class for patch validator"""
def setUp(self):
super().setUp()
self._mock_db_api("subcloud_status_get", db_api.subcloud_status_get)
self.validator = PatchStrategyValidator()
def _get_mock_db_api(self):
return self.mock_db_api
def _get_validator(self):
return self.validator

View File

@@ -0,0 +1,34 @@
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Prestage strategy validation tests
"""
from dcmanager.db import api as db_api
from dcmanager.orchestrator.validators.prestage_validator import (
PrestageStrategyValidator
)
from dcmanager.tests.base import DCManagerTestCase
from dcmanager.tests.unit.orchestrator.validators.validators_mixin import (
StrategyRequirementsMixin
)
class TestPrestageValidator(DCManagerTestCase, StrategyRequirementsMixin):
"""Test class for prestage validator"""
def setUp(self):
super().setUp()
self._mock_db_api("subcloud_status_get", db_api.subcloud_status_get)
self.validator = PrestageStrategyValidator()
def _get_mock_db_api(self):
return self.mock_db_api
def _get_validator(self):
return self.validator

View File

@@ -0,0 +1,34 @@
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Software deploy strategy validation tests
"""
from dcmanager.db import api as db_api
from dcmanager.orchestrator.validators.software_deploy_validator import (
SoftwareDeployStrategyValidator
)
from dcmanager.tests.base import DCManagerTestCase
from dcmanager.tests.unit.orchestrator.validators.validators_mixin import (
StrategyRequirementsMixin
)
class TestSoftwareDeployValidator(DCManagerTestCase, StrategyRequirementsMixin):
"""Test class for software deploy validator"""
def setUp(self):
super().setUp()
self._mock_db_api("subcloud_status_get", db_api.subcloud_status_get)
self.validator = SoftwareDeployStrategyValidator()
def _get_mock_db_api(self):
return self.mock_db_api
def _get_validator(self):
return self.validator

View File

@@ -0,0 +1,82 @@
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Test classes for generic strategy validation tests
"""
import abc
import mock
from dccommon import consts as dccommon_consts
from dcmanager.common import exceptions
from dcmanager.db import api as db_api
from dcmanager.tests.unit.common.fake_subcloud import create_fake_subcloud
class BaseMixin(object, metaclass=abc.ABCMeta):
"""Base mixin class to declare common methods"""
def setUp(self):
self.subcloud = create_fake_subcloud(self.ctx)
@abc.abstractmethod
def _get_mock_db_api(self):
"""Returns the mocked db api object"""
@abc.abstractmethod
def _get_validator(self):
"""Returns the validator object"""
class StrategyRequirementsMixin(BaseMixin):
"""Mixin class for validate_strategy_requirements method tests"""
def test_validate_strategy_requirements_suceeds(self):
"""Test validate_strategy_requirements succeeds"""
self._get_validator().validate_strategy_requirements(
self.ctx, self.subcloud.id, self.subcloud.name, False
)
self._get_mock_db_api().assert_called_once_with(
mock.ANY, self.subcloud.id, self._get_validator().endpoint_type
)
def test_validate_strategy_requirements_suceeds_with_force(self):
"""Test validate_strategy_requirements succeeds with force
If the strategy being tested does not accept force, it should execute
normally. If it does, the execution is skipped.
"""
self._get_validator().validate_strategy_requirements(
self.ctx, self.subcloud.id, self.subcloud.name, True
)
if self._get_validator().accepts_force:
self._get_mock_db_api().assert_not_called()
else:
self._get_mock_db_api().assert_called_once_with(
mock.ANY, self.subcloud.id, self._get_validator().endpoint_type
)
def test_validate_strategy_requirements_fails_with_sync_status_out_of_sync(self):
"""Test validate_strategy_requirements fails with sync status out-of-sync"""
db_api.subcloud_status_update(
self.ctx, self.subcloud.id, self._get_validator().endpoint_type,
dccommon_consts.SYNC_STATUS_OUT_OF_SYNC
)
self.assertRaises(
exceptions.BadRequest,
self._get_validator().validate_strategy_requirements,
self.ctx, self.subcloud.id, self.subcloud.name, False
)
self._get_mock_db_api().assert_called_once_with(
mock.ANY, self.subcloud.id, self._get_validator().endpoint_type
)