The most of objects are removed from the extensions module. The last thing is the expected_errors decorator, but that decorator is nothing about the extensions. So move the decorator to the wsgi module where is the place put the other decorator also. Then we can remove the extensions module entirely. Partial implement bp api-extensions-merge-queens Change-Id: I4802c5b38001a756448d4feb9ca336908821f591
410 lines
18 KiB
Python
410 lines
18 KiB
Python
# Copyright (c) 2012 OpenStack Foundation
|
|
# 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.
|
|
|
|
"""The hypervisors admin extension."""
|
|
|
|
from oslo_log import log as logging
|
|
from oslo_serialization import jsonutils
|
|
from oslo_utils import strutils
|
|
from oslo_utils import uuidutils
|
|
import webob.exc
|
|
|
|
from nova.api.openstack import api_version_request
|
|
from nova.api.openstack import common
|
|
from nova.api.openstack.compute.schemas import hypervisors as hyper_schema
|
|
from nova.api.openstack.compute.views import hypervisors as hyper_view
|
|
from nova.api.openstack import wsgi
|
|
from nova.api import validation
|
|
from nova.cells import utils as cells_utils
|
|
from nova import compute
|
|
from nova import exception
|
|
from nova.i18n import _
|
|
from nova.policies import hypervisors as hv_policies
|
|
from nova import servicegroup
|
|
from nova import utils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
UUID_FOR_ID_MIN_VERSION = '2.53'
|
|
|
|
|
|
class HypervisorsController(wsgi.Controller):
|
|
"""The Hypervisors API controller for the OpenStack API."""
|
|
|
|
_view_builder_class = hyper_view.ViewBuilder
|
|
|
|
def __init__(self):
|
|
self.host_api = compute.HostAPI()
|
|
self.servicegroup_api = servicegroup.API()
|
|
super(HypervisorsController, self).__init__()
|
|
|
|
def _view_hypervisor(self, hypervisor, service, detail, req, servers=None,
|
|
**kwargs):
|
|
alive = self.servicegroup_api.service_is_up(service)
|
|
# The 2.53 microversion returns the compute node uuid rather than id.
|
|
uuid_for_id = api_version_request.is_supported(
|
|
req, min_version=UUID_FOR_ID_MIN_VERSION)
|
|
hyp_dict = {
|
|
'id': hypervisor.uuid if uuid_for_id else hypervisor.id,
|
|
'hypervisor_hostname': hypervisor.hypervisor_hostname,
|
|
'state': 'up' if alive else 'down',
|
|
'status': ('disabled' if service.disabled
|
|
else 'enabled'),
|
|
}
|
|
|
|
if detail:
|
|
for field in ('vcpus', 'memory_mb', 'local_gb', 'vcpus_used',
|
|
'memory_mb_used', 'local_gb_used',
|
|
'hypervisor_type', 'hypervisor_version',
|
|
'free_ram_mb', 'free_disk_gb', 'current_workload',
|
|
'running_vms', 'disk_available_least', 'host_ip'):
|
|
hyp_dict[field] = getattr(hypervisor, field)
|
|
|
|
service_id = service.uuid if uuid_for_id else service.id
|
|
hyp_dict['service'] = {
|
|
'id': service_id,
|
|
'host': hypervisor.host,
|
|
'disabled_reason': service.disabled_reason,
|
|
}
|
|
|
|
if api_version_request.is_supported(req, min_version='2.28'):
|
|
if hypervisor.cpu_info:
|
|
hyp_dict['cpu_info'] = jsonutils.loads(hypervisor.cpu_info)
|
|
else:
|
|
hyp_dict['cpu_info'] = {}
|
|
else:
|
|
hyp_dict['cpu_info'] = hypervisor.cpu_info
|
|
|
|
if servers:
|
|
hyp_dict['servers'] = [dict(name=serv['name'], uuid=serv['uuid'])
|
|
for serv in servers]
|
|
|
|
# Add any additional info
|
|
if kwargs:
|
|
hyp_dict.update(kwargs)
|
|
|
|
return hyp_dict
|
|
|
|
def _get_compute_nodes_by_name_pattern(self, context, hostname_match):
|
|
compute_nodes = self.host_api.compute_node_search_by_hypervisor(
|
|
context, hostname_match)
|
|
if not compute_nodes:
|
|
msg = (_("No hypervisor matching '%s' could be found.") %
|
|
hostname_match)
|
|
raise webob.exc.HTTPNotFound(explanation=msg)
|
|
return compute_nodes
|
|
|
|
def _get_hypervisors(self, req, detail=False, limit=None, marker=None,
|
|
links=False):
|
|
"""Get hypervisors for the given request.
|
|
|
|
:param req: nova.api.openstack.wsgi.Request for the GET request
|
|
:param detail: If True, return a detailed response.
|
|
:param limit: An optional user-supplied page limit.
|
|
:param marker: An optional user-supplied marker for paging.
|
|
:param links: If True, return links in the response for paging.
|
|
"""
|
|
context = req.environ['nova.context']
|
|
context.can(hv_policies.BASE_POLICY_NAME)
|
|
|
|
# The 2.53 microversion moves the search and servers routes into
|
|
# GET /os-hypervisors and GET /os-hypervisors/detail with query
|
|
# parameters.
|
|
if api_version_request.is_supported(
|
|
req, min_version=UUID_FOR_ID_MIN_VERSION):
|
|
hypervisor_match = req.GET.get('hypervisor_hostname_pattern')
|
|
with_servers = strutils.bool_from_string(
|
|
req.GET.get('with_servers', False), strict=True)
|
|
else:
|
|
hypervisor_match = None
|
|
with_servers = False
|
|
|
|
if hypervisor_match is not None:
|
|
# We have to check for 'limit' in the request itself because
|
|
# the limit passed in is CONF.api.max_limit by default.
|
|
if 'limit' in req.GET or marker:
|
|
# Paging with hostname pattern isn't supported.
|
|
raise webob.exc.HTTPBadRequest(
|
|
_('Paging over hypervisors with the '
|
|
'hypervisor_hostname_pattern query parameter is not '
|
|
'supported.'))
|
|
|
|
# Explicitly do not try to generate links when querying with the
|
|
# hostname pattern since the request in the link would fail the
|
|
# check above.
|
|
links = False
|
|
|
|
# Get all compute nodes with a hypervisor_hostname that matches
|
|
# the given pattern. If none are found then it's a 404 error.
|
|
compute_nodes = self._get_compute_nodes_by_name_pattern(
|
|
context, hypervisor_match)
|
|
else:
|
|
# Get all compute nodes.
|
|
try:
|
|
compute_nodes = self.host_api.compute_node_get_all(
|
|
context, limit=limit, marker=marker)
|
|
except exception.MarkerNotFound:
|
|
msg = _('marker [%s] not found') % marker
|
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
|
|
|
hypervisors_list = []
|
|
for hyp in compute_nodes:
|
|
try:
|
|
instances = None
|
|
if with_servers:
|
|
instances = self.host_api.instance_get_all_by_host(
|
|
context, hyp.host)
|
|
service = self.host_api.service_get_by_compute_host(
|
|
context, hyp.host)
|
|
hypervisors_list.append(
|
|
self._view_hypervisor(
|
|
hyp, service, detail, req, servers=instances))
|
|
except (exception.ComputeHostNotFound,
|
|
exception.HostMappingNotFound):
|
|
# The compute service could be deleted which doesn't delete
|
|
# the compute node record, that has to be manually removed
|
|
# from the database so we just ignore it when listing nodes.
|
|
LOG.debug('Unable to find service for compute node %s. The '
|
|
'service may be deleted and compute nodes need to '
|
|
'be manually cleaned up.', hyp.host)
|
|
|
|
hypervisors_dict = dict(hypervisors=hypervisors_list)
|
|
if links:
|
|
hypervisors_links = self._view_builder.get_links(
|
|
req, hypervisors_list, detail)
|
|
if hypervisors_links:
|
|
hypervisors_dict['hypervisors_links'] = hypervisors_links
|
|
return hypervisors_dict
|
|
|
|
@wsgi.Controller.api_version(UUID_FOR_ID_MIN_VERSION)
|
|
@validation.query_schema(hyper_schema.list_query_schema_v253,
|
|
UUID_FOR_ID_MIN_VERSION)
|
|
@wsgi.expected_errors((400, 404))
|
|
def index(self, req):
|
|
"""Starting with the 2.53 microversion, the id field in the response
|
|
is the compute_nodes.uuid value. Also, the search and servers routes
|
|
are superseded and replaced with query parameters for listing
|
|
hypervisors by a hostname pattern and whether or not to include
|
|
hosted servers in the response.
|
|
"""
|
|
limit, marker = common.get_limit_and_marker(req)
|
|
return self._index(req, limit=limit, marker=marker, links=True)
|
|
|
|
@wsgi.Controller.api_version("2.33", "2.52") # noqa
|
|
@validation.query_schema(hyper_schema.list_query_schema_v233)
|
|
@wsgi.expected_errors((400))
|
|
def index(self, req):
|
|
limit, marker = common.get_limit_and_marker(req)
|
|
return self._index(req, limit=limit, marker=marker, links=True)
|
|
|
|
@wsgi.Controller.api_version("2.1", "2.32") # noqa
|
|
@wsgi.expected_errors(())
|
|
def index(self, req):
|
|
return self._index(req)
|
|
|
|
def _index(self, req, limit=None, marker=None, links=False):
|
|
return self._get_hypervisors(req, detail=False, limit=limit,
|
|
marker=marker, links=links)
|
|
|
|
@wsgi.Controller.api_version(UUID_FOR_ID_MIN_VERSION)
|
|
@validation.query_schema(hyper_schema.list_query_schema_v253,
|
|
UUID_FOR_ID_MIN_VERSION)
|
|
@wsgi.expected_errors((400, 404))
|
|
def detail(self, req):
|
|
"""Starting with the 2.53 microversion, the id field in the response
|
|
is the compute_nodes.uuid value. Also, the search and servers routes
|
|
are superseded and replaced with query parameters for listing
|
|
hypervisors by a hostname pattern and whether or not to include
|
|
hosted servers in the response.
|
|
"""
|
|
limit, marker = common.get_limit_and_marker(req)
|
|
return self._detail(req, limit=limit, marker=marker, links=True)
|
|
|
|
@wsgi.Controller.api_version("2.33", "2.52") # noqa
|
|
@validation.query_schema(hyper_schema.list_query_schema_v233)
|
|
@wsgi.expected_errors((400))
|
|
def detail(self, req):
|
|
limit, marker = common.get_limit_and_marker(req)
|
|
return self._detail(req, limit=limit, marker=marker, links=True)
|
|
|
|
@wsgi.Controller.api_version("2.1", "2.32") # noqa
|
|
@wsgi.expected_errors(())
|
|
def detail(self, req):
|
|
return self._detail(req)
|
|
|
|
def _detail(self, req, limit=None, marker=None, links=False):
|
|
return self._get_hypervisors(req, detail=True, limit=limit,
|
|
marker=marker, links=links)
|
|
|
|
@staticmethod
|
|
def _validate_id(req, hypervisor_id):
|
|
"""Validates that the id is a uuid for microversions that require it.
|
|
|
|
:param req: The HTTP request object which contains the requested
|
|
microversion information.
|
|
:param hypervisor_id: The provided hypervisor id.
|
|
:raises: webob.exc.HTTPBadRequest if the requested microversion is
|
|
greater than or equal to 2.53 and the id is not a uuid.
|
|
:raises: webob.exc.HTTPNotFound if the requested microversion is
|
|
less than 2.53 and the id is not an integer.
|
|
"""
|
|
expect_uuid = api_version_request.is_supported(
|
|
req, min_version=UUID_FOR_ID_MIN_VERSION)
|
|
if expect_uuid:
|
|
if not uuidutils.is_uuid_like(hypervisor_id):
|
|
msg = _('Invalid uuid %s') % hypervisor_id
|
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
|
else:
|
|
# This API is supported for cells v1 and as such the id can be
|
|
# a cell v1 delimited string, so we have to parse it first.
|
|
if cells_utils.CELL_ITEM_SEP in str(hypervisor_id):
|
|
hypervisor_id = cells_utils.split_cell_and_item(
|
|
hypervisor_id)[1]
|
|
try:
|
|
utils.validate_integer(hypervisor_id, 'id')
|
|
except exception.InvalidInput:
|
|
msg = (_("Hypervisor with ID '%s' could not be found.") %
|
|
hypervisor_id)
|
|
raise webob.exc.HTTPNotFound(explanation=msg)
|
|
|
|
@wsgi.Controller.api_version(UUID_FOR_ID_MIN_VERSION)
|
|
@validation.query_schema(hyper_schema.show_query_schema_v253,
|
|
UUID_FOR_ID_MIN_VERSION)
|
|
@wsgi.expected_errors((400, 404))
|
|
def show(self, req, id):
|
|
"""The 2.53 microversion requires that the id is a uuid and as a result
|
|
it can also return a 400 response if an invalid uuid is passed.
|
|
|
|
The 2.53 microversion also supports the with_servers query parameter
|
|
to include a list of servers on the given hypervisor if requested.
|
|
"""
|
|
with_servers = strutils.bool_from_string(
|
|
req.GET.get('with_servers', False), strict=True)
|
|
return self._show(req, id, with_servers)
|
|
|
|
@wsgi.Controller.api_version("2.1", "2.52") # noqa F811
|
|
@wsgi.expected_errors(404)
|
|
def show(self, req, id):
|
|
return self._show(req, id)
|
|
|
|
def _show(self, req, id, with_servers=False):
|
|
context = req.environ['nova.context']
|
|
context.can(hv_policies.BASE_POLICY_NAME)
|
|
|
|
self._validate_id(req, id)
|
|
|
|
try:
|
|
hyp = self.host_api.compute_node_get(context, id)
|
|
instances = None
|
|
if with_servers:
|
|
instances = self.host_api.instance_get_all_by_host(
|
|
context, hyp.host)
|
|
service = self.host_api.service_get_by_compute_host(
|
|
context, hyp.host)
|
|
except (ValueError, exception.ComputeHostNotFound,
|
|
exception.HostMappingNotFound):
|
|
msg = _("Hypervisor with ID '%s' could not be found.") % id
|
|
raise webob.exc.HTTPNotFound(explanation=msg)
|
|
return dict(hypervisor=self._view_hypervisor(
|
|
hyp, service, True, req, instances))
|
|
|
|
@wsgi.expected_errors((400, 404, 501))
|
|
def uptime(self, req, id):
|
|
context = req.environ['nova.context']
|
|
context.can(hv_policies.BASE_POLICY_NAME)
|
|
|
|
self._validate_id(req, id)
|
|
|
|
try:
|
|
hyp = self.host_api.compute_node_get(context, id)
|
|
except (ValueError, exception.ComputeHostNotFound):
|
|
msg = _("Hypervisor with ID '%s' could not be found.") % id
|
|
raise webob.exc.HTTPNotFound(explanation=msg)
|
|
|
|
# Get the uptime
|
|
try:
|
|
host = hyp.host
|
|
uptime = self.host_api.get_host_uptime(context, host)
|
|
service = self.host_api.service_get_by_compute_host(context, host)
|
|
except NotImplementedError:
|
|
common.raise_feature_not_supported()
|
|
except exception.ComputeServiceUnavailable as e:
|
|
raise webob.exc.HTTPBadRequest(explanation=e.format_message())
|
|
except exception.HostMappingNotFound:
|
|
# NOTE(danms): This mirrors the compute_node_get() behavior
|
|
# where the node is missing, resulting in NotFound instead of
|
|
# BadRequest if we fail on the map lookup.
|
|
msg = _("Hypervisor with ID '%s' could not be found.") % id
|
|
raise webob.exc.HTTPNotFound(explanation=msg)
|
|
|
|
return dict(hypervisor=self._view_hypervisor(hyp, service, False, req,
|
|
uptime=uptime))
|
|
|
|
@wsgi.Controller.api_version('2.1', '2.52')
|
|
@wsgi.expected_errors(404)
|
|
def search(self, req, id):
|
|
"""Prior to microversion 2.53 you could search for hypervisors by a
|
|
hostname pattern on a dedicated route. Starting with 2.53, searching
|
|
by a hostname pattern is a query parameter in the GET /os-hypervisors
|
|
index and detail methods.
|
|
"""
|
|
context = req.environ['nova.context']
|
|
context.can(hv_policies.BASE_POLICY_NAME)
|
|
hypervisors = self._get_compute_nodes_by_name_pattern(context, id)
|
|
try:
|
|
return dict(hypervisors=[
|
|
self._view_hypervisor(
|
|
hyp,
|
|
self.host_api.service_get_by_compute_host(context,
|
|
hyp.host),
|
|
False, req)
|
|
for hyp in hypervisors])
|
|
except exception.HostMappingNotFound:
|
|
msg = _("No hypervisor matching '%s' could be found.") % id
|
|
raise webob.exc.HTTPNotFound(explanation=msg)
|
|
|
|
@wsgi.Controller.api_version('2.1', '2.52')
|
|
@wsgi.expected_errors(404)
|
|
def servers(self, req, id):
|
|
"""Prior to microversion 2.53 you could search for hypervisors by a
|
|
hostname pattern and include servers on those hosts in the response on
|
|
a dedicated route. Starting with 2.53, searching by a hostname pattern
|
|
and including hosted servers is a query parameter in the
|
|
GET /os-hypervisors index and detail methods.
|
|
"""
|
|
context = req.environ['nova.context']
|
|
context.can(hv_policies.BASE_POLICY_NAME)
|
|
compute_nodes = self._get_compute_nodes_by_name_pattern(context, id)
|
|
hypervisors = []
|
|
for compute_node in compute_nodes:
|
|
try:
|
|
instances = self.host_api.instance_get_all_by_host(context,
|
|
compute_node.host)
|
|
service = self.host_api.service_get_by_compute_host(
|
|
context, compute_node.host)
|
|
except exception.HostMappingNotFound as e:
|
|
raise webob.exc.HTTPNotFound(explanation=e.format_message())
|
|
hyp = self._view_hypervisor(compute_node, service, False, req,
|
|
instances)
|
|
hypervisors.append(hyp)
|
|
return dict(hypervisors=hypervisors)
|
|
|
|
@wsgi.expected_errors(())
|
|
def statistics(self, req):
|
|
context = req.environ['nova.context']
|
|
context.can(hv_policies.BASE_POLICY_NAME)
|
|
stats = self.host_api.compute_node_statistics(context)
|
|
return dict(hypervisor_statistics=stats)
|