diff options
-rw-r--r-- | etc/nova/policy.json | 1 | ||||
-rw-r--r-- | nova/api/openstack/compute/contrib/admin_actions.py | 32 | ||||
-rw-r--r-- | nova/tests/api/openstack/compute/contrib/test_admin_actions.py | 60 | ||||
-rw-r--r-- | nova/tests/policy.json | 1 |
4 files changed, 94 insertions, 0 deletions
diff --git a/etc/nova/policy.json b/etc/nova/policy.json index 6fa6da5fb..f61c4dcac 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -22,6 +22,7 @@ "compute_extension:admin_actions:injectNetworkInfo": [["rule:admin_api"]], "compute_extension:admin_actions:createBackup": [["rule:admin_or_owner"]], "compute_extension:admin_actions:migrateLive": [["rule:admin_api"]], + "compute_extension:admin_actions:resetState": [["rule:admin_api"]], "compute_extension:admin_actions:migrate": [["rule:admin_api"]], "compute_extension:aggregates": [["rule:admin_api"]], "compute_extension:certificates": [], diff --git a/nova/api/openstack/compute/contrib/admin_actions.py b/nova/api/openstack/compute/contrib/admin_actions.py index 18adf8377..72815ed00 100644 --- a/nova/api/openstack/compute/contrib/admin_actions.py +++ b/nova/api/openstack/compute/contrib/admin_actions.py @@ -22,6 +22,7 @@ from nova.api.openstack import common from nova.api.openstack import extensions from nova.api.openstack import wsgi from nova import compute +from nova.compute import vm_states from nova import exception from nova import flags from nova import log as logging @@ -31,6 +32,10 @@ FLAGS = flags.FLAGS LOG = logging.getLogger(__name__) +# States usable in resetState action +state_map = dict(active=vm_states.ACTIVE, error=vm_states.ERROR) + + def authorize(context, action_name): action = 'admin_actions:%s' % action_name extensions.extension_authorizer('compute', action)(context) @@ -284,6 +289,33 @@ class AdminActionsController(wsgi.Controller): return webob.Response(status_int=202) + @wsgi.action('os-resetState') + def _reset_state(self, req, id, body): + """Permit admins to reset the state of a server.""" + context = req.environ["nova.context"] + authorize(context, 'resetState') + + # Identify the desired state from the body + try: + state = state_map[body["os-resetState"]["state"]] + except (TypeError, KeyError): + msg = _("Desired state must be specified. Valid states " + "are: %s") % ', '.join(sorted(state_map.keys())) + raise exc.HTTPBadRequest(explanation=msg) + + try: + instance = self.compute_api.get(context, id) + self.compute_api.update(context, instance, + vm_state=state, + task_state=None) + except exception.InstanceNotFound: + raise exc.HTTPNotFound(_("Server not found")) + except Exception: + readable = traceback.format_exc() + LOG.exception(_("Compute.api::resetState %s"), readable) + raise exc.HTTPUnprocessableEntity() + return webob.Response(status_int=202) + class Admin_actions(extensions.ExtensionDescriptor): """Enable admin-only server actions diff --git a/nova/tests/api/openstack/compute/contrib/test_admin_actions.py b/nova/tests/api/openstack/compute/contrib/test_admin_actions.py index 922e09f0b..330e24be6 100644 --- a/nova/tests/api/openstack/compute/contrib/test_admin_actions.py +++ b/nova/tests/api/openstack/compute/contrib/test_admin_actions.py @@ -17,6 +17,7 @@ import datetime import webob from nova.api.openstack import compute as compute_api +from nova.api.openstack.compute.contrib import admin_actions from nova import compute from nova.compute import vm_states from nova import context @@ -285,3 +286,62 @@ class CreateBackupTests(test.TestCase): request = self._get_request(body) response = request.get_response(self.app) self.assertEqual(response.status_int, 409) + + +class ResetStateTests(test.TestCase): + def setUp(self): + super(ResetStateTests, self).setUp() + + self.exists = True + self.kwargs = None + self.uuid = utils.gen_uuid() + + def fake_get(inst, context, instance_id): + if self.exists: + return dict(id=1, uuid=instance_id, vm_state=vm_states.ACTIVE) + raise exception.InstanceNotFound() + + def fake_update(inst, context, instance, **kwargs): + self.kwargs = kwargs + + self.stubs.Set(compute.API, 'get', fake_get) + self.stubs.Set(compute.API, 'update', fake_update) + self.admin_api = admin_actions.AdminActionsController() + + url = '/fake/servers/%s/action' % self.uuid + self.request = fakes.HTTPRequest.blank(url) + + def test_no_state(self): + self.assertRaises(webob.exc.HTTPBadRequest, + self.admin_api._reset_state, + self.request, 'inst_id', + {"os-resetState": None}) + + def test_bad_state(self): + self.assertRaises(webob.exc.HTTPBadRequest, + self.admin_api._reset_state, + self.request, 'inst_id', + {"os-resetState": {"state": "spam"}}) + + def test_no_instance(self): + self.exists = False + self.assertRaises(webob.exc.HTTPNotFound, + self.admin_api._reset_state, + self.request, 'inst_id', + {"os-resetState": {"state": "active"}}) + + def test_reset_active(self): + body = {"os-resetState": {"state": "active"}} + result = self.admin_api._reset_state(self.request, 'inst_id', body) + + self.assertEqual(result.status_int, 202) + self.assertEqual(self.kwargs, dict(vm_state=vm_states.ACTIVE, + task_state=None)) + + def test_reset_error(self): + body = {"os-resetState": {"state": "error"}} + result = self.admin_api._reset_state(self.request, 'inst_id', body) + + self.assertEqual(result.status_int, 202) + self.assertEqual(self.kwargs, dict(vm_state=vm_states.ERROR, + task_state=None)) diff --git a/nova/tests/policy.json b/nova/tests/policy.json index 8f8ea769c..e6b534f5a 100644 --- a/nova/tests/policy.json +++ b/nova/tests/policy.json @@ -78,6 +78,7 @@ "compute_extension:admin_actions:injectNetworkInfo": [], "compute_extension:admin_actions:createBackup": [], "compute_extension:admin_actions:migrateLive": [], + "compute_extension:admin_actions:resetState": [], "compute_extension:admin_actions:migrate": [], "compute_extension:aggregates": [], "compute_extension:certificates": [], |