diff --git a/doc/source/actions/change_nova_service_state.rst b/doc/source/actions/change_nova_service_state.rst index e55641f7a..ece1135f9 100644 --- a/doc/source/actions/change_nova_service_state.rst +++ b/doc/source/actions/change_nova_service_state.rst @@ -30,3 +30,12 @@ parameter type required description ``watcher_disabled`` or ``watcher_maintaining`` ======================== ====== ======== =================================== + +Skipping conditions +-------------------- + +Change nova service state actions will be automatically skipped in the +pre_condition phase in the following cases: + +- nova-compute service does not exist +- nova-compute service is already in the desired state (enabled or disabled) diff --git a/releasenotes/notes/change-nova-service-precondition-check-8c87bc04a4fd3d0f.yaml b/releasenotes/notes/change-nova-service-precondition-check-8c87bc04a4fd3d0f.yaml new file mode 100644 index 000000000..6c407825d --- /dev/null +++ b/releasenotes/notes/change-nova-service-precondition-check-8c87bc04a4fd3d0f.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Added a pre-condition check to the change_nova_service_state action + to prevent executing it when required criteria are not met. In the + following conditions, the action status will be set to SKIPPED: + + - The specified nova-compute service is not found + - The specified nova-compute service is in the desired state diff --git a/watcher/applier/actions/change_nova_service_state.py b/watcher/applier/actions/change_nova_service_state.py index 3a16ce9fe..a4379ea70 100644 --- a/watcher/applier/actions/change_nova_service_state.py +++ b/watcher/applier/actions/change_nova_service_state.py @@ -118,7 +118,22 @@ class ChangeNovaServiceState(base.BaseAction): return nova.disable_service_nova_compute(self.host, self.reason) def pre_condition(self): - pass + """Check change_nova_service_state preconditions + + Skipping conditions: + - nova-compute service does not exist + - nova-compute service is already in the desired state + """ + nova = nova_helper.NovaHelper(osc=self.osc) + services = nova.get_service_list() + service = next((s for s in services if s.host == self.host), None) + if service is None: + raise exception.ActionSkipped( + _("nova-compute service %s not found") % self.host) + if service.status == self.state: + raise exception.ActionSkipped( + _("nova-compute service %s is already in state %s") % + (self.host, self.state)) def post_condition(self): pass diff --git a/watcher/tests/unit/applier/actions/test_change_nova_service_state.py b/watcher/tests/unit/applier/actions/test_change_nova_service_state.py index 0055be67e..e916ebd26 100644 --- a/watcher/tests/unit/applier/actions/test_change_nova_service_state.py +++ b/watcher/tests/unit/applier/actions/test_change_nova_service_state.py @@ -20,12 +20,14 @@ import jsonschema from watcher.applier.actions import base as baction from watcher.applier.actions import change_nova_service_state from watcher.common import clients +from watcher.common import exception from watcher.common import nova_helper from watcher.decision_engine.model import element from watcher.tests.unit import base +from watcher.tests.unit.common import utils as test_utils -class TestChangeNovaServiceState(base.TestCase): +class TestChangeNovaServiceState(test_utils.NovaResourcesMixin, base.TestCase): def setUp(self): super().setUp() @@ -87,11 +89,53 @@ class TestChangeNovaServiceState(base.TestCase): self.assertRaises(jsonschema.ValidationError, self.action.validate_parameters) - def test_change_service_state_pre_condition(self): - try: - self.action.pre_condition() - except Exception as exc: - self.fail(exc) + def test_change_service_state_pre_condition_enable(self): + svc1 = nova_helper.Service.from_openstacksdk( + self.create_openstacksdk_service( + host='compute-1', + status=element.ServiceState.DISABLED.value) + ) + self.m_helper.get_service_list.return_value = [svc1] + self.action.pre_condition() + self.m_helper.get_service_list.assert_called_once_with() + + def test_change_service_state_pre_condition_disable(self): + self.action.input_parameters["state"] = ( + element.ServiceState.DISABLED.value) + svc1 = nova_helper.Service.from_openstacksdk( + self.create_openstacksdk_service( + host='compute-1', + status=element.ServiceState.ENABLED.value) + ) + self.m_helper.get_service_list.return_value = [svc1] + self.action.pre_condition() + self.m_helper.get_service_list.assert_called_once_with() + + def test_change_service_state_pre_condition_skipped_not_found(self): + svc1 = nova_helper.Service.from_openstacksdk( + self.create_openstacksdk_service( + host='compute-2', + status=element.ServiceState.DISABLED.value) + ) + self.m_helper.get_service_list.return_value = [svc1] + self.assertRaisesRegex( + exception.ActionSkipped, + "nova-compute service compute-1 not found", + self.action.pre_condition) + self.m_helper.get_service_list.assert_called_once_with() + + def test_change_service_state_pre_condition_skipped_state(self): + svc1 = nova_helper.Service.from_openstacksdk( + self.create_openstacksdk_service( + host='compute-1', + status=element.ServiceState.ENABLED.value) + ) + self.m_helper.get_service_list.return_value = [svc1] + self.assertRaisesRegex( + exception.ActionSkipped, + "nova-compute service compute-1 is already in state enabled", + self.action.pre_condition) + self.m_helper.get_service_list.assert_called_once_with() def test_change_service_state_post_condition(self): try: