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:
parent
cf18549468
commit
964adebb3c
@ -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": [],
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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": [],
|
||||
|
Loading…
x
Reference in New Issue
Block a user