diff --git a/nova/api/openstack/compute/admin_actions.py b/nova/api/openstack/compute/admin_actions.py index 2b0b182a9d55..ba882e6a320b 100644 --- a/nova/api/openstack/compute/admin_actions.py +++ b/nova/api/openstack/compute/admin_actions.py @@ -19,8 +19,10 @@ from nova.api.openstack.compute.schemas import reset_server_state from nova.api.openstack import wsgi from nova.api import validation from nova.compute import api as compute +from nova.compute import instance_actions from nova.compute import vm_states from nova import exception +from nova import objects from nova.policies import admin_actions as aa_policies # States usable in resetState action @@ -65,9 +67,14 @@ class AdminActionsController(wsgi.Controller): context.can(aa_policies.POLICY_ROOT % 'reset_state', target={'project_id': instance.project_id}) + # Log os-resetState as an instance action + instance_action = objects.InstanceAction.action_start( + context, instance.uuid, instance_actions.RESET_STATE) + # Identify the desired state from the body state = state_map[body["os-resetState"]["state"]] instance.vm_state = state instance.task_state = None instance.save(admin_state_reset=True) + instance_action.finish() diff --git a/nova/compute/instance_actions.py b/nova/compute/instance_actions.py index e6e51c95e46f..10899750495a 100644 --- a/nova/compute/instance_actions.py +++ b/nova/compute/instance_actions.py @@ -70,3 +70,4 @@ LOCK = 'lock' UNLOCK = 'unlock' BACKUP = 'createBackup' CREATE_IMAGE = 'createImage' +RESET_STATE = 'resetState' diff --git a/nova/tests/unit/api/openstack/compute/test_server_reset_state.py b/nova/tests/unit/api/openstack/compute/test_server_reset_state.py index 591ab493a466..3462cf21acc5 100644 --- a/nova/tests/unit/api/openstack/compute/test_server_reset_state.py +++ b/nova/tests/unit/api/openstack/compute/test_server_reset_state.py @@ -18,6 +18,7 @@ import webob from nova.api.openstack.compute import admin_actions \ as admin_actions_v21 +from nova.compute import instance_actions from nova.compute import vm_states from nova import exception from nova import objects @@ -59,18 +60,27 @@ class ResetStateTestsV21(test.NoDBTestCase): def _get_request(self): return fakes.HTTPRequest.blank('') + @mock.patch( + 'nova.objects.instance_action.InstanceAction.action_start', + new=mock.Mock(spec=objects.InstanceAction)) def test_no_state(self): self.assertRaises(self.bad_request, self.admin_api._reset_state, self.request, self.uuid, body={"os-resetState": None}) + @mock.patch( + 'nova.objects.instance_action.InstanceAction.action_start', + new=mock.Mock(spec=objects.InstanceAction)) def test_bad_state(self): self.assertRaises(self.bad_request, self.admin_api._reset_state, self.request, self.uuid, body={"os-resetState": {"state": "spam"}}) + @mock.patch( + 'nova.objects.instance_action.InstanceAction.action_start', + new=mock.Mock(spec=objects.InstanceAction)) def test_no_instance(self): self.compute_api.get = mock.MagicMock( side_effect=exception.InstanceNotFound(instance_id='inst_ud')) @@ -84,11 +94,14 @@ class ResetStateTestsV21(test.NoDBTestCase): self.context, self.uuid, expected_attrs=None, cell_down_support=False) - def test_reset_active(self): + @mock.patch('nova.objects.instance_action.InstanceAction.action_start') + def test_reset_active(self, mock_instance_action_start): expected = dict(vm_state=vm_states.ACTIVE, task_state=None) self.instance.save = mock.MagicMock( side_effect=lambda **kw: self._check_instance_state(expected)) self.compute_api.get = mock.MagicMock(return_value=self.instance) + mock_instance_action = mock.Mock(spec=objects.InstanceAction) + mock_instance_action_start.return_value = mock_instance_action body = {"os-resetState": {"state": "active"}} result = self.admin_api._reset_state(self.request, self.uuid, @@ -107,11 +120,18 @@ class ResetStateTestsV21(test.NoDBTestCase): cell_down_support=False) self.instance.save.assert_called_once_with(admin_state_reset=True) - def test_reset_error(self): + mock_instance_action_start.assert_called_once_with( + self.context, self.instance.uuid, instance_actions.RESET_STATE) + mock_instance_action.finish.assert_called_once() + + @mock.patch('nova.objects.instance_action.InstanceAction.action_start') + def test_reset_error(self, mock_instance_action_start): expected = dict(vm_state=vm_states.ERROR, task_state=None) self.instance.save = mock.MagicMock( side_effect=lambda **kw: self._check_instance_state(expected)) self.compute_api.get = mock.MagicMock(return_value=self.instance) + mock_instance_action = mock.Mock(spec=objects.InstanceAction) + mock_instance_action_start.return_value = mock_instance_action body = {"os-resetState": {"state": "error"}} result = self.admin_api._reset_state(self.request, self.uuid, @@ -129,3 +149,7 @@ class ResetStateTestsV21(test.NoDBTestCase): self.context, self.instance.uuid, expected_attrs=None, cell_down_support=False) self.instance.save.assert_called_once_with(admin_state_reset=True) + + mock_instance_action_start.assert_called_once_with( + self.context, self.instance.uuid, instance_actions.RESET_STATE) + mock_instance_action.finish.assert_called_once() diff --git a/releasenotes/notes/bug-1911924-6e93d8a5038d18c1.yaml b/releasenotes/notes/bug-1911924-6e93d8a5038d18c1.yaml new file mode 100644 index 000000000000..ff27f936aa6b --- /dev/null +++ b/releasenotes/notes/bug-1911924-6e93d8a5038d18c1.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + The `os-resetState`_ API will now log an instance action when called. The + resulting instance action being visable via the `os-instance-actions`_ API + to users and admins, resolving `bug 1911924`_. + + .. _bug 1911924: https://launchpad.net/bugs/1911924 + .. _os-instance-actions: https://docs.openstack.org/api-ref/compute/?expanded=reset-server-state-os-resetstate-action-detail,list-actions-for-server-detail + .. _os-resetState: https://docs.openstack.org/api-ref/compute/?expanded=reset-server-state-os-resetstate-action-detail