Moving admin actions to extension
Begins to address LP bug 821145 Change-Id: I2799a8d70a167dda6d56f8fab2fc121fa2365a8a
This commit is contained in:
@@ -115,17 +115,8 @@ class APIRouter(base_wsgi.Router):
|
||||
if FLAGS.allow_admin_api:
|
||||
LOG.debug(_("Including admin operations in API."))
|
||||
|
||||
server_members['pause'] = 'POST'
|
||||
server_members['unpause'] = 'POST'
|
||||
server_members['diagnostics'] = 'GET'
|
||||
server_members['actions'] = 'GET'
|
||||
server_members['suspend'] = 'POST'
|
||||
server_members['resume'] = 'POST'
|
||||
server_members['rescue'] = 'POST'
|
||||
server_members['migrate'] = 'POST'
|
||||
server_members['unrescue'] = 'POST'
|
||||
server_members['reset_network'] = 'POST'
|
||||
server_members['inject_network_info'] = 'POST'
|
||||
|
||||
mapper.resource("user", "users",
|
||||
controller=users.create_resource(),
|
||||
|
||||
204
nova/api/openstack/contrib/admin_actions.py
Normal file
204
nova/api/openstack/contrib/admin_actions.py
Normal file
@@ -0,0 +1,204 @@
|
||||
# Copyright 2011 Openstack, LLC.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""The rescue mode extension."""
|
||||
|
||||
import traceback
|
||||
|
||||
import webob
|
||||
from webob import exc
|
||||
|
||||
from nova import compute
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova import log as logging
|
||||
from nova import utils
|
||||
from nova.api.openstack import extensions
|
||||
from nova.api.openstack import faults
|
||||
from nova.scheduler import api as scheduler_api
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
LOG = logging.getLogger("nova.api.openstack.contrib.admin_actions")
|
||||
|
||||
|
||||
class Admin_actions(extensions.ExtensionDescriptor):
|
||||
"""Adds a set of admin-only actions to servers"""
|
||||
|
||||
def __init__(self):
|
||||
super(Admin_actions, self).__init__()
|
||||
self.compute_api = compute.API()
|
||||
|
||||
@extensions.admin_only
|
||||
@exception.novaclient_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def _pause(self, input_dict, req, id):
|
||||
"""Permit Admins to pause the server"""
|
||||
ctxt = req.environ['nova.context']
|
||||
try:
|
||||
self.compute_api.pause(ctxt, id)
|
||||
except Exception:
|
||||
readable = traceback.format_exc()
|
||||
LOG.exception(_("Compute.api::pause %s"), readable)
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@extensions.admin_only
|
||||
@exception.novaclient_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def _unpause(self, input_dict, req, id):
|
||||
"""Permit Admins to unpause the server"""
|
||||
ctxt = req.environ['nova.context']
|
||||
try:
|
||||
self.compute_api.unpause(ctxt, id)
|
||||
except Exception:
|
||||
readable = traceback.format_exc()
|
||||
LOG.exception(_("Compute.api::unpause %s"), readable)
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@extensions.admin_only
|
||||
@exception.novaclient_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def _suspend(self, input_dict, req, id):
|
||||
"""Permit admins to suspend the server"""
|
||||
context = req.environ['nova.context']
|
||||
try:
|
||||
self.compute_api.suspend(context, id)
|
||||
except Exception:
|
||||
readable = traceback.format_exc()
|
||||
LOG.exception(_("compute.api::suspend %s"), readable)
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@extensions.admin_only
|
||||
@exception.novaclient_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def _resume(self, input_dict, req, id):
|
||||
"""Permit admins to resume the server from suspend"""
|
||||
context = req.environ['nova.context']
|
||||
try:
|
||||
self.compute_api.resume(context, id)
|
||||
except Exception:
|
||||
readable = traceback.format_exc()
|
||||
LOG.exception(_("compute.api::resume %s"), readable)
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@extensions.admin_only
|
||||
@exception.novaclient_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def _migrate(self, input_dict, req, id):
|
||||
"""Permit admins to migrate a server to a new host"""
|
||||
try:
|
||||
self.compute_api.resize(req.environ['nova.context'], id)
|
||||
except Exception, e:
|
||||
LOG.exception(_("Error in migrate %s"), e)
|
||||
raise exc.HTTPBadRequest()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@extensions.admin_only
|
||||
@exception.novaclient_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def _reset_network(self, input_dict, req, id):
|
||||
"""Permit admins to reset networking on an server"""
|
||||
context = req.environ['nova.context']
|
||||
try:
|
||||
self.compute_api.reset_network(context, id)
|
||||
except Exception:
|
||||
readable = traceback.format_exc()
|
||||
LOG.exception(_("Compute.api::reset_network %s"), readable)
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@extensions.admin_only
|
||||
@exception.novaclient_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def _inject_network_info(self, input_dict, req, id):
|
||||
"""Permit admins to inject network info into a server"""
|
||||
context = req.environ['nova.context']
|
||||
try:
|
||||
self.compute_api.inject_network_info(context, id)
|
||||
except Exception:
|
||||
readable = traceback.format_exc()
|
||||
LOG.exception(_("Compute.api::inject_network_info %s"), readable)
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@extensions.admin_only
|
||||
@exception.novaclient_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def _lock(self, input_dict, req, id):
|
||||
"""Permit admins to lock a server"""
|
||||
context = req.environ['nova.context']
|
||||
try:
|
||||
self.compute_api.lock(context, id)
|
||||
except Exception:
|
||||
readable = traceback.format_exc()
|
||||
LOG.exception(_("Compute.api::lock %s"), readable)
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@extensions.admin_only
|
||||
@exception.novaclient_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def _unlock(self, input_dict, req, id):
|
||||
"""Permit admins to lock a server"""
|
||||
context = req.environ['nova.context']
|
||||
try:
|
||||
self.compute_api.unlock(context, id)
|
||||
except Exception:
|
||||
readable = traceback.format_exc()
|
||||
LOG.exception(_("Compute.api::unlock %s"), readable)
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
def get_name(self):
|
||||
return "AdminActions"
|
||||
|
||||
def get_alias(self):
|
||||
return "os-admin-actions"
|
||||
|
||||
def get_description(self):
|
||||
return "Adds admin-only server actions: pause, unpause, " + \
|
||||
"suspend, resume, migrate, resetNetwork, " +\
|
||||
"injectNetworkInfo, lock and unlock"
|
||||
|
||||
def get_namespace(self):
|
||||
return "http://docs.openstack.org/ext/admin-actions/api/v1.1"
|
||||
|
||||
def get_updated(self):
|
||||
return "2011-09-20T00:00:00+00:00"
|
||||
|
||||
def get_actions(self):
|
||||
actions = [
|
||||
extensions.ActionExtension("servers", "pause", self._pause),
|
||||
extensions.ActionExtension("servers", "unpause", self._unpause),
|
||||
extensions.ActionExtension("servers", "suspend", self._suspend),
|
||||
extensions.ActionExtension("servers", "resume", self._resume),
|
||||
extensions.ActionExtension("servers", "migrate", self._migrate),
|
||||
|
||||
extensions.ActionExtension("servers",
|
||||
"resetNetwork",
|
||||
self._reset_network),
|
||||
|
||||
extensions.ActionExtension("servers",
|
||||
"injectNetworkInfo",
|
||||
self._inject_network_info),
|
||||
|
||||
extensions.ActionExtension("servers", "lock", self._lock),
|
||||
extensions.ActionExtension("servers", "unlock", self._unlock),
|
||||
]
|
||||
|
||||
return actions
|
||||
@@ -1,30 +0,0 @@
|
||||
# Copyright (c) 2011 Openstack, LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Decorator for limiting extensions that should be admin-only."""
|
||||
|
||||
from functools import wraps
|
||||
from nova import flags
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
def admin_only(fnc):
|
||||
@wraps(fnc)
|
||||
def _wrapped(self, *args, **kwargs):
|
||||
if FLAGS.allow_admin_api:
|
||||
return fnc(self, *args, **kwargs)
|
||||
return []
|
||||
_wrapped.func_name = fnc.func_name
|
||||
return _wrapped
|
||||
@@ -24,7 +24,6 @@ from nova import log as logging
|
||||
from nova.api.openstack import common
|
||||
from nova.api.openstack import extensions
|
||||
from nova.api.openstack import faults
|
||||
from nova.api.openstack.contrib import admin_only
|
||||
from nova.scheduler import api as scheduler_api
|
||||
|
||||
|
||||
@@ -105,12 +104,15 @@ class HostController(object):
|
||||
raise webob.exc.HTTPBadRequest(explanation=e.msg)
|
||||
return {"host": host, "power_action": result}
|
||||
|
||||
@extensions.admin_only
|
||||
def startup(self, req, id):
|
||||
return self._host_power_action(req, host=id, action="startup")
|
||||
|
||||
@extensions.admin_only
|
||||
def shutdown(self, req, id):
|
||||
return self._host_power_action(req, host=id, action="shutdown")
|
||||
|
||||
@extensions.admin_only
|
||||
def reboot(self, req, id):
|
||||
return self._host_power_action(req, host=id, action="reboot")
|
||||
|
||||
@@ -131,7 +133,6 @@ class Hosts(extensions.ExtensionDescriptor):
|
||||
def get_updated(self):
|
||||
return "2011-06-29T00:00:00+00:00"
|
||||
|
||||
@admin_only.admin_only
|
||||
def get_resources(self):
|
||||
resources = [extensions.ResourceExtension('os-hosts',
|
||||
HostController(), collection_actions={'update': 'PUT'},
|
||||
|
||||
@@ -29,23 +29,13 @@ FLAGS = flags.FLAGS
|
||||
LOG = logging.getLogger("nova.api.contrib.rescue")
|
||||
|
||||
|
||||
def wrap_errors(fn):
|
||||
""""Ensure errors are not passed along."""
|
||||
def wrapped(*args):
|
||||
try:
|
||||
return fn(*args)
|
||||
except Exception, e:
|
||||
return faults.Fault(exc.HTTPInternalServerError())
|
||||
return wrapped
|
||||
|
||||
|
||||
class Rescue(exts.ExtensionDescriptor):
|
||||
"""The Rescue controller for the OpenStack API."""
|
||||
def __init__(self):
|
||||
super(Rescue, self).__init__()
|
||||
self.compute_api = compute.API()
|
||||
|
||||
@wrap_errors
|
||||
@exts.wrap_errors
|
||||
def _rescue(self, input_dict, req, instance_id):
|
||||
"""Rescue an instance."""
|
||||
context = req.environ["nova.context"]
|
||||
@@ -57,7 +47,7 @@ class Rescue(exts.ExtensionDescriptor):
|
||||
|
||||
return {'adminPass': password}
|
||||
|
||||
@wrap_errors
|
||||
@exts.wrap_errors
|
||||
def _unrescue(self, input_dict, req, instance_id):
|
||||
"""Unrescue an instance."""
|
||||
context = req.environ["nova.context"]
|
||||
|
||||
@@ -16,14 +16,16 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import functools
|
||||
import imp
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
|
||||
from lxml import etree
|
||||
import routes
|
||||
import webob.dec
|
||||
import webob.exc
|
||||
from lxml import etree
|
||||
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
@@ -36,7 +38,7 @@ from nova.api.openstack import wsgi
|
||||
from nova.api.openstack import xmlutil
|
||||
|
||||
|
||||
LOG = logging.getLogger('extensions')
|
||||
LOG = logging.getLogger('nova.api.openstack.extensions')
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
@@ -523,3 +525,23 @@ class ExtensionsXMLSerializer(wsgi.XMLDictSerializer):
|
||||
"""Convert the xml object to an xml string."""
|
||||
|
||||
return etree.tostring(root, encoding='UTF-8')
|
||||
|
||||
|
||||
def admin_only(fnc):
|
||||
@functools.wraps(fnc)
|
||||
def _wrapped(self, *args, **kwargs):
|
||||
if FLAGS.allow_admin_api:
|
||||
return fnc(self, *args, **kwargs)
|
||||
return faults.Fault(webob.exc.HTTPNotFound())
|
||||
_wrapped.func_name = fnc.func_name
|
||||
return _wrapped
|
||||
|
||||
|
||||
def wrap_errors(fn):
|
||||
""""Ensure errors are not passed along."""
|
||||
def wrapped(*args):
|
||||
try:
|
||||
return fn(*args)
|
||||
except Exception, e:
|
||||
return faults.Fault(webob.exc.HTTPInternalServerError())
|
||||
return wrapped
|
||||
|
||||
@@ -18,7 +18,6 @@ import base64
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from novaclient import exceptions as novaclient_exceptions
|
||||
from lxml import etree
|
||||
from webob import exc
|
||||
import webob
|
||||
@@ -59,19 +58,6 @@ class ConvertedException(exc.WSGIHTTPException):
|
||||
super(ConvertedException, self).__init__()
|
||||
|
||||
|
||||
def novaclient_exception_converter(f):
|
||||
"""Convert novaclient ClientException HTTP codes to webob exceptions.
|
||||
Has to be the outer-most decorator.
|
||||
"""
|
||||
def new_f(*args, **kwargs):
|
||||
try:
|
||||
ret = f(*args, **kwargs)
|
||||
return ret
|
||||
except novaclient_exceptions.ClientException, e:
|
||||
raise ConvertedException(e.code, e.message, e.details)
|
||||
return new_f
|
||||
|
||||
|
||||
class Controller(object):
|
||||
""" The Server API base controller class for the OpenStack API """
|
||||
|
||||
@@ -354,7 +340,7 @@ class Controller(object):
|
||||
expl = _('Userdata content cannot be decoded')
|
||||
raise exc.HTTPBadRequest(explanation=expl)
|
||||
|
||||
@novaclient_exception_converter
|
||||
@exception.novaclient_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def show(self, req, id):
|
||||
""" Returns server details by server id """
|
||||
@@ -580,7 +566,7 @@ class Controller(object):
|
||||
def _update(self, context, req, id, inst_dict):
|
||||
return exc.HTTPNotImplemented()
|
||||
|
||||
@novaclient_exception_converter
|
||||
@exception.novaclient_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def action(self, req, id, body):
|
||||
"""Multi-purpose method used to take actions on a server"""
|
||||
@@ -719,41 +705,7 @@ class Controller(object):
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@novaclient_exception_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def lock(self, req, id):
|
||||
"""
|
||||
lock the instance with id
|
||||
admin only operation
|
||||
|
||||
"""
|
||||
context = req.environ['nova.context']
|
||||
try:
|
||||
self.compute_api.lock(context, id)
|
||||
except Exception:
|
||||
readable = traceback.format_exc()
|
||||
LOG.exception(_("Compute.api::lock %s"), readable)
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@novaclient_exception_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def unlock(self, req, id):
|
||||
"""
|
||||
unlock the instance with id
|
||||
admin only operation
|
||||
|
||||
"""
|
||||
context = req.environ['nova.context']
|
||||
try:
|
||||
self.compute_api.unlock(context, id)
|
||||
except Exception:
|
||||
readable = traceback.format_exc()
|
||||
LOG.exception(_("Compute.api::unlock %s"), readable)
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@novaclient_exception_converter
|
||||
@exception.novaclient_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def get_lock(self, req, id):
|
||||
"""
|
||||
@@ -769,133 +721,7 @@ class Controller(object):
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@novaclient_exception_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def reset_network(self, req, id):
|
||||
"""
|
||||
Reset networking on an instance (admin only).
|
||||
|
||||
"""
|
||||
context = req.environ['nova.context']
|
||||
try:
|
||||
self.compute_api.reset_network(context, id)
|
||||
except Exception:
|
||||
readable = traceback.format_exc()
|
||||
LOG.exception(_("Compute.api::reset_network %s"), readable)
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@novaclient_exception_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def inject_network_info(self, req, id):
|
||||
"""
|
||||
Inject network info for an instance (admin only).
|
||||
|
||||
"""
|
||||
context = req.environ['nova.context']
|
||||
try:
|
||||
self.compute_api.inject_network_info(context, id)
|
||||
except Exception:
|
||||
readable = traceback.format_exc()
|
||||
LOG.exception(_("Compute.api::inject_network_info %s"), readable)
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@novaclient_exception_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def pause(self, req, id):
|
||||
""" Permit Admins to Pause the server. """
|
||||
ctxt = req.environ['nova.context']
|
||||
try:
|
||||
self.compute_api.pause(ctxt, id)
|
||||
except Exception:
|
||||
readable = traceback.format_exc()
|
||||
LOG.exception(_("Compute.api::pause %s"), readable)
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@novaclient_exception_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def unpause(self, req, id):
|
||||
""" Permit Admins to Unpause the server. """
|
||||
ctxt = req.environ['nova.context']
|
||||
try:
|
||||
self.compute_api.unpause(ctxt, id)
|
||||
except Exception:
|
||||
readable = traceback.format_exc()
|
||||
LOG.exception(_("Compute.api::unpause %s"), readable)
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@novaclient_exception_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def suspend(self, req, id):
|
||||
"""permit admins to suspend the server"""
|
||||
context = req.environ['nova.context']
|
||||
try:
|
||||
self.compute_api.suspend(context, id)
|
||||
except Exception:
|
||||
readable = traceback.format_exc()
|
||||
LOG.exception(_("compute.api::suspend %s"), readable)
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@novaclient_exception_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def resume(self, req, id):
|
||||
"""permit admins to resume the server from suspend"""
|
||||
context = req.environ['nova.context']
|
||||
try:
|
||||
self.compute_api.resume(context, id)
|
||||
except Exception:
|
||||
readable = traceback.format_exc()
|
||||
LOG.exception(_("compute.api::resume %s"), readable)
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@novaclient_exception_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def migrate(self, req, id):
|
||||
try:
|
||||
self.compute_api.resize(req.environ['nova.context'], id)
|
||||
except Exception, e:
|
||||
LOG.exception(_("Error in migrate %s"), e)
|
||||
raise exc.HTTPBadRequest()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@novaclient_exception_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def rescue(self, req, id, body={}):
|
||||
"""Permit users to rescue the server."""
|
||||
context = req.environ["nova.context"]
|
||||
try:
|
||||
if 'rescue' in body and body['rescue'] and \
|
||||
'adminPass' in body['rescue']:
|
||||
password = body['rescue']['adminPass']
|
||||
else:
|
||||
password = utils.generate_password(FLAGS.password_length)
|
||||
self.compute_api.rescue(context, id, rescue_password=password)
|
||||
except Exception:
|
||||
readable = traceback.format_exc()
|
||||
LOG.exception(_("compute.api::rescue %s"), readable)
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
|
||||
return {'adminPass': password}
|
||||
|
||||
@novaclient_exception_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def unrescue(self, req, id):
|
||||
"""Permit users to unrescue the server."""
|
||||
context = req.environ["nova.context"]
|
||||
try:
|
||||
self.compute_api.unrescue(context, id)
|
||||
except Exception:
|
||||
readable = traceback.format_exc()
|
||||
LOG.exception(_("compute.api::unrescue %s"), readable)
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@novaclient_exception_converter
|
||||
@exception.novaclient_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def get_ajax_console(self, req, id):
|
||||
"""Returns a url to an instance's ajaxterm console."""
|
||||
@@ -906,7 +732,7 @@ class Controller(object):
|
||||
raise exc.HTTPNotFound()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@novaclient_exception_converter
|
||||
@exception.novaclient_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def get_vnc_console(self, req, id):
|
||||
"""Returns a url to an instance's ajaxterm console."""
|
||||
@@ -917,7 +743,7 @@ class Controller(object):
|
||||
raise exc.HTTPNotFound()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
@novaclient_exception_converter
|
||||
@exception.novaclient_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def diagnostics(self, req, id):
|
||||
"""Permit Admins to retrieve server diagnostics."""
|
||||
@@ -960,7 +786,7 @@ class Controller(object):
|
||||
class ControllerV10(Controller):
|
||||
"""v1.0 OpenStack API controller"""
|
||||
|
||||
@novaclient_exception_converter
|
||||
@exception.novaclient_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def delete(self, req, id):
|
||||
""" Destroys a server """
|
||||
@@ -1047,7 +873,7 @@ class ControllerV10(Controller):
|
||||
class ControllerV11(Controller):
|
||||
"""v1.1 OpenStack API controller"""
|
||||
|
||||
@novaclient_exception_converter
|
||||
@exception.novaclient_converter
|
||||
@scheduler_api.redirect_handler
|
||||
def delete(self, req, id):
|
||||
""" Destroys a server """
|
||||
|
||||
@@ -27,11 +27,26 @@ SHOULD include dedicated exception logging.
|
||||
from functools import wraps
|
||||
import sys
|
||||
|
||||
from novaclient import exceptions as novaclient_exceptions
|
||||
|
||||
from nova import log as logging
|
||||
|
||||
LOG = logging.getLogger('nova.exception')
|
||||
|
||||
|
||||
def novaclient_converter(f):
|
||||
"""Convert novaclient ClientException HTTP codes to webob exceptions.
|
||||
Has to be the outer-most decorator.
|
||||
"""
|
||||
def new_f(*args, **kwargs):
|
||||
try:
|
||||
ret = f(*args, **kwargs)
|
||||
return ret
|
||||
except novaclient_exceptions.ClientException, e:
|
||||
raise ConvertedException(e.code, e.message, e.details)
|
||||
return new_f
|
||||
|
||||
|
||||
class ProcessExecutionError(IOError):
|
||||
def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None,
|
||||
description=None):
|
||||
|
||||
64
nova/tests/api/openstack/contrib/test_admin_actions.py
Normal file
64
nova/tests/api/openstack/contrib/test_admin_actions.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# Copyright 2011 OpenStack LLC.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import webob
|
||||
|
||||
from nova import compute
|
||||
from nova import flags
|
||||
from nova import test
|
||||
from nova.tests.api.openstack import fakes
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
def fake_compute_api(cls, req, id):
|
||||
return True
|
||||
|
||||
|
||||
class AdminActionsTest(test.TestCase):
|
||||
|
||||
_actions = ('pause', 'unpause', 'suspend', 'resume', 'migrate',
|
||||
'resetNetwork', 'injectNetworkInfo', 'lock', 'unlock')
|
||||
|
||||
_methods = ('pause', 'unpause', 'suspend', 'resume', 'resize',
|
||||
'reset_network', 'inject_network_info', 'lock', 'unlock')
|
||||
|
||||
def setUp(self):
|
||||
super(AdminActionsTest, self).setUp()
|
||||
self.flags(allow_admin_api=True)
|
||||
for _method in self._methods:
|
||||
self.stubs.Set(compute.API, _method, fake_compute_api)
|
||||
|
||||
def test_admin_api_enabled(self):
|
||||
app = fakes.wsgi_app()
|
||||
for _action in self._actions:
|
||||
req = webob.Request.blank('/v1.1/fake/servers/1/action')
|
||||
req.method = 'POST'
|
||||
req.body = json.dumps({_action: None})
|
||||
req.content_type = 'application/json'
|
||||
res = req.get_response(app)
|
||||
self.assertEqual(res.status_int, 202)
|
||||
|
||||
def test_admin_api_disabled(self):
|
||||
FLAGS.allow_admin_api = False
|
||||
app = fakes.wsgi_app()
|
||||
for _action in self._actions:
|
||||
req = webob.Request.blank('/v1.1/fake/servers/1/action')
|
||||
req.method = 'POST'
|
||||
req.body = json.dumps({_action: None})
|
||||
req.content_type = 'application/json'
|
||||
res = req.get_response(app)
|
||||
self.assertEqual(res.status_int, 404)
|
||||
@@ -85,6 +85,7 @@ class ExtensionControllerTest(test.TestCase):
|
||||
ext_path = os.path.join(os.path.dirname(__file__), "extensions")
|
||||
self.flags(osapi_extensions_path=ext_path)
|
||||
self.ext_list = [
|
||||
"AdminActions",
|
||||
"Createserverext",
|
||||
"DeferredDelete",
|
||||
"DiskConfig",
|
||||
|
||||
@@ -299,23 +299,6 @@ class ServerActionsTest(test.TestCase):
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
self.assertEqual(res.status_int, 400)
|
||||
|
||||
def test_migrate_server(self):
|
||||
"""This is basically the same as resize, only we provide the `migrate`
|
||||
attribute in the body's dict.
|
||||
"""
|
||||
req = self.webreq('/1/migrate', 'POST')
|
||||
|
||||
self.resize_called = False
|
||||
|
||||
def resize_mock(*args):
|
||||
self.resize_called = True
|
||||
|
||||
self.stubs.Set(nova.compute.api.API, 'resize', resize_mock)
|
||||
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
self.assertEqual(res.status_int, 202)
|
||||
self.assertEqual(self.resize_called, True)
|
||||
|
||||
def test_create_backup(self):
|
||||
"""The happy path for creating backups"""
|
||||
self.flags(allow_admin_api=True)
|
||||
|
||||
@@ -261,10 +261,6 @@ class ServersTest(test.TestCase):
|
||||
instance_addresses)
|
||||
self.stubs.Set(nova.db.api, 'instance_get_floating_address',
|
||||
instance_addresses)
|
||||
self.stubs.Set(nova.compute.API, 'pause', fake_compute_api)
|
||||
self.stubs.Set(nova.compute.API, 'unpause', fake_compute_api)
|
||||
self.stubs.Set(nova.compute.API, 'suspend', fake_compute_api)
|
||||
self.stubs.Set(nova.compute.API, 'resume', fake_compute_api)
|
||||
self.stubs.Set(nova.compute.API, "get_diagnostics", fake_compute_api)
|
||||
self.stubs.Set(nova.compute.API, "get_actions", fake_compute_api)
|
||||
|
||||
@@ -2700,55 +2696,6 @@ class ServersTest(test.TestCase):
|
||||
self.assertEqual(s['imageId'], 10)
|
||||
self.assertEqual(s['flavorId'], 1)
|
||||
|
||||
def test_server_pause(self):
|
||||
self.flags(allow_admin_api=True)
|
||||
req = webob.Request.blank('/v1.0/servers/1/pause')
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
self.assertEqual(res.status_int, 202)
|
||||
|
||||
def test_server_unpause(self):
|
||||
self.flags(allow_admin_api=True)
|
||||
req = webob.Request.blank('/v1.0/servers/1/unpause')
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
self.assertEqual(res.status_int, 202)
|
||||
|
||||
def test_server_suspend(self):
|
||||
self.flags(allow_admin_api=True)
|
||||
req = webob.Request.blank('/v1.0/servers/1/suspend')
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
self.assertEqual(res.status_int, 202)
|
||||
|
||||
def test_server_resume(self):
|
||||
self.flags(allow_admin_api=True)
|
||||
req = webob.Request.blank('/v1.0/servers/1/resume')
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
self.assertEqual(res.status_int, 202)
|
||||
|
||||
def test_server_reset_network(self):
|
||||
self.flags(allow_admin_api=True)
|
||||
req = webob.Request.blank('/v1.0/servers/1/reset_network')
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
self.assertEqual(res.status_int, 202)
|
||||
|
||||
def test_server_inject_network_info(self):
|
||||
self.flags(allow_admin_api=True)
|
||||
req = webob.Request.blank(
|
||||
'/v1.0/servers/1/inject_network_info')
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
self.assertEqual(res.status_int, 202)
|
||||
|
||||
def test_server_diagnostics(self):
|
||||
self.flags(allow_admin_api=False)
|
||||
req = webob.Request.blank("/v1.0/servers/1/diagnostics")
|
||||
@@ -2779,66 +2726,6 @@ class ServersTest(test.TestCase):
|
||||
self.assertEqual(res.status, '202 Accepted')
|
||||
self.assertEqual(self.server_delete_called, True)
|
||||
|
||||
def test_rescue_generates_password(self):
|
||||
self.flags(allow_admin_api=True)
|
||||
|
||||
self.called = False
|
||||
|
||||
def rescue_mock(*args, **kwargs):
|
||||
self.called = True
|
||||
|
||||
self.stubs.Set(nova.compute.api.API, 'rescue', rescue_mock)
|
||||
req = webob.Request.blank('/v1.0/servers/1/rescue')
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
self.assertEqual(self.called, True)
|
||||
self.assertEqual(res.status_int, 200)
|
||||
res_body = json.loads(res.body)
|
||||
self.assertTrue('adminPass' in res_body)
|
||||
self.assertEqual(FLAGS.password_length, len(res_body['adminPass']))
|
||||
|
||||
def test_rescue_with_preset_password(self):
|
||||
self.flags(allow_admin_api=True)
|
||||
|
||||
self.called = False
|
||||
|
||||
def rescue_mock(*args, **kwargs):
|
||||
self.called = True
|
||||
|
||||
self.stubs.Set(nova.compute.api.API, 'rescue', rescue_mock)
|
||||
req = webob.Request.blank('/v1.0/servers/1/rescue')
|
||||
req.method = 'POST'
|
||||
body = {"rescue": {"adminPass": "AABBCC112233"}}
|
||||
req.body = json.dumps(body)
|
||||
req.content_type = 'application/json'
|
||||
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
self.assertEqual(self.called, True)
|
||||
self.assertEqual(res.status_int, 200)
|
||||
res_body = json.loads(res.body)
|
||||
self.assertTrue('adminPass' in res_body)
|
||||
self.assertEqual('AABBCC112233', res_body['adminPass'])
|
||||
|
||||
def test_rescue_raises_handled(self):
|
||||
self.flags(allow_admin_api=True)
|
||||
body = {}
|
||||
|
||||
def rescue_mock(*args, **kwargs):
|
||||
raise Exception('Who cares?')
|
||||
|
||||
self.stubs.Set(nova.compute.api.API, 'rescue', rescue_mock)
|
||||
req = webob.Request.blank('/v1.0/servers/1/rescue')
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
self.assertEqual(res.status_int, 422)
|
||||
|
||||
def test_delete_server_instance_v1_1(self):
|
||||
req = webob.Request.blank('/v1.1/fake/servers/1')
|
||||
req.method = 'DELETE'
|
||||
|
||||
Reference in New Issue
Block a user