diff --git a/doc/source/actions/resize.rst b/doc/source/actions/resize.rst index 4ffaa0ecd..630bdbc4a 100644 --- a/doc/source/actions/resize.rst +++ b/doc/source/actions/resize.rst @@ -23,3 +23,16 @@ parameter type required description ``flavor`` string yes ID or Name of Flavor (Nova accepts either ID or Name) ======================== ====== ======== =================================== + +Skipping conditions +-------------------- + +Resize actions will be automatically skipped in the pre_condition phase in +the following case: + +- The server does not exist + +On other condition the action will be FAILED in the pre_condition check: + +- Destination flavor does not exist + diff --git a/releasenotes/notes/resize-precondition-check-3d234a9000c3c93f.yaml b/releasenotes/notes/resize-precondition-check-3d234a9000c3c93f.yaml new file mode 100644 index 000000000..24d43b070 --- /dev/null +++ b/releasenotes/notes/resize-precondition-check-3d234a9000c3c93f.yaml @@ -0,0 +1,15 @@ +--- +features: + - | + Added a pre-condition check to the resize action to prevent executing + it when required criteria are not met. In following condition, + the action status will be set to SKIPPED: + + - Instance is not found + + On other condition the action will be FAILED in the pre_condition + check: + + - Destination flavor does not exist + + This improves safety and correctness of migrations. diff --git a/watcher/applier/actions/resize.py b/watcher/applier/actions/resize.py index 42acab55e..7c10841ea 100644 --- a/watcher/applier/actions/resize.py +++ b/watcher/applier/actions/resize.py @@ -18,7 +18,9 @@ from oslo_log import log +from watcher._i18n import _ from watcher.applier.actions import base +from watcher.common import exception from watcher.common import nova_helper LOG = log.getLogger(__name__) @@ -97,9 +99,27 @@ class Resize(base.BaseAction): LOG.warning("revert not supported") def pre_condition(self): - # TODO(jed): check if the instance exists / check if the instance is on - # the source_node - pass + """Check resize preconditions + + Skipping conditions: + - Instance does not exist + Failing conditions: + - Flavor does not exist + """ + nova = nova_helper.NovaHelper(osc=self.osc) + + # Check that the instance exists + try: + nova.find_instance(self.instance_uuid) + except exception.ComputeResourceNotFound: + raise exception.ActionSkipped( + _("Instance %s not found") % self.instance_uuid) + + try: + nova.get_flavor_id(self.flavor) + except exception.ComputeResourceNotFound: + raise exception.ActionExecutionFailure( + _("Flavor %s not found") % self.flavor) def post_condition(self): # TODO(jed): check extra parameters (network response, etc.) diff --git a/watcher/tests/unit/applier/actions/test_resize.py b/watcher/tests/unit/applier/actions/test_resize.py index 2ecf5a685..b672f24b1 100644 --- a/watcher/tests/unit/applier/actions/test_resize.py +++ b/watcher/tests/unit/applier/actions/test_resize.py @@ -18,6 +18,7 @@ from unittest import mock from watcher.applier.actions import base as baction from watcher.applier.actions import resize from watcher.common import clients +from watcher.common import exception from watcher.common import nova_helper from watcher.tests.unit import base @@ -89,3 +90,29 @@ class TestResize(base.TestCase): self.action.execute() self.r_helper.resize_instance.assert_called_once_with( instance_id=self.INSTANCE_UUID, flavor='x1') + + def test_pre_condition_success(self): + """Test pre_condition succeeds when instance and flavor exist""" + self.r_helper.find_instance.return_value = self.INSTANCE_UUID + self.r_helper.get_flavor_id.return_value = '123' + self.action.pre_condition() + + def test_pre_condition_instance_not_found(self): + """Test pre_condition fails when instance doesn't exist""" + err = exception.ComputeResourceNotFound() + self.r_helper.find_instance.side_effect = err + + self.assertRaisesRegex( + exception.ActionSkipped, + f"Instance {self.INSTANCE_UUID} not found", + self.action.pre_condition) + + def test_pre_condition_flavor_not_found(self): + """Test pre_condition fails when flavor doesn't exist""" + err = exception.ComputeResourceNotFound() + self.r_helper.get_flavor_id.side_effect = err + + self.assertRaisesRegex( + exception.ActionExecutionFailure, + "Flavor x1 not found", + self.action.pre_condition)