1
# Copyright 2011 Openstack, LLC.
3
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
# not use this file except in compliance with the License. You may obtain
5
# a copy of the License at
7
# http://www.apache.org/licenses/LICENSE-2.0
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
# License for the specific language governing permissions and limitations
21
from nova.api.openstack import common
22
from nova.api.openstack import extensions
23
from nova.api.openstack import wsgi
24
from nova import compute
25
from nova import exception
26
from nova import flags
27
from nova import log as logging
28
from nova.scheduler import api as scheduler_api
32
LOG = logging.getLogger("nova.api.openstack.compute.contrib.admin_actions")
35
class AdminActionsController(wsgi.Controller):
36
def __init__(self, *args, **kwargs):
37
super(AdminActionsController, self).__init__(*args, **kwargs)
38
self.compute_api = compute.API()
40
# TODO(bcwaldon): These action names should be prefixed with 'os-'
43
@exception.novaclient_converter
44
@scheduler_api.redirect_handler
45
def _pause(self, req, id, body):
46
"""Permit Admins to pause the server"""
47
ctxt = req.environ['nova.context']
49
server = self.compute_api.get(ctxt, id)
50
self.compute_api.pause(ctxt, server)
51
except exception.InstanceInvalidState as state_error:
52
common.raise_http_conflict_for_instance_invalid_state(state_error,
55
readable = traceback.format_exc()
56
LOG.exception(_("Compute.api::pause %s"), readable)
57
raise exc.HTTPUnprocessableEntity()
58
return webob.Response(status_int=202)
60
@wsgi.action('unpause')
61
@exception.novaclient_converter
62
@scheduler_api.redirect_handler
63
def _unpause(self, req, id, body):
64
"""Permit Admins to unpause the server"""
65
ctxt = req.environ['nova.context']
67
server = self.compute_api.get(ctxt, id)
68
self.compute_api.unpause(ctxt, server)
69
except exception.InstanceInvalidState as state_error:
70
common.raise_http_conflict_for_instance_invalid_state(state_error,
73
readable = traceback.format_exc()
74
LOG.exception(_("Compute.api::unpause %s"), readable)
75
raise exc.HTTPUnprocessableEntity()
76
return webob.Response(status_int=202)
78
@wsgi.action('suspend')
79
@exception.novaclient_converter
80
@scheduler_api.redirect_handler
81
def _suspend(self, req, id, body):
82
"""Permit admins to suspend the server"""
83
context = req.environ['nova.context']
85
server = self.compute_api.get(context, id)
86
self.compute_api.suspend(context, server)
87
except exception.InstanceInvalidState as state_error:
88
common.raise_http_conflict_for_instance_invalid_state(state_error,
91
readable = traceback.format_exc()
92
LOG.exception(_("compute.api::suspend %s"), readable)
93
raise exc.HTTPUnprocessableEntity()
94
return webob.Response(status_int=202)
96
@wsgi.action('resume')
97
@exception.novaclient_converter
98
@scheduler_api.redirect_handler
99
def _resume(self, req, id, body):
100
"""Permit admins to resume the server from suspend"""
101
context = req.environ['nova.context']
103
server = self.compute_api.get(context, id)
104
self.compute_api.resume(context, server)
105
except exception.InstanceInvalidState as state_error:
106
common.raise_http_conflict_for_instance_invalid_state(state_error,
109
readable = traceback.format_exc()
110
LOG.exception(_("compute.api::resume %s"), readable)
111
raise exc.HTTPUnprocessableEntity()
112
return webob.Response(status_int=202)
114
@wsgi.action('migrate')
115
@exception.novaclient_converter
116
@scheduler_api.redirect_handler
117
def _migrate(self, req, id, body):
118
"""Permit admins to migrate a server to a new host"""
119
context = req.environ['nova.context']
121
instance = self.compute_api.get(context, id)
122
self.compute_api.resize(req.environ['nova.context'], instance)
123
except exception.InstanceInvalidState as state_error:
124
common.raise_http_conflict_for_instance_invalid_state(state_error,
127
LOG.exception(_("Error in migrate %s"), e)
128
raise exc.HTTPBadRequest()
129
return webob.Response(status_int=202)
131
@wsgi.action('resetNetwork')
132
@exception.novaclient_converter
133
@scheduler_api.redirect_handler
134
def _reset_network(self, req, id, body):
135
"""Permit admins to reset networking on an server"""
136
context = req.environ['nova.context']
138
instance = self.compute_api.get(context, id)
139
self.compute_api.reset_network(context, instance)
141
readable = traceback.format_exc()
142
LOG.exception(_("Compute.api::reset_network %s"), readable)
143
raise exc.HTTPUnprocessableEntity()
144
return webob.Response(status_int=202)
146
@wsgi.action('injectNetworkInfo')
147
@exception.novaclient_converter
148
@scheduler_api.redirect_handler
149
def _inject_network_info(self, req, id, body):
150
"""Permit admins to inject network info into a server"""
151
context = req.environ['nova.context']
153
instance = self.compute_api.get(context, id)
154
self.compute_api.inject_network_info(context, instance)
155
except exception.InstanceNotFound:
156
raise exc.HTTPNotFound(_("Server not found"))
158
readable = traceback.format_exc()
159
LOG.exception(_("Compute.api::inject_network_info %s"), readable)
160
raise exc.HTTPUnprocessableEntity()
161
return webob.Response(status_int=202)
164
@exception.novaclient_converter
165
@scheduler_api.redirect_handler
166
def _lock(self, req, id, body):
167
"""Permit admins to lock a server"""
168
context = req.environ['nova.context']
170
instance = self.compute_api.get(context, id)
171
self.compute_api.lock(context, instance)
172
except exception.InstanceNotFound:
173
raise exc.HTTPNotFound(_("Server not found"))
175
readable = traceback.format_exc()
176
LOG.exception(_("Compute.api::lock %s"), readable)
177
raise exc.HTTPUnprocessableEntity()
178
return webob.Response(status_int=202)
180
@wsgi.action('unlock')
181
@exception.novaclient_converter
182
@scheduler_api.redirect_handler
183
def _unlock(self, req, id, body):
184
"""Permit admins to lock a server"""
185
context = req.environ['nova.context']
187
instance = self.compute_api.get(context, id)
188
self.compute_api.unlock(context, instance)
189
except exception.InstanceNotFound:
190
raise exc.HTTPNotFound(_("Server not found"))
192
readable = traceback.format_exc()
193
LOG.exception(_("Compute.api::unlock %s"), readable)
194
raise exc.HTTPUnprocessableEntity()
195
return webob.Response(status_int=202)
197
@wsgi.action('createBackup')
198
def _create_backup(self, req, id, body):
199
"""Backup a server instance.
201
Images now have an `image_type` associated with them, which can be
202
'snapshot' or the backup type, like 'daily' or 'weekly'.
204
If the image_type is backup-like, then the rotation factor can be
205
included and that will cause the oldest backups that exceed the
206
rotation factor to be deleted.
209
context = req.environ["nova.context"]
212
entity = body["createBackup"]
213
except (KeyError, TypeError):
214
raise exc.HTTPBadRequest(_("Malformed request body"))
217
image_name = entity["name"]
218
backup_type = entity["backup_type"]
219
rotation = entity["rotation"]
221
except KeyError as missing_key:
222
msg = _("createBackup entity requires %s attribute") % missing_key
223
raise exc.HTTPBadRequest(explanation=msg)
226
msg = _("Malformed createBackup entity")
227
raise exc.HTTPBadRequest(explanation=msg)
230
rotation = int(rotation)
232
msg = _("createBackup attribute 'rotation' must be an integer")
233
raise exc.HTTPBadRequest(explanation=msg)
236
metadata = entity.get('metadata', {})
237
common.check_img_metadata_quota_limit(context, metadata)
239
props.update(metadata)
241
msg = _("Invalid metadata")
242
raise exc.HTTPBadRequest(explanation=msg)
245
instance = self.compute_api.get(context, id)
246
except exception.NotFound:
247
raise exc.HTTPNotFound(_("Instance not found"))
250
image = self.compute_api.backup(context, instance, image_name,
251
backup_type, rotation, extra_properties=props)
252
except exception.InstanceInvalidState as state_error:
253
common.raise_http_conflict_for_instance_invalid_state(state_error,
256
# build location of newly-created image entity
257
image_id = str(image['id'])
258
image_ref = os.path.join(req.application_url, 'images', image_id)
260
resp = webob.Response(status_int=202)
261
resp.headers['Location'] = image_ref
265
class Admin_actions(extensions.ExtensionDescriptor):
266
"""Enable admin-only server actions
268
Actions include: pause, unpause, suspend, resume, migrate,
269
resetNetwork, injectNetworkInfo, lock, unlock, createBackup
272
name = "AdminActions"
273
alias = "os-admin-actions"
274
namespace = "http://docs.openstack.org/compute/ext/admin-actions/api/v1.1"
275
updated = "2011-09-20T00:00:00+00:00"
278
def get_controller_extensions(self):
279
controller = AdminActionsController()
280
extension = extensions.ControllerExtension(self, 'servers', controller)