1
# Copyright (c) 2011 OpenStack, LLC.
4
# Licensed under the Apache License, Version 2.0 (the "License"); you may
5
# not use this file except in compliance with the License. You may obtain
6
# a copy of the License at
8
# http://www.apache.org/licenses/LICENSE-2.0
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
# License for the specific language governing permissions and limitations
16
"""The hosts admin extension."""
19
from xml.dom import minidom
20
from xml.parsers import expat
22
from nova.api.openstack import extensions
23
from nova.api.openstack import wsgi
24
from nova.api.openstack import xmlutil
25
from nova.compute import api as compute_api
27
from nova import exception
28
from nova import flags
29
from nova.openstack.common import log as logging
32
LOG = logging.getLogger(__name__)
34
authorize = extensions.extension_authorizer('compute', 'hosts')
37
class HostIndexTemplate(xmlutil.TemplateBuilder):
39
def shimmer(obj, do_raise=False):
40
# A bare list is passed in; we need to wrap it in a dict
41
return dict(hosts=obj)
43
root = xmlutil.TemplateElement('hosts', selector=shimmer)
44
elem = xmlutil.SubTemplateElement(root, 'host', selector='hosts')
48
return xmlutil.MasterTemplate(root, 1)
51
class HostUpdateTemplate(xmlutil.TemplateBuilder):
53
root = xmlutil.TemplateElement('host')
56
root.set('maintenance_mode')
58
return xmlutil.MasterTemplate(root, 1)
61
class HostActionTemplate(xmlutil.TemplateBuilder):
63
root = xmlutil.TemplateElement('host')
65
root.set('power_action')
67
return xmlutil.MasterTemplate(root, 1)
70
class HostShowTemplate(xmlutil.TemplateBuilder):
72
root = xmlutil.TemplateElement('host')
73
elem = xmlutil.make_flat_dict('resource', selector='host',
74
subselector='resource')
77
return xmlutil.MasterTemplate(root, 1)
80
class HostDeserializer(wsgi.XMLDeserializer):
81
def default(self, string):
83
node = minidom.parseString(string)
84
except expat.ExpatError:
85
msg = _("cannot understand XML")
86
raise exception.MalformedRequestBody(reason=msg)
89
for child in node.childNodes[0].childNodes:
90
updates[child.tagName] = self.extract_text(child)
92
return dict(body=updates)
95
def _list_hosts(req, service=None):
96
"""Returns a summary list of hosts, optionally filtering
99
context = req.environ['nova.context']
100
services = db.service_get_all(context, False)
103
for host in services:
104
hosts.append({"host_name": host['host'], 'service': host['topic']})
106
hosts = [host for host in hosts
107
if host["service"] == service]
112
"""Makes sure that the host exists."""
113
def wrapped(self, req, id, service=None, *args, **kwargs):
114
listed_hosts = _list_hosts(req, service)
115
hosts = [h["host_name"] for h in listed_hosts]
117
return fn(self, req, id, *args, **kwargs)
119
message = _("Host '%s' could not be found.") % id
120
raise webob.exc.HTTPNotFound(explanation=message)
124
class HostController(object):
125
"""The Hosts API controller for the OpenStack API."""
127
self.api = compute_api.HostAPI()
128
super(HostController, self).__init__()
130
@wsgi.serializers(xml=HostIndexTemplate)
131
def index(self, req):
132
authorize(req.environ['nova.context'])
133
return {'hosts': _list_hosts(req)}
135
@wsgi.serializers(xml=HostUpdateTemplate)
136
@wsgi.deserializers(xml=HostDeserializer)
138
def update(self, req, id, body):
139
authorize(req.environ['nova.context'])
141
for raw_key, raw_val in body.iteritems():
142
key = raw_key.lower().strip()
143
val = raw_val.lower().strip()
145
if val in ("enable", "disable"):
146
update_values['status'] = val.startswith("enable")
148
explanation = _("Invalid status: '%s'") % raw_val
149
raise webob.exc.HTTPBadRequest(explanation=explanation)
150
elif key == "maintenance_mode":
151
if val not in ['enable', 'disable']:
152
explanation = _("Invalid mode: '%s'") % raw_val
153
raise webob.exc.HTTPBadRequest(explanation=explanation)
154
update_values['maintenance_mode'] = val == 'enable'
156
explanation = _("Invalid update setting: '%s'") % raw_key
157
raise webob.exc.HTTPBadRequest(explanation=explanation)
159
# this is for handling multiple settings at the same time:
160
# the result dictionaries are merged in the first one.
161
# Note: the 'host' key will always be the same so it's
162
# okay that it gets overwritten.
163
update_setters = {'status': self._set_enabled_status,
164
'maintenance_mode': self._set_host_maintenance}
166
for key, value in update_values.iteritems():
167
result.update(update_setters[key](req, id, value))
170
def _set_host_maintenance(self, req, host, mode=True):
171
"""Start/Stop host maintenance window. On start, it triggers
172
guest VMs evacuation."""
173
context = req.environ['nova.context']
174
LOG.audit(_("Putting host %(host)s in maintenance "
175
"mode %(mode)s.") % locals())
176
result = self.api.set_host_maintenance(context, host, mode)
177
if result not in ("on_maintenance", "off_maintenance"):
178
raise webob.exc.HTTPBadRequest(explanation=result)
179
return {"host": host, "maintenance_mode": result}
181
def _set_enabled_status(self, req, host, enabled):
182
"""Sets the specified host's ability to accept new instances."""
183
context = req.environ['nova.context']
184
state = "enabled" if enabled else "disabled"
185
LOG.audit(_("Setting host %(host)s to %(state)s.") % locals())
186
result = self.api.set_host_enabled(context, host=host,
188
if result not in ("enabled", "disabled"):
189
# An error message was returned
190
raise webob.exc.HTTPBadRequest(explanation=result)
191
return {"host": host, "status": result}
193
def _host_power_action(self, req, host, action):
194
"""Reboots, shuts down or powers up the host."""
195
context = req.environ['nova.context']
198
result = self.api.host_power_action(context, host=host,
200
except NotImplementedError as e:
201
raise webob.exc.HTTPBadRequest(explanation=e.msg)
202
return {"host": host, "power_action": result}
204
@wsgi.serializers(xml=HostActionTemplate)
205
def startup(self, req, id):
206
return self._host_power_action(req, host=id, action="startup")
208
@wsgi.serializers(xml=HostActionTemplate)
209
def shutdown(self, req, id):
210
return self._host_power_action(req, host=id, action="shutdown")
212
@wsgi.serializers(xml=HostActionTemplate)
213
def reboot(self, req, id):
214
return self._host_power_action(req, host=id, action="reboot")
216
@wsgi.serializers(xml=HostShowTemplate)
217
def show(self, req, id):
218
"""Shows the physical/usage resource given by hosts.
220
:param context: security context
221
:param host: hostname
222
:returns: expected to use HostShowTemplate.
225
{'host': {'resource':D},..}
226
D: {'host': 'hostname','project': 'admin',
227
'cpu': 1, 'memory_mb': 2048, 'disk_gb': 30}
230
context = req.environ['nova.context']
231
if not context.is_admin:
232
msg = _("Describe-resource is admin only functionality")
233
raise webob.exc.HTTPForbidden(explanation=msg)
235
# Getting compute node info and related instances info
237
compute_ref = db.service_get_all_compute_by_host(context, host)
238
compute_ref = compute_ref[0]
239
except exception.ComputeHostNotFound:
240
raise webob.exc.HTTPNotFound(explanation=_("Host not found"))
241
instance_refs = db.instance_get_all_by_host(context,
244
# Getting total available/used resource
245
compute_ref = compute_ref['compute_node'][0]
246
resources = [{'resource': {'host': host, 'project': '(total)',
247
'cpu': compute_ref['vcpus'],
248
'memory_mb': compute_ref['memory_mb'],
249
'disk_gb': compute_ref['local_gb']}},
250
{'resource': {'host': host, 'project': '(used_now)',
251
'cpu': compute_ref['vcpus_used'],
252
'memory_mb': compute_ref['memory_mb_used'],
253
'disk_gb': compute_ref['local_gb_used']}}]
258
for i in instance_refs:
259
cpu_sum += i['vcpus']
260
mem_sum += i['memory_mb']
261
hdd_sum += i['root_gb'] + i['ephemeral_gb']
263
resources.append({'resource': {'host': host,
264
'project': '(used_max)',
266
'memory_mb': mem_sum,
267
'disk_gb': hdd_sum}})
269
# Getting usage resource per project
270
project_ids = [i['project_id'] for i in instance_refs]
271
project_ids = list(set(project_ids))
272
for project_id in project_ids:
273
vcpus = [i['vcpus'] for i in instance_refs
274
if i['project_id'] == project_id]
276
mem = [i['memory_mb'] for i in instance_refs
277
if i['project_id'] == project_id]
279
disk = [i['root_gb'] + i['ephemeral_gb'] for i in instance_refs
280
if i['project_id'] == project_id]
282
resources.append({'resource': {'host': host,
283
'project': project_id,
284
'cpu': reduce(lambda x, y: x + y, vcpus),
285
'memory_mb': reduce(lambda x, y: x + y, mem),
286
'disk_gb': reduce(lambda x, y: x + y, disk)}})
288
return {'host': resources}
291
class Hosts(extensions.ExtensionDescriptor):
292
"""Admin-only host administration"""
296
namespace = "http://docs.openstack.org/compute/ext/hosts/api/v1.1"
297
updated = "2011-06-29T00:00:00+00:00"
299
def get_resources(self):
300
resources = [extensions.ResourceExtension('os-hosts',
302
collection_actions={'update': 'PUT'},
303
member_actions={"startup": "GET", "shutdown": "GET",