Admin action to reset states.

Adds an Admin API action to reset the state of an instance.  This will
at least allow easy clean-up from bugs which corrupt the state of an
instance and inhibit the owner of the instance from deleting it.

Change-Id: Ia059cbd643e24e04dede06da330f444d03b07674
This commit is contained in:
Kevin L. Mitchell 2012-06-20 11:43:03 -05:00
parent cf18549468
commit 964adebb3c
4 changed files with 94 additions and 0 deletions

View File

@ -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": [],

View File

@ -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

View File

@ -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))

View File

@ -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": [],