PageRenderTime 34ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/nova/api/openstack/compute/contrib/hosts.py

http://github.com/openstack/nova
Python | 315 lines | 288 code | 7 blank | 20 comment | 3 complexity | 68a9ebc9a435f4328b47bef8f59b0690 MD5 | raw file
Possible License(s): Apache-2.0
  1. # Copyright (c) 2011 OpenStack Foundation
  2. # All Rights Reserved.
  3. #
  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
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  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
  14. # under the License.
  15. """The hosts admin extension."""
  16. from oslo_log import log as logging
  17. import webob.exc
  18. from nova.api.openstack import extensions
  19. from nova import compute
  20. from nova import context as nova_context
  21. from nova import exception
  22. from nova.i18n import _
  23. from nova.i18n import _LI
  24. from nova import objects
  25. LOG = logging.getLogger(__name__)
  26. authorize = extensions.extension_authorizer('compute', 'hosts')
  27. class HostController(object):
  28. """The Hosts API controller for the OpenStack API."""
  29. def __init__(self):
  30. self.api = compute.HostAPI()
  31. super(HostController, self).__init__()
  32. def index(self, req):
  33. """Returns a dict in the format:
  34. | {'hosts': [{'host_name': 'some.host.name',
  35. | 'service': 'cells',
  36. | 'zone': 'internal'},
  37. | {'host_name': 'some.other.host.name',
  38. | 'service': 'cells',
  39. | 'zone': 'internal'},
  40. | {'host_name': 'some.celly.host.name',
  41. | 'service': 'cells',
  42. | 'zone': 'internal'},
  43. | {'host_name': 'console1.host.com',
  44. | 'service': 'consoleauth',
  45. | 'zone': 'internal'},
  46. | {'host_name': 'network1.host.com',
  47. | 'service': 'network',
  48. | 'zone': 'internal'},
  49. | {'host_name': 'netwwork2.host.com',
  50. | 'service': 'network',
  51. | 'zone': 'internal'},
  52. | {'host_name': 'compute1.host.com',
  53. | 'service': 'compute',
  54. | 'zone': 'nova'},
  55. | {'host_name': 'compute2.host.com',
  56. | 'service': 'compute',
  57. | 'zone': 'nova'},
  58. | {'host_name': 'sched1.host.com',
  59. | 'service': 'scheduler',
  60. | 'zone': 'internal'},
  61. | {'host_name': 'sched2.host.com',
  62. | 'service': 'scheduler',
  63. | 'zone': 'internal'},
  64. | {'host_name': 'vol1.host.com',
  65. | 'service': 'volume',
  66. | 'zone': 'internal'}]}
  67. """
  68. context = req.environ['nova.context']
  69. authorize(context)
  70. # NOTE(alex_xu): back-compatible with db layer hard-code admin
  71. # permission checks
  72. nova_context.require_admin_context(context)
  73. filters = {'disabled': False}
  74. zone = req.GET.get('zone', None)
  75. if zone:
  76. filters['availability_zone'] = zone
  77. services = self.api.service_get_all(context, filters=filters,
  78. set_zones=True)
  79. hosts = []
  80. for service in services:
  81. hosts.append({'host_name': service['host'],
  82. 'service': service['topic'],
  83. 'zone': service['availability_zone']})
  84. return {'hosts': hosts}
  85. def update(self, req, id, body):
  86. """Updates a specified body.
  87. :param body: example format {'status': 'enable',
  88. 'maintenance_mode': 'enable'}
  89. """
  90. def read_enabled(orig_val, msg):
  91. """Checks a specified orig_val and returns True for 'enabled'
  92. and False for 'disabled'.
  93. :param orig_val: A string with either 'enable' or 'disable'. May
  94. be surrounded by whitespace, and case doesn't
  95. matter
  96. :param msg: The message to be passed to HTTPBadRequest. A single
  97. %s will be replaced with orig_val.
  98. """
  99. val = orig_val.strip().lower()
  100. if val == "enable":
  101. return True
  102. elif val == "disable":
  103. return False
  104. else:
  105. raise webob.exc.HTTPBadRequest(explanation=msg % orig_val)
  106. context = req.environ['nova.context']
  107. authorize(context)
  108. # See what the user wants to 'update'
  109. params = {k.strip().lower(): v for k, v in body.iteritems()}
  110. orig_status = status = params.pop('status', None)
  111. orig_maint_mode = maint_mode = params.pop('maintenance_mode', None)
  112. # Validate the request
  113. if len(params) > 0:
  114. # Some extra param was passed. Fail.
  115. explanation = _("Invalid update setting: '%s'") % params.keys()[0]
  116. raise webob.exc.HTTPBadRequest(explanation=explanation)
  117. if orig_status is not None:
  118. status = read_enabled(orig_status, _("Invalid status: '%s'"))
  119. if orig_maint_mode is not None:
  120. maint_mode = read_enabled(orig_maint_mode, _("Invalid mode: '%s'"))
  121. if status is None and maint_mode is None:
  122. explanation = _("'status' or 'maintenance_mode' needed for "
  123. "host update")
  124. raise webob.exc.HTTPBadRequest(explanation=explanation)
  125. # Make the calls and merge the results
  126. result = {'host': id}
  127. if status is not None:
  128. result['status'] = self._set_enabled_status(context, id, status)
  129. if maint_mode is not None:
  130. result['maintenance_mode'] = self._set_host_maintenance(context,
  131. id, maint_mode)
  132. return result
  133. def _set_host_maintenance(self, context, host_name, mode=True):
  134. """Start/Stop host maintenance window. On start, it triggers
  135. guest VMs evacuation.
  136. """
  137. LOG.info(_LI("Putting host %(host_name)s in maintenance mode "
  138. "%(mode)s."),
  139. {'host_name': host_name, 'mode': mode})
  140. try:
  141. result = self.api.set_host_maintenance(context, host_name, mode)
  142. except NotImplementedError:
  143. msg = _("Virt driver does not implement host maintenance mode.")
  144. raise webob.exc.HTTPNotImplemented(explanation=msg)
  145. except exception.NotFound as e:
  146. raise webob.exc.HTTPNotFound(explanation=e.format_message())
  147. except exception.ComputeServiceUnavailable as e:
  148. raise webob.exc.HTTPBadRequest(explanation=e.format_message())
  149. if result not in ("on_maintenance", "off_maintenance"):
  150. raise webob.exc.HTTPBadRequest(explanation=result)
  151. return result
  152. def _set_enabled_status(self, context, host_name, enabled):
  153. """Sets the specified host's ability to accept new instances.
  154. :param enabled: a boolean - if False no new VMs will be able to start
  155. on the host
  156. """
  157. if enabled:
  158. LOG.info(_LI("Enabling host %s.") % host_name)
  159. else:
  160. LOG.info(_LI("Disabling host %s.") % host_name)
  161. try:
  162. result = self.api.set_host_enabled(context, host_name=host_name,
  163. enabled=enabled)
  164. except NotImplementedError:
  165. msg = _("Virt driver does not implement host disabled status.")
  166. raise webob.exc.HTTPNotImplemented(explanation=msg)
  167. except exception.NotFound as e:
  168. raise webob.exc.HTTPNotFound(explanation=e.format_message())
  169. except exception.ComputeServiceUnavailable as e:
  170. raise webob.exc.HTTPBadRequest(explanation=e.format_message())
  171. if result not in ("enabled", "disabled"):
  172. raise webob.exc.HTTPBadRequest(explanation=result)
  173. return result
  174. def _host_power_action(self, req, host_name, action):
  175. """Reboots, shuts down or powers up the host."""
  176. context = req.environ['nova.context']
  177. authorize(context)
  178. try:
  179. result = self.api.host_power_action(context, host_name=host_name,
  180. action=action)
  181. except NotImplementedError:
  182. msg = _("Virt driver does not implement host power management.")
  183. raise webob.exc.HTTPNotImplemented(explanation=msg)
  184. except exception.NotFound as e:
  185. raise webob.exc.HTTPNotFound(explanation=e.format_message())
  186. except exception.ComputeServiceUnavailable as e:
  187. raise webob.exc.HTTPBadRequest(explanation=e.format_message())
  188. return {"host": host_name, "power_action": result}
  189. def startup(self, req, id):
  190. return self._host_power_action(req, host_name=id, action="startup")
  191. def shutdown(self, req, id):
  192. return self._host_power_action(req, host_name=id, action="shutdown")
  193. def reboot(self, req, id):
  194. return self._host_power_action(req, host_name=id, action="reboot")
  195. @staticmethod
  196. def _get_total_resources(host_name, compute_node):
  197. return {'resource': {'host': host_name,
  198. 'project': '(total)',
  199. 'cpu': compute_node['vcpus'],
  200. 'memory_mb': compute_node['memory_mb'],
  201. 'disk_gb': compute_node['local_gb']}}
  202. @staticmethod
  203. def _get_used_now_resources(host_name, compute_node):
  204. return {'resource': {'host': host_name,
  205. 'project': '(used_now)',
  206. 'cpu': compute_node['vcpus_used'],
  207. 'memory_mb': compute_node['memory_mb_used'],
  208. 'disk_gb': compute_node['local_gb_used']}}
  209. @staticmethod
  210. def _get_resource_totals_from_instances(host_name, instances):
  211. cpu_sum = 0
  212. mem_sum = 0
  213. hdd_sum = 0
  214. for instance in instances:
  215. cpu_sum += instance['vcpus']
  216. mem_sum += instance['memory_mb']
  217. hdd_sum += instance['root_gb'] + instance['ephemeral_gb']
  218. return {'resource': {'host': host_name,
  219. 'project': '(used_max)',
  220. 'cpu': cpu_sum,
  221. 'memory_mb': mem_sum,
  222. 'disk_gb': hdd_sum}}
  223. @staticmethod
  224. def _get_resources_by_project(host_name, instances):
  225. # Getting usage resource per project
  226. project_map = {}
  227. for instance in instances:
  228. resource = project_map.setdefault(instance['project_id'],
  229. {'host': host_name,
  230. 'project': instance['project_id'],
  231. 'cpu': 0,
  232. 'memory_mb': 0,
  233. 'disk_gb': 0})
  234. resource['cpu'] += instance['vcpus']
  235. resource['memory_mb'] += instance['memory_mb']
  236. resource['disk_gb'] += (instance['root_gb'] +
  237. instance['ephemeral_gb'])
  238. return project_map
  239. def show(self, req, id):
  240. """Shows the physical/usage resource given by hosts.
  241. :param id: hostname
  242. :returns: expected to use HostShowTemplate.
  243. ex.::
  244. {'host': {'resource':D},..}
  245. D: {'host': 'hostname','project': 'admin',
  246. 'cpu': 1, 'memory_mb': 2048, 'disk_gb': 30}
  247. """
  248. context = req.environ['nova.context']
  249. host_name = id
  250. try:
  251. compute_node = (
  252. objects.ComputeNode.get_first_node_by_host_for_old_compat(
  253. context, host_name))
  254. except exception.NotFound as e:
  255. raise webob.exc.HTTPNotFound(explanation=e.format_message())
  256. except exception.AdminRequired:
  257. msg = _("Describe-resource is admin only functionality")
  258. raise webob.exc.HTTPForbidden(explanation=msg)
  259. instances = self.api.instance_get_all_by_host(context, host_name)
  260. resources = [self._get_total_resources(host_name, compute_node)]
  261. resources.append(self._get_used_now_resources(host_name,
  262. compute_node))
  263. resources.append(self._get_resource_totals_from_instances(host_name,
  264. instances))
  265. by_proj_resources = self._get_resources_by_project(host_name,
  266. instances)
  267. for resource in by_proj_resources.itervalues():
  268. resources.append({'resource': resource})
  269. return {'host': resources}
  270. class Hosts(extensions.ExtensionDescriptor):
  271. """Admin-only host administration."""
  272. name = "Hosts"
  273. alias = "os-hosts"
  274. namespace = "http://docs.openstack.org/compute/ext/hosts/api/v1.1"
  275. updated = "2011-06-29T00:00:00Z"
  276. def get_resources(self):
  277. resources = [extensions.ResourceExtension('os-hosts',
  278. HostController(),
  279. collection_actions={'update': 'PUT'},
  280. member_actions={"startup": "GET", "shutdown": "GET",
  281. "reboot": "GET"})]
  282. return resources