Handle uuids in os-hypervisors API

There are quite a few changes here as this is not only handling
uuids for the hypervisor id but it's also a refactor in several
APIs for consistency.

The main changes are detailed in the REST API Version History
doc in this change, but to summarize the changes:

* Hypervisor and service IDs are handled as the UUIDs for those
  resources; this is necessary for accurately working with these
  resources across multiple cells.
* The 'servers' and 'search' routes are deprecated and folded into
  the index and detail methods as query parameters, validated using
  json schema.
* The show method will also be able to return the list of servers
  hosted on the given hypervisor using the with_servers query
  parameter.
* The marker used when paging over lists of hypervisors is the
  compute node UUID.
* Using the hypervisor_hostname_pattern query parameter will not
  work with paging parameters.
* API reference docs are updated for the detailed changes.
* Functional and unit tests are provided for all changes.

Part of blueprint service-hyper-uuid-in-api

Change-Id: I828350c179df8bcfa4739910abeafaba2f96982b
This commit is contained in:
Matt Riedemann 2017-07-18 15:38:07 -04:00
parent 2f7bf29d47
commit 622bfb2e95
32 changed files with 1520 additions and 61 deletions

View File

@ -25,7 +25,7 @@ the ``policy.json`` file.
Normal response codes: 200 Normal response codes: 200
Error response codes: unauthorized(401), forbidden(403) Error response codes: badRequest(400), unauthorized(401), forbidden(403)
Request Request
------- -------
@ -34,6 +34,9 @@ Request
- limit: hypervisor_limit - limit: hypervisor_limit
- marker: hypervisor_marker - marker: hypervisor_marker
- marker: hypervisor_marker_uuid
- hypervisor_hostname_pattern: hypervisor_hostname_pattern_query
- with_servers: hypervisor_with_servers_query
Response Response
-------- --------
@ -43,15 +46,24 @@ Response
- hypervisors: hypervisors - hypervisors: hypervisors
- hypervisor_hostname: hypervisor_hostname - hypervisor_hostname: hypervisor_hostname
- id: hypervisor_id_body - id: hypervisor_id_body
- id: hypervisor_id_body_uuid
- state: hypervisor_state - state: hypervisor_state
- status: hypervisor_status - status: hypervisor_status
- hypervisor_links: hypervisor_links - hypervisor_links: hypervisor_links
- servers: hypervisor_servers
- servers.uuid: hypervisor_servers_uuid
- servers.name: hypervisor_servers_name
**Example List Hypervisors (v2.33): JSON response** **Example List Hypervisors (v2.33): JSON response**
.. literalinclude:: ../../doc/api_samples/os-hypervisors/v2.33/hypervisors-list-resp.json .. literalinclude:: ../../doc/api_samples/os-hypervisors/v2.33/hypervisors-list-resp.json
:language: javascript :language: javascript
**Example List Hypervisors With Servers (v2.53): JSON response**
.. literalinclude:: ../../doc/api_samples/os-hypervisors/v2.53/hypervisors-with-servers-resp.json
:language: javascript
List Hypervisors Details List Hypervisors Details
======================== ========================
@ -65,7 +77,7 @@ the ``policy.json`` file.
Normal response codes: 200 Normal response codes: 200
Error response codes: unauthorized(401), forbidden(403) Error response codes: badRequest(400), unauthorized(401), forbidden(403)
Request Request
------- -------
@ -74,6 +86,9 @@ Request
- limit: hypervisor_limit - limit: hypervisor_limit
- marker: hypervisor_marker - marker: hypervisor_marker
- marker: hypervisor_marker_uuid
- hypervisor_hostname_pattern: hypervisor_hostname_pattern_query
- with_servers: hypervisor_with_servers_query
Response Response
-------- --------
@ -93,14 +108,19 @@ Response
- hypervisor_type: hypervisor_type_body - hypervisor_type: hypervisor_type_body
- hypervisor_version: hypervisor_version - hypervisor_version: hypervisor_version
- id: hypervisor_id_body - id: hypervisor_id_body
- id: hypervisor_id_body_uuid
- local_gb: local_gb - local_gb: local_gb
- local_gb_used: local_gb_used - local_gb_used: local_gb_used
- memory_mb: memory_mb - memory_mb: memory_mb
- memory_mb_used: memory_mb_used - memory_mb_used: memory_mb_used
- running_vms: running_vms - running_vms: running_vms
- servers: hypervisor_servers
- servers.uuid: hypervisor_servers_uuid
- servers.name: hypervisor_servers_name
- service: hypervisor_service - service: hypervisor_service
- service.host: host_name_body - service.host: host_name_body
- service.id: service_id_body - service.id: service_id_body_2_52
- service.id: service_id_body_2_53
- service.disable_reason: service_disable_reason - service.disable_reason: service_disable_reason
- vcpus: hypervisor_vcpus - vcpus: hypervisor_vcpus
- vcpus_used: hypervisor_vcpus_used - vcpus_used: hypervisor_vcpus_used
@ -111,6 +131,11 @@ Response
.. literalinclude:: ../../doc/api_samples/os-hypervisors/v2.33/hypervisors-detail-resp.json .. literalinclude:: ../../doc/api_samples/os-hypervisors/v2.33/hypervisors-detail-resp.json
:language: javascript :language: javascript
**Example List Hypervisors Details (v2.53): JSON response**
.. literalinclude:: ../../doc/api_samples/os-hypervisors/v2.53/hypervisors-detail-resp.json
:language: javascript
Show Hypervisor Statistics Show Hypervisor Statistics
========================== ==========================
@ -163,7 +188,7 @@ the ``policy.json`` file.
Normal response codes: 200 Normal response codes: 200
Error response codes: unauthorized(401), forbidden(403), itemNotFound(404) Error response codes: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)
Request Request
------- -------
@ -171,6 +196,8 @@ Request
.. rest_parameters:: parameters.yaml .. rest_parameters:: parameters.yaml
- hypervisor_id: hypervisor_id - hypervisor_id: hypervisor_id
- hypervisor_id: hypervisor_id_uuid
- with_servers: hypervisor_with_servers_query
Response Response
-------- --------
@ -190,14 +217,19 @@ Response
- hypervisor_type: hypervisor_type_body - hypervisor_type: hypervisor_type_body
- hypervisor_version: hypervisor_version - hypervisor_version: hypervisor_version
- id: hypervisor_id_body - id: hypervisor_id_body
- id: hypervisor_id_body_uuid
- local_gb: local_gb - local_gb: local_gb
- local_gb_used: local_gb_used - local_gb_used: local_gb_used
- memory_mb: memory_mb - memory_mb: memory_mb
- memory_mb_used: memory_mb_used - memory_mb_used: memory_mb_used
- running_vms: running_vms - running_vms: running_vms
- servers: hypervisor_servers
- servers.uuid: hypervisor_servers_uuid
- servers.name: hypervisor_servers_name
- service: hypervisor_service - service: hypervisor_service
- service.host: host_name_body - service.host: host_name_body
- service.id: service_id_body - service.id: service_id_body_2_52
- service.id: service_id_body_2_53
- service.disable_reason: service_disable_reason - service.disable_reason: service_disable_reason
- vcpus: hypervisor_vcpus - vcpus: hypervisor_vcpus
- vcpus_used: hypervisor_vcpus_used - vcpus_used: hypervisor_vcpus_used
@ -207,6 +239,11 @@ Response
.. literalinclude:: ../../doc/api_samples/os-hypervisors/v2.28/hypervisors-show-resp.json .. literalinclude:: ../../doc/api_samples/os-hypervisors/v2.28/hypervisors-show-resp.json
:language: javascript :language: javascript
**Example Show Hypervisor Details With Servers (v2.53): JSON response**
.. literalinclude:: ../../doc/api_samples/os-hypervisors/v2.53/hypervisors-show-with-servers-resp.json
:language: javascript
Show Hypervisor Uptime Show Hypervisor Uptime
====================== ======================
@ -220,7 +257,7 @@ the ``policy.json`` file.
Normal response codes: 200 Normal response codes: 200
Error response codes: unauthorized(401), forbidden(403), itemNotFound(404), NotImplemented(501) Error response codes: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404), NotImplemented(501)
Request Request
------- -------
@ -228,6 +265,7 @@ Request
.. rest_parameters:: parameters.yaml .. rest_parameters:: parameters.yaml
- hypervisor_id: hypervisor_id - hypervisor_id: hypervisor_id
- hypervisor_id: hypervisor_id_uuid
Response Response
-------- --------
@ -237,6 +275,7 @@ Response
- hypervisor: hypervisor - hypervisor: hypervisor
- hypervisor_hostname: hypervisor_hostname - hypervisor_hostname: hypervisor_hostname
- id: hypervisor_id_body - id: hypervisor_id_body
- id: hypervisor_id_body_uuid
- state: hypervisor_state - state: hypervisor_state
- status: hypervisor_status - status: hypervisor_status
- uptime: uptime - uptime: uptime
@ -246,13 +285,23 @@ Response
.. literalinclude:: ../../doc/api_samples/os-hypervisors/hypervisors-uptime-resp.json .. literalinclude:: ../../doc/api_samples/os-hypervisors/hypervisors-uptime-resp.json
:language: javascript :language: javascript
**Example Show Hypervisor Uptime (v2.53): JSON response**
.. literalinclude:: ../../doc/api_samples/os-hypervisors/v2.53/hypervisors-uptime-resp.json
:language: javascript
Search Hypervisor Search Hypervisor
================= =================
.. rest_method:: GET /os-hypervisors/{hypervisor_hostname_pattern}/search .. rest_method:: GET /os-hypervisors/{hypervisor_hostname_pattern}/search
max_version: 2.52
Search hypervisor by a given hypervisor host name or portion of it. Search hypervisor by a given hypervisor host name or portion of it.
.. warning:: This API is deprecated starting with microversion 2.53. Use
`List Hypervisors`_ with the ``hypervisor_hostname_pattern`` query
parameter with microversion 2.53 and later.
Policy defaults enable only users with the administrative role to perform Policy defaults enable only users with the administrative role to perform
this operation. Cloud providers can change these permissions through this operation. Cloud providers can change these permissions through
the ``policy.json`` file. the ``policy.json`` file.
@ -275,7 +324,7 @@ Response
- hypervisors: hypervisors - hypervisors: hypervisors
- hypervisor_hostname: hypervisor_hostname - hypervisor_hostname: hypervisor_hostname
- id: hypervisor_id_body - id: hypervisor_id_body_no_version
- state: hypervisor_state - state: hypervisor_state
- status: hypervisor_status - status: hypervisor_status
@ -288,10 +337,15 @@ List Hypervisor Servers
======================= =======================
.. rest_method:: GET /os-hypervisors/{hypervisor_hostname_pattern}/servers .. rest_method:: GET /os-hypervisors/{hypervisor_hostname_pattern}/servers
max_version: 2.52
List all servers belong to each hypervisor whose host name is matching List all servers belong to each hypervisor whose host name is matching
a given hypervisor host name or portion of it. a given hypervisor host name or portion of it.
.. warning:: This API is deprecated starting with microversion 2.53. Use
`List Hypervisors`_ with the ``hypervisor_hostname_pattern`` and
``with_servers`` query parameters with microversion 2.53 and later.
Policy defaults enable only users with the administrative role to perform Policy defaults enable only users with the administrative role to perform
this operation. Cloud providers can change these permissions through this operation. Cloud providers can change these permissions through
the ``policy.json`` file. the ``policy.json`` file.
@ -314,7 +368,7 @@ Response
- hypervisors: hypervisors - hypervisors: hypervisors
- hypervisor_hostname: hypervisor_hostname - hypervisor_hostname: hypervisor_hostname
- id: hypervisor_id_body - id: hypervisor_id_body_no_version
- state: hypervisor_state - state: hypervisor_state
- status: hypervisor_status - status: hypervisor_status
- servers: servers - servers: servers

View File

@ -182,6 +182,14 @@ hypervisor_id:
in: path in: path
required: true required: true
type: integer type: integer
max_version: 2.52
hypervisor_id_uuid:
description: |
The ID of the hypervisor as a UUID.
in: path
required: true
type: string
min_version: 2.53
image_id: image_id:
description: | description: |
The UUID of the image. The UUID of the image.
@ -565,6 +573,19 @@ hostname_query_server:
in: query in: query
required: false required: false
type: string type: string
hypervisor_hostname_pattern_query:
description: |
The hypervisor host name or a portion of it. The hypervisor hosts are
selected with the host name matching this pattern.
.. note:: ``limit`` and ``marker`` query parameters for paging are
not supported when listing hypervisors using a hostname pattern.
Also, ``links`` will not be returned in the response when using this
query parameter.
in: query
required: false
type: string
min_version: 2.53
hypervisor_limit: hypervisor_limit:
description: | description: |
Requests a page size of items. Returns a number of items up to a limit value. Requests a page size of items. Returns a number of items up to a limit value.
@ -584,12 +605,29 @@ hypervisor_marker:
required: false required: false
type: integer type: integer
min_version: 2.33 min_version: 2.33
max_version: 2.52
hypervisor_marker_uuid:
description: |
The ID of the last-seen item as a UUID. Use the ``limit`` parameter to make
an initial limited request and use the ID of the last-seen item from the
response as the ``marker`` parameter value in a subsequent limited request.
in: query
required: false
type: string
min_version: 2.53
hypervisor_query: hypervisor_query:
description: | description: |
Filters the response by a hypervisor type. Filters the response by a hypervisor type.
in: query in: query
required: false required: false
type: string type: string
hypervisor_with_servers_query:
description: |
Include all servers which belong to each hypervisor in the response output.
in: query
required: false
type: boolean
min_version: 2.53
image_name_query: image_name_query:
description: | description: |
Filters the response by an image name, as a string. Filters the response by an image name, as a string.
@ -2965,6 +3003,20 @@ hypervisor_id_body:
in: body in: body
required: true required: true
type: integer type: integer
max_version: 2.52
hypervisor_id_body_no_version:
description: |
The id of the hypervisor.
in: body
required: true
type: integer
hypervisor_id_body_uuid:
description: |
The id of the hypervisor as a UUID.
in: body
required: true
type: string
min_version: 2.53
hypervisor_links: hypervisor_links:
description: | description: |
Links to the hypervisors resource. See `API Guide / Links and Links to the hypervisors resource. See `API Guide / Links and
@ -2982,6 +3034,27 @@ hypervisor_os_diagnostics:
type: string type: string
required: true required: true
min_version: 2.48 min_version: 2.48
hypervisor_servers:
description: |
A list of ``server`` objects.
in: body
required: false
type: array
min_version: 2.53
hypervisor_servers_name:
description: |
The server name.
in: body
required: false
type: string
min_version: 2.53
hypervisor_servers_uuid:
description: |
The server ID.
in: body
required: false
type: string
min_version: 2.53
hypervisor_service: hypervisor_service:
description: | description: |
The hypervisor service object. The hypervisor service object.

View File

@ -0,0 +1,49 @@
{
"hypervisors": [
{
"cpu_info": {
"arch": "x86_64",
"model": "Nehalem",
"vendor": "Intel",
"features": [
"pge",
"clflush"
],
"topology": {
"cores": 1,
"threads": 1,
"sockets": 4
}
},
"current_workload": 0,
"status": "enabled",
"state": "up",
"disk_available_least": 0,
"host_ip": "1.1.1.1",
"free_disk_gb": 1028,
"free_ram_mb": 7680,
"hypervisor_hostname": "fake-mini",
"hypervisor_type": "fake",
"hypervisor_version": 1000,
"id": "1bb62a04-c576-402c-8147-9e89757a09e3",
"local_gb": 1028,
"local_gb_used": 0,
"memory_mb": 8192,
"memory_mb_used": 512,
"running_vms": 0,
"service": {
"host": "host1",
"id": "62f62f6e-a713-4cbe-87d3-3ecf8a1e0f8d",
"disabled_reason": null
},
"vcpus": 1,
"vcpus_used": 0
}
],
"hypervisors_links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/hypervisors/detail?limit=1&marker=1bb62a04-c576-402c-8147-9e89757a09e3",
"rel": "next"
}
]
}

View File

@ -0,0 +1,53 @@
{
"hypervisors": [
{
"cpu_info": {
"arch": "x86_64",
"model": "Nehalem",
"vendor": "Intel",
"features": [
"pge",
"clflush"
],
"topology": {
"cores": 1,
"threads": 1,
"sockets": 4
}
},
"current_workload": 0,
"status": "enabled",
"servers": [
{
"name": "test_server1",
"uuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
},
{
"name": "test_server2",
"uuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
}
],
"state": "up",
"disk_available_least": 0,
"host_ip": "1.1.1.1",
"free_disk_gb": 1028,
"free_ram_mb": 7680,
"hypervisor_hostname": "fake-mini",
"hypervisor_type": "fake",
"hypervisor_version": 1000,
"id": "b1e43b5f-eec1-44e0-9f10-7b4945c0226d",
"local_gb": 1028,
"local_gb_used": 0,
"memory_mb": 8192,
"memory_mb_used": 512,
"running_vms": 0,
"service": {
"host": "host1",
"id": "5d343e1d-938e-4284-b98b-6a2b5406ba76",
"disabled_reason": null
},
"vcpus": 1,
"vcpus_used": 0
}
]
}

View File

@ -0,0 +1,16 @@
{
"hypervisors": [
{
"hypervisor_hostname": "fake-mini",
"id": "1bb62a04-c576-402c-8147-9e89757a09e3",
"state": "up",
"status": "enabled"
}
],
"hypervisors_links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/hypervisors?limit=1&marker=1bb62a04-c576-402c-8147-9e89757a09e3",
"rel": "next"
}
]
}

View File

@ -0,0 +1,10 @@
{
"hypervisors": [
{
"hypervisor_hostname": "fake-mini",
"id": "b1e43b5f-eec1-44e0-9f10-7b4945c0226d",
"state": "up",
"status": "enabled"
}
]
}

View File

@ -0,0 +1,41 @@
{
"hypervisor": {
"cpu_info": {
"arch": "x86_64",
"model": "Nehalem",
"vendor": "Intel",
"features": [
"pge",
"clflush"
],
"topology": {
"cores": 1,
"threads": 1,
"sockets": 4
}
},
"state": "up",
"status": "enabled",
"current_workload": 0,
"disk_available_least": 0,
"host_ip": "1.1.1.1",
"free_disk_gb": 1028,
"free_ram_mb": 7680,
"hypervisor_hostname": "fake-mini",
"hypervisor_type": "fake",
"hypervisor_version": 1000,
"id": "b1e43b5f-eec1-44e0-9f10-7b4945c0226d",
"local_gb": 1028,
"local_gb_used": 0,
"memory_mb": 8192,
"memory_mb_used": 512,
"running_vms": 0,
"service": {
"host": "043b3cacf6f34c90a7245151fc8ebcda",
"id": "5d343e1d-938e-4284-b98b-6a2b5406ba76",
"disabled_reason": null
},
"vcpus": 1,
"vcpus_used": 0
}
}

View File

@ -0,0 +1,51 @@
{
"hypervisor": {
"cpu_info": {
"arch": "x86_64",
"model": "Nehalem",
"vendor": "Intel",
"features": [
"pge",
"clflush"
],
"topology": {
"cores": 1,
"threads": 1,
"sockets": 4
}
},
"state": "up",
"status": "enabled",
"servers": [
{
"name": "test_server1",
"uuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
},
{
"name": "test_server2",
"uuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
}
],
"current_workload": 0,
"disk_available_least": 0,
"host_ip": "1.1.1.1",
"free_disk_gb": 1028,
"free_ram_mb": 7680,
"hypervisor_hostname": "fake-mini",
"hypervisor_type": "fake",
"hypervisor_version": 1000,
"id": "b1e43b5f-eec1-44e0-9f10-7b4945c0226d",
"local_gb": 1028,
"local_gb_used": 0,
"memory_mb": 8192,
"memory_mb_used": 512,
"running_vms": 0,
"service": {
"host": "043b3cacf6f34c90a7245151fc8ebcda",
"id": "5d343e1d-938e-4284-b98b-6a2b5406ba76",
"disabled_reason": null
},
"vcpus": 1,
"vcpus_used": 0
}
}

View File

@ -0,0 +1,16 @@
{
"hypervisor_statistics": {
"count": 1,
"current_workload": 0,
"disk_available_least": 0,
"free_disk_gb": 1028,
"free_ram_mb": 7680,
"local_gb": 1028,
"local_gb_used": 0,
"memory_mb": 8192,
"memory_mb_used": 512,
"running_vms": 0,
"vcpus": 1,
"vcpus_used": 0
}
}

View File

@ -0,0 +1,9 @@
{
"hypervisor": {
"hypervisor_hostname": "fake-mini",
"id": "b1e43b5f-eec1-44e0-9f10-7b4945c0226d",
"state": "up",
"status": "enabled",
"uptime": " 08:32:11 up 93 days, 18:25, 12 users, load average: 0.20, 0.12, 0.14"
}
}

View File

@ -0,0 +1,20 @@
{
"hypervisors": [
{
"hypervisor_hostname": "fake-mini",
"id": "b1e43b5f-eec1-44e0-9f10-7b4945c0226d",
"state": "up",
"status": "enabled",
"servers": [
{
"name": "test_server1",
"uuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
},
{
"name": "test_server2",
"uuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
}
]
}
]
}

View File

@ -0,0 +1,10 @@
{
"hypervisors": [
{
"hypervisor_hostname": "fake-mini",
"id": "b1e43b5f-eec1-44e0-9f10-7b4945c0226d",
"state": "up",
"status": "enabled"
}
]
}

View File

@ -124,9 +124,10 @@ REST_API_VERSION_HISTORY = """REST API Version History:
non-admins can see instance action event details except for the non-admins can see instance action event details except for the
traceback field. traceback field.
* 2.52 - Adds support for applying tags when creating a server. * 2.52 - Adds support for applying tags when creating a server.
* 2.53 - Service database ids are hidden. The os-services API now returns * 2.53 - Service and compute node (hypervisor) database ids are hidden.
a uuid in the id field, and takes a uuid in The os-services and os-hypervisors APIs now return a uuid in the
DELETE /services/{service_uuid}. id field, and takes a uuid in requests. PUT and GET requests
and responses are also changed.
""" """
# The minimum and maximum versions of the API supported # The minimum and maximum versions of the API supported

View File

@ -17,21 +17,29 @@
from oslo_log import log as logging from oslo_log import log as logging
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from oslo_utils import strutils
from oslo_utils import uuidutils
import webob.exc import webob.exc
from nova.api.openstack import api_version_request from nova.api.openstack import api_version_request
from nova.api.openstack import common 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.compute.views import hypervisors as hyper_view
from nova.api.openstack import extensions from nova.api.openstack import extensions
from nova.api.openstack import wsgi 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 compute
from nova import exception from nova import exception
from nova.i18n import _ from nova.i18n import _
from nova.policies import hypervisors as hv_policies from nova.policies import hypervisors as hv_policies
from nova import servicegroup from nova import servicegroup
from nova import utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
UUID_FOR_ID_MIN_VERSION = '2.53'
class HypervisorsController(wsgi.Controller): class HypervisorsController(wsgi.Controller):
"""The Hypervisors API controller for the OpenStack API.""" """The Hypervisors API controller for the OpenStack API."""
@ -46,15 +54,18 @@ class HypervisorsController(wsgi.Controller):
def _view_hypervisor(self, hypervisor, service, detail, req, servers=None, def _view_hypervisor(self, hypervisor, service, detail, req, servers=None,
**kwargs): **kwargs):
alive = self.servicegroup_api.service_is_up(service) 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 = { hyp_dict = {
'id': hypervisor.id, 'id': hypervisor.uuid if uuid_for_id else hypervisor.id,
'hypervisor_hostname': hypervisor.hypervisor_hostname, 'hypervisor_hostname': hypervisor.hypervisor_hostname,
'state': 'up' if alive else 'down', 'state': 'up' if alive else 'down',
'status': ('disabled' if service.disabled 'status': ('disabled' if service.disabled
else 'enabled'), else 'enabled'),
} }
if detail and not servers: if detail:
for field in ('vcpus', 'memory_mb', 'local_gb', 'vcpus_used', for field in ('vcpus', 'memory_mb', 'local_gb', 'vcpus_used',
'memory_mb_used', 'local_gb_used', 'memory_mb_used', 'local_gb_used',
'hypervisor_type', 'hypervisor_version', 'hypervisor_type', 'hypervisor_version',
@ -62,8 +73,9 @@ class HypervisorsController(wsgi.Controller):
'running_vms', 'disk_available_least', 'host_ip'): 'running_vms', 'disk_available_least', 'host_ip'):
hyp_dict[field] = getattr(hypervisor, field) hyp_dict[field] = getattr(hypervisor, field)
service_id = service.uuid if uuid_for_id else service.id
hyp_dict['service'] = { hyp_dict['service'] = {
'id': service.id, 'id': service_id,
'host': hypervisor.host, 'host': hypervisor.host,
'disabled_reason': service.disabled_reason, 'disabled_reason': service.disabled_reason,
} }
@ -108,6 +120,39 @@ class HypervisorsController(wsgi.Controller):
context = req.environ['nova.context'] context = req.environ['nova.context']
context.can(hv_policies.BASE_POLICY_NAME) 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: try:
compute_nodes = self.host_api.compute_node_get_all( compute_nodes = self.host_api.compute_node_get_all(
context, limit=limit, marker=marker) context, limit=limit, marker=marker)
@ -118,11 +163,15 @@ class HypervisorsController(wsgi.Controller):
hypervisors_list = [] hypervisors_list = []
for hyp in compute_nodes: for hyp in compute_nodes:
try: 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( service = self.host_api.service_get_by_compute_host(
context, hyp.host) context, hyp.host)
hypervisors_list.append( hypervisors_list.append(
self._view_hypervisor( self._view_hypervisor(
hyp, service, detail, req)) hyp, service, detail, req, servers=instances))
except (exception.ComputeHostNotFound, except (exception.ComputeHostNotFound,
exception.HostMappingNotFound): exception.HostMappingNotFound):
# The compute service could be deleted which doesn't delete # The compute service could be deleted which doesn't delete
@ -140,7 +189,21 @@ class HypervisorsController(wsgi.Controller):
hypervisors_dict['hypervisors_links'] = hypervisors_links hypervisors_dict['hypervisors_links'] = hypervisors_links
return hypervisors_dict return hypervisors_dict
@wsgi.Controller.api_version("2.33") # noqa @wsgi.Controller.api_version(UUID_FOR_ID_MIN_VERSION)
@validation.query_schema(hyper_schema.list_query_schema_v253,
UUID_FOR_ID_MIN_VERSION)
@extensions.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
@extensions.expected_errors((400)) @extensions.expected_errors((400))
def index(self, req): def index(self, req):
limit, marker = common.get_limit_and_marker(req) limit, marker = common.get_limit_and_marker(req)
@ -155,7 +218,21 @@ class HypervisorsController(wsgi.Controller):
return self._get_hypervisors(req, detail=False, limit=limit, return self._get_hypervisors(req, detail=False, limit=limit,
marker=marker, links=links) marker=marker, links=links)
@wsgi.Controller.api_version("2.33") # noqa @wsgi.Controller.api_version(UUID_FOR_ID_MIN_VERSION)
@validation.query_schema(hyper_schema.list_query_schema_v253,
UUID_FOR_ID_MIN_VERSION)
@extensions.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
@extensions.expected_errors((400)) @extensions.expected_errors((400))
def detail(self, req): def detail(self, req):
limit, marker = common.get_limit_and_marker(req) limit, marker = common.get_limit_and_marker(req)
@ -170,12 +247,69 @@ class HypervisorsController(wsgi.Controller):
return self._get_hypervisors(req, detail=True, limit=limit, return self._get_hypervisors(req, detail=True, limit=limit,
marker=marker, links=links) 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)
@extensions.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
@extensions.expected_errors(404) @extensions.expected_errors(404)
def show(self, req, id): def show(self, req, id):
return self._show(req, id)
def _show(self, req, id, with_servers=False):
context = req.environ['nova.context'] context = req.environ['nova.context']
context.can(hv_policies.BASE_POLICY_NAME) context.can(hv_policies.BASE_POLICY_NAME)
self._validate_id(req, id)
try: try:
hyp = self.host_api.compute_node_get(context, id) 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( service = self.host_api.service_get_by_compute_host(
context, hyp.host) context, hyp.host)
except (ValueError, exception.ComputeHostNotFound, except (ValueError, exception.ComputeHostNotFound,
@ -183,12 +317,15 @@ class HypervisorsController(wsgi.Controller):
msg = _("Hypervisor with ID '%s' could not be found.") % id msg = _("Hypervisor with ID '%s' could not be found.") % id
raise webob.exc.HTTPNotFound(explanation=msg) raise webob.exc.HTTPNotFound(explanation=msg)
return dict(hypervisor=self._view_hypervisor( return dict(hypervisor=self._view_hypervisor(
hyp, service, True, req)) hyp, service, True, req, instances))
@extensions.expected_errors((400, 404, 501)) @extensions.expected_errors((400, 404, 501))
def uptime(self, req, id): def uptime(self, req, id):
context = req.environ['nova.context'] context = req.environ['nova.context']
context.can(hv_policies.BASE_POLICY_NAME) context.can(hv_policies.BASE_POLICY_NAME)
self._validate_id(req, id)
try: try:
hyp = self.host_api.compute_node_get(context, id) hyp = self.host_api.compute_node_get(context, id)
except (ValueError, exception.ComputeHostNotFound): except (ValueError, exception.ComputeHostNotFound):
@ -214,8 +351,14 @@ class HypervisorsController(wsgi.Controller):
return dict(hypervisor=self._view_hypervisor(hyp, service, False, req, return dict(hypervisor=self._view_hypervisor(hyp, service, False, req,
uptime=uptime)) uptime=uptime))
@wsgi.Controller.api_version('2.1', '2.52')
@extensions.expected_errors(404) @extensions.expected_errors(404)
def search(self, req, id): 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 = req.environ['nova.context']
context.can(hv_policies.BASE_POLICY_NAME) context.can(hv_policies.BASE_POLICY_NAME)
hypervisors = self._get_compute_nodes_by_name_pattern(context, id) hypervisors = self._get_compute_nodes_by_name_pattern(context, id)
@ -231,8 +374,15 @@ class HypervisorsController(wsgi.Controller):
msg = _("No hypervisor matching '%s' could be found.") % id msg = _("No hypervisor matching '%s' could be found.") % id
raise webob.exc.HTTPNotFound(explanation=msg) raise webob.exc.HTTPNotFound(explanation=msg)
@wsgi.Controller.api_version('2.1', '2.52')
@extensions.expected_errors(404) @extensions.expected_errors(404)
def servers(self, req, id): 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 = req.environ['nova.context']
context.can(hv_policies.BASE_POLICY_NAME) context.can(hv_policies.BASE_POLICY_NAME)
compute_nodes = self._get_compute_nodes_by_name_pattern(context, id) compute_nodes = self._get_compute_nodes_by_name_pattern(context, id)

View File

@ -657,3 +657,30 @@ user documentation.
* ``PUT /os-services/{service_uuid}`` will now return a full service resource * ``PUT /os-services/{service_uuid}`` will now return a full service resource
representation like in a ``GET`` response representation like in a ``GET`` response
**os-hypervisors**
Hypervisors are now identified by uuid instead of database id to ensure
uniqueness across cells. This microversion brings the following changes:
* ``GET /os-hypervisors/{hypervisor_hostname_pattern}/search`` is deprecated
and replaced with the ``hypervisor_hostname_pattern`` query parameter on
the ``GET /os-hypervisors`` and ``GET /os-hypervisors/detail`` APIs.
Paging with ``hypervisor_hostname_pattern`` is not supported.
* ``GET /os-hypervisors/{hypervisor_hostname_pattern}/servers`` is deprecated
and replaced with the ``with_servers`` query parameter on the
``GET /os-hypervisors`` and ``GET /os-hypervisors/detail`` APIs.
* ``GET /os-hypervisors/{hypervisor_id}`` supports the ``with_servers`` query
parameter to include hosted server details in the response.
* ``GET /os-hypervisors/{hypervisor_id}`` and
``GET /os-hypervisors/{hypervisor_id}/uptime`` APIs now take a uuid value
for the ``{hypervisor_id}`` path parameter.
* The ``GET /os-hypervisors`` and ``GET /os-hypervisors/detail`` APIs will
now use a uuid marker for paging across cells.
* The following APIs will now return a uuid value for the hypervisor id and
optionally service id fields in the response:
* ``GET /os-hypervisors``
* ``GET /os-hypervisors/detail``
* ``GET /os-hypervisors/{hypervisor_id}``
* ``GET /os-hypervisors/{hypervisor_id}/uptime``

View File

@ -0,0 +1,43 @@
# Copyright 2017 Huawei Technologies Co.,LTD.
#
# 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.
from nova.api.validation import parameter_types
list_query_schema_v253 = {
'type': 'object',
'properties': {
# The 2.33 microversion added support for paging by limit and marker.
'limit': parameter_types.single_param(
parameter_types.non_negative_integer),
'marker': parameter_types.single_param({'type': 'string'}),
# The 2.53 microversion adds support for filtering by hostname pattern
# and requesting hosted servers in the GET /os-hypervisors and
# GET /os-hypervisors/detail response.
'hypervisor_hostname_pattern': parameter_types.single_param(
parameter_types.hostname),
'with_servers': parameter_types.single_param(
parameter_types.boolean)
},
'additionalProperties': False
}
show_query_schema_v253 = {
'type': 'object',
'properties': {
'with_servers': parameter_types.single_param(
parameter_types.boolean)
},
'additionalProperties': False
}

View File

@ -34,7 +34,7 @@ PATH_CELL_SEP = '!'
# meaningful PATH_CELL_SEP in an invalid way will need to suffice. # meaningful PATH_CELL_SEP in an invalid way will need to suffice.
BLOCK_SYNC_FLAG = '!!' BLOCK_SYNC_FLAG = '!!'
# Separator used between cell name and item # Separator used between cell name and item
_CELL_ITEM_SEP = '@' CELL_ITEM_SEP = '@'
CONF = nova.conf.CONF CONF = nova.conf.CONF
@ -192,12 +192,12 @@ def cell_with_item(cell_name, item):
"""Turn cell_name and item into <cell_name>@<item>.""" """Turn cell_name and item into <cell_name>@<item>."""
if cell_name is None: if cell_name is None:
return item return item
return cell_name + _CELL_ITEM_SEP + str(item) return cell_name + CELL_ITEM_SEP + str(item)
def split_cell_and_item(cell_and_item): def split_cell_and_item(cell_and_item):
"""Split a combined cell@item and return them.""" """Split a combined cell@item and return them."""
result = cell_and_item.rsplit(_CELL_ITEM_SEP, 1) result = cell_and_item.rsplit(CELL_ITEM_SEP, 1)
if len(result) == 1: if len(result) == 1:
return (None, cell_and_item) return (None, cell_and_item)
else: else:

View File

@ -0,0 +1,49 @@
{
"hypervisors": [
{
"cpu_info": {
"arch": "x86_64",
"model": "Nehalem",
"vendor": "Intel",
"features": [
"pge",
"clflush"
],
"topology": {
"cores": 1,
"threads": 1,
"sockets": 4
}
},
"current_workload": 0,
"state": "up",
"status": "enabled",
"disk_available_least": 0,
"host_ip": "%(ip)s",
"free_disk_gb": 1028,
"free_ram_mb": 7680,
"hypervisor_hostname": "fake-mini",
"hypervisor_type": "fake",
"hypervisor_version": 1000,
"id": "%(hypervisor_id)s",
"local_gb": 1028,
"local_gb_used": 0,
"memory_mb": 8192,
"memory_mb_used": 512,
"running_vms": 0,
"service": {
"host": "%(host_name)s",
"id": "%(service_id)s",
"disabled_reason": null
},
"vcpus": 1,
"vcpus_used": 0
}
],
"hypervisors_links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/hypervisors/detail?limit=1&marker=%(hypervisor_id)s",
"rel": "next"
}
]
}

View File

@ -0,0 +1,53 @@
{
"hypervisors": [
{
"cpu_info": {
"arch": "x86_64",
"model": "Nehalem",
"vendor": "Intel",
"features": [
"pge",
"clflush"
],
"topology": {
"cores": 1,
"threads": 1,
"sockets": 4
}
},
"current_workload": 0,
"state": "up",
"status": "enabled",
"servers": [
{
"name": "test_server1",
"uuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
},
{
"name": "test_server2",
"uuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
}
],
"disk_available_least": 0,
"host_ip": "%(ip)s",
"free_disk_gb": 1028,
"free_ram_mb": 7680,
"hypervisor_hostname": "fake-mini",
"hypervisor_type": "fake",
"hypervisor_version": 1000,
"id": "%(hypervisor_id)s",
"local_gb": 1028,
"local_gb_used": 0,
"memory_mb": 8192,
"memory_mb_used": 512,
"running_vms": 0,
"service": {
"host": "%(host_name)s",
"id": "%(service_id)s",
"disabled_reason": null
},
"vcpus": 1,
"vcpus_used": 0
}
]
}

View File

@ -0,0 +1,16 @@
{
"hypervisors": [
{
"hypervisor_hostname": "fake-mini",
"id": "%(hypervisor_id)s",
"state": "up",
"status": "enabled"
}
],
"hypervisors_links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/hypervisors?limit=1&marker=%(hypervisor_id)s",
"rel": "next"
}
]
}

View File

@ -0,0 +1,10 @@
{
"hypervisors": [
{
"hypervisor_hostname": "fake-mini",
"id": "%(hypervisor_id)s",
"state": "up",
"status": "enabled"
}
]
}

View File

@ -0,0 +1,41 @@
{
"hypervisor": {
"cpu_info": {
"arch": "x86_64",
"model": "Nehalem",
"vendor": "Intel",
"features": [
"pge",
"clflush"
],
"topology": {
"cores": 1,
"threads": 1,
"sockets": 4
}
},
"current_workload": 0,
"disk_available_least": 0,
"state": "up",
"status": "enabled",
"host_ip": "%(ip)s",
"free_disk_gb": 1028,
"free_ram_mb": 7680,
"hypervisor_hostname": "fake-mini",
"hypervisor_type": "fake",
"hypervisor_version": 1000,
"id": "%(hypervisor_id)s",
"local_gb": 1028,
"local_gb_used": 0,
"memory_mb": 8192,
"memory_mb_used": 512,
"running_vms": 0,
"service": {
"host": "%(host_name)s",
"id": "%(service_id)s",
"disabled_reason": null
},
"vcpus": 1,
"vcpus_used": 0
}
}

View File

@ -0,0 +1,51 @@
{
"hypervisor": {
"cpu_info": {
"arch": "x86_64",
"model": "Nehalem",
"vendor": "Intel",
"features": [
"pge",
"clflush"
],
"topology": {
"cores": 1,
"threads": 1,
"sockets": 4
}
},
"current_workload": 0,
"disk_available_least": 0,
"state": "up",
"status": "enabled",
"servers": [
{
"name": "test_server1",
"uuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
},
{
"name": "test_server2",
"uuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
}
],
"host_ip": "%(ip)s",
"free_disk_gb": 1028,
"free_ram_mb": 7680,
"hypervisor_hostname": "fake-mini",
"hypervisor_type": "fake",
"hypervisor_version": 1000,
"id": "%(hypervisor_id)s",
"local_gb": 1028,
"local_gb_used": 0,
"memory_mb": 8192,
"memory_mb_used": 512,
"running_vms": 0,
"service": {
"host": "%(host_name)s",
"id": "%(service_id)s",
"disabled_reason": null
},
"vcpus": 1,
"vcpus_used": 0
}
}

View File

@ -0,0 +1,16 @@
{
"hypervisor_statistics": {
"count": 1,
"current_workload": 0,
"disk_available_least": 0,
"free_disk_gb": 1028,
"free_ram_mb": 7680,
"local_gb": 1028,
"local_gb_used": 0,
"memory_mb": 8192,
"memory_mb_used": 512,
"running_vms": 0,
"vcpus": 1,
"vcpus_used": 0
}
}

View File

@ -0,0 +1,9 @@
{
"hypervisor": {
"hypervisor_hostname": "fake-mini",
"id": "%(hypervisor_id)s",
"state": "up",
"status": "enabled",
"uptime": " 08:32:11 up 93 days, 18:25, 12 users, load average: 0.20, 0.12, 0.14"
}
}

View File

@ -0,0 +1,20 @@
{
"hypervisors": [
{
"hypervisor_hostname": "fake-mini",
"id": "%(hypervisor_id)s",
"state": "up",
"status": "enabled",
"servers": [
{
"name": "test_server1",
"uuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
},
{
"name": "test_server2",
"uuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
}
]
}
]
}

View File

@ -0,0 +1,10 @@
{
"hypervisors": [
{
"hypervisor_hostname": "fake-mini",
"id": "%(hypervisor_id)s",
"state": "up",
"status": "enabled"
}
]
}

View File

@ -169,3 +169,147 @@ class HypervisorsSampleJson233Tests(api_sample_base.ApiSampleTestBaseV21):
} }
response = self._do_get('os-hypervisors/detail?limit=1&marker=1') response = self._do_get('os-hypervisors/detail?limit=1&marker=1')
self._verify_response('hypervisors-detail-resp', subs, response, 200) self._verify_response('hypervisors-detail-resp', subs, response, 200)
class HypervisorsSampleJson253Tests(HypervisorsSampleJson228Tests):
microversion = '2.53'
scenarios = [('v2_53', {'api_major_version': 'v2.1'})]
def setUp(self):
super(HypervisorsSampleJson253Tests, self).setUp()
self.compute_node_1 = self.compute.service_ref.compute_node
def generalize_subs(self, subs, vanilla_regexes):
"""Give the test a chance to modify subs after the server response
was verified, and before the on-disk doc/api_samples file is checked.
"""
# When comparing the template to the sample we just care that the
# hypervisor id and service id are UUIDs.
subs['hypervisor_id'] = vanilla_regexes['uuid']
subs['service_id'] = vanilla_regexes['uuid']
return subs
def test_hypervisors_list(self):
# Start another compute service to get a 2nd compute for paging tests.
compute_node_2 = self.start_service(
'compute', host='host2').service_ref.compute_node
marker = self.compute_node_1.uuid
response = self._do_get('os-hypervisors?limit=1&marker=%s' % marker)
subs = {'hypervisor_id': compute_node_2.uuid}
self._verify_response('hypervisors-list-resp', subs, response, 200)
def test_hypervisors_detail(self):
# Start another compute service to get a 2nd compute for paging tests.
service_2 = self.start_service('compute', host='host2').service_ref
compute_node_2 = service_2.compute_node
marker = self.compute_node_1.uuid
subs = {
'hypervisor_id': compute_node_2.uuid,
'service_id': service_2.uuid
}
response = self._do_get('os-hypervisors/detail?limit=1&marker=%s' %
marker)
self._verify_response('hypervisors-detail-resp', subs, response, 200)
@mock.patch("nova.compute.api.HostAPI.instance_get_all_by_host")
def test_hypervisors_detail_with_servers(self, instance_get_all_by_host):
"""List hypervisors with details and with hosted servers."""
instances = [
{
"name": "test_server1",
"uuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
},
{
"name": "test_server2",
"uuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
}]
instance_get_all_by_host.return_value = instances
response = self._do_get('os-hypervisors/detail?with_servers=1')
subs = {
'hypervisor_id': self.compute_node_1.uuid,
'service_id': self.compute.service_ref.uuid,
}
self._verify_response('hypervisors-detail-with-servers-resp',
subs, response, 200)
def test_hypervisors_search(self):
"""The search route is deprecated in 2.53 and is now a query parameter
on the GET /os-hypervisors API.
"""
response = self._do_get(
'os-hypervisors?hypervisor_hostname_pattern=fake')
subs = {'hypervisor_id': self.compute_node_1.uuid}
self._verify_response('hypervisors-search-resp', subs, response, 200)
@mock.patch("nova.compute.api.HostAPI.instance_get_all_by_host")
def test_hypervisors_with_servers(self, instance_get_all_by_host):
"""The servers route is deprecated in 2.53 and is now a query parameter
on the GET /os-hypervisors API.
"""
instances = [
{
"name": "test_server1",
"uuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
},
{
"name": "test_server2",
"uuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
}]
instance_get_all_by_host.return_value = instances
response = self._do_get('os-hypervisors?with_servers=true')
subs = {'hypervisor_id': self.compute_node_1.uuid}
self._verify_response('hypervisors-with-servers-resp', subs,
response, 200)
def test_hypervisors_without_servers(self):
# This is the same as GET /os-hypervisors in 2.53 which is covered by
# test_hypervisors_list already.
pass
def test_hypervisors_uptime(self):
def fake_get_host_uptime(self, context, hyp):
return (" 08:32:11 up 93 days, 18:25, 12 users, load average:"
" 0.20, 0.12, 0.14")
self.stub_out('nova.compute.api.HostAPI.get_host_uptime',
fake_get_host_uptime)
hypervisor_id = self.compute_node_1.uuid
response = self._do_get('os-hypervisors/%s/uptime' % hypervisor_id)
subs = {
'hypervisor_id': hypervisor_id,
}
self._verify_response('hypervisors-uptime-resp', subs, response, 200)
def test_hypervisors_show(self):
hypervisor_id = self.compute_node_1.uuid
subs = {
'hypervisor_id': hypervisor_id,
'service_id': self.compute.service_ref.uuid,
}
response = self._do_get('os-hypervisors/%s' % hypervisor_id)
self._verify_response('hypervisors-show-resp', subs, response, 200)
@mock.patch("nova.compute.api.HostAPI.instance_get_all_by_host")
def test_hypervisors_show_with_servers(self, instance_get_all_by_host):
"""Tests getting details for a specific hypervisor and including the
hosted servers in the response.
"""
instances = [
{
"name": "test_server1",
"uuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
},
{
"name": "test_server2",
"uuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
}]
instance_get_all_by_host.return_value = instances
hypervisor_id = self.compute_node_1.uuid
subs = {
'hypervisor_id': hypervisor_id,
'service_id': self.compute.service_ref.uuid,
}
response = self._do_get('os-hypervisors/%s?with_servers=1' %
hypervisor_id)
self._verify_response('hypervisors-show-with-servers-resp', subs,
response, 200)

View File

@ -48,6 +48,8 @@ class ExtendedHypervisorsTestV21(test.NoDBTestCase):
del DETAIL_HYPERS_DICTS[1]['service_id'] del DETAIL_HYPERS_DICTS[1]['service_id']
del DETAIL_HYPERS_DICTS[0]['host'] del DETAIL_HYPERS_DICTS[0]['host']
del DETAIL_HYPERS_DICTS[1]['host'] del DETAIL_HYPERS_DICTS[1]['host']
del DETAIL_HYPERS_DICTS[0]['uuid']
del DETAIL_HYPERS_DICTS[1]['uuid']
DETAIL_HYPERS_DICTS[0].update({'state': 'up', DETAIL_HYPERS_DICTS[0].update({'state': 'up',
'status': 'enabled', 'status': 'enabled',
'service': dict(id=1, host='compute1', 'service': dict(id=1, host='compute1',

View File

@ -18,6 +18,7 @@ import copy
import mock import mock
import netaddr import netaddr
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
import six
from webob import exc from webob import exc
from nova.api.openstack.compute import hypervisors \ from nova.api.openstack.compute import hypervisors \
@ -39,6 +40,7 @@ CPU_INFO = """
TEST_HYPERS = [ TEST_HYPERS = [
dict(id=1, dict(id=1,
uuid=uuids.hyper1,
service_id=1, service_id=1,
host="compute1", host="compute1",
vcpus=4, vcpus=4,
@ -58,6 +60,7 @@ TEST_HYPERS = [
disk_available_least=100, disk_available_least=100,
host_ip=netaddr.IPAddress('1.1.1.1')), host_ip=netaddr.IPAddress('1.1.1.1')),
dict(id=2, dict(id=2,
uuid=uuids.hyper2,
service_id=2, service_id=2,
host="compute2", host="compute2",
vcpus=4, vcpus=4,
@ -80,6 +83,7 @@ TEST_HYPERS = [
TEST_SERVICES = [ TEST_SERVICES = [
objects.Service(id=1, objects.Service(id=1,
uuid=uuids.service1,
host="compute1", host="compute1",
binary="nova-compute", binary="nova-compute",
topic="compute_topic", topic="compute_topic",
@ -88,6 +92,7 @@ TEST_SERVICES = [
disabled_reason=None, disabled_reason=None,
availability_zone="nova"), availability_zone="nova"),
objects.Service(id=2, objects.Service(id=2,
uuid=uuids.service2,
host="compute2", host="compute2",
binary="nova-compute", binary="nova-compute",
topic="compute_topic", topic="compute_topic",
@ -110,12 +115,13 @@ TEST_SERVERS = [dict(name="inst1", uuid=uuids.instance_1, host="compute1"),
def fake_compute_node_get_all(context, limit=None, marker=None): def fake_compute_node_get_all(context, limit=None, marker=None):
if marker in ['99999']: if marker in ['99999', uuids.invalid_marker]:
raise exception.MarkerNotFound(marker) raise exception.MarkerNotFound(marker)
marker_found = True if marker is None else False marker_found = True if marker is None else False
output = [] output = []
for hyper in TEST_HYPERS_OBJ: for hyper in TEST_HYPERS_OBJ:
if not marker_found and marker == str(hyper.id): # Starting with the 2.53 microversion, the marker is a uuid.
if not marker_found and marker in (str(hyper.id), hyper.uuid):
marker_found = True marker_found = True
elif marker_found: elif marker_found:
if limit is None or len(output) < int(limit): if limit is None or len(output) < int(limit):
@ -129,7 +135,7 @@ def fake_compute_node_search_by_hypervisor(context, hypervisor_re):
def fake_compute_node_get(context, compute_id): def fake_compute_node_get(context, compute_id):
for hyper in TEST_HYPERS_OBJ: for hyper in TEST_HYPERS_OBJ:
if hyper.id == int(compute_id): if hyper.uuid == compute_id or hyper.id == int(compute_id):
return hyper return hyper
raise exception.ComputeHostNotFound(host=compute_id) raise exception.ComputeHostNotFound(host=compute_id)
@ -177,6 +183,9 @@ def fake_instance_get_all_by_host(context, host):
class HypervisorsTestV21(test.NoDBTestCase): class HypervisorsTestV21(test.NoDBTestCase):
api_version = '2.1' api_version = '2.1'
# Allow subclasses to override if the id value in the response is the
# compute node primary key integer id or the uuid.
expect_uuid_for_id = False
# copying the objects locally so the cells testcases can provide their own # copying the objects locally so the cells testcases can provide their own
TEST_HYPERS_OBJ = copy.deepcopy(TEST_HYPERS_OBJ) TEST_HYPERS_OBJ = copy.deepcopy(TEST_HYPERS_OBJ)
@ -188,6 +197,8 @@ class HypervisorsTestV21(test.NoDBTestCase):
del DETAIL_HYPERS_DICTS[1]['service_id'] del DETAIL_HYPERS_DICTS[1]['service_id']
del DETAIL_HYPERS_DICTS[0]['host'] del DETAIL_HYPERS_DICTS[0]['host']
del DETAIL_HYPERS_DICTS[1]['host'] del DETAIL_HYPERS_DICTS[1]['host']
del DETAIL_HYPERS_DICTS[0]['uuid']
del DETAIL_HYPERS_DICTS[1]['uuid']
DETAIL_HYPERS_DICTS[0].update({'state': 'up', DETAIL_HYPERS_DICTS[0].update({'state': 'up',
'status': 'enabled', 'status': 'enabled',
'service': dict(id=1, host='compute1', 'service': dict(id=1, host='compute1',
@ -213,6 +224,15 @@ class HypervisorsTestV21(test.NoDBTestCase):
self.controller.servicegroup_api.service_is_up = mock.MagicMock( self.controller.servicegroup_api.service_is_up = mock.MagicMock(
return_value=True) return_value=True)
def _get_hyper_id(self):
"""Helper function to get the proper hypervisor id for a request
:returns: The first hypervisor's uuid for microversions that expect a
uuid for the id, otherwise the hypervisor's id primary key
"""
return (self.TEST_HYPERS_OBJ[0].uuid if self.expect_uuid_for_id
else self.TEST_HYPERS_OBJ[0].id)
def setUp(self): def setUp(self):
super(HypervisorsTestV21, self).setUp() super(HypervisorsTestV21, self).setUp()
self._set_up_controller() self._set_up_controller()
@ -317,7 +337,8 @@ class HypervisorsTestV21(test.NoDBTestCase):
result = self.controller.index(req) result = self.controller.index(req)
self.assertEqual(1, len(result['hypervisors'])) self.assertEqual(1, len(result['hypervisors']))
expected = { expected = {
'id': compute_nodes[0].id, 'id': compute_nodes[0].uuid if self.expect_uuid_for_id
else compute_nodes[0].id,
'hypervisor_hostname': compute_nodes[0].hypervisor_hostname, 'hypervisor_hostname': compute_nodes[0].hypervisor_hostname,
'state': 'up', 'state': 'up',
'status': 'enabled', 'status': 'enabled',
@ -350,7 +371,8 @@ class HypervisorsTestV21(test.NoDBTestCase):
result = self.controller.index(req) result = self.controller.index(req)
self.assertEqual(1, len(result['hypervisors'])) self.assertEqual(1, len(result['hypervisors']))
expected = { expected = {
'id': compute_nodes[0].id, 'id': compute_nodes[0].uuid if self.expect_uuid_for_id
else compute_nodes[0].id,
'hypervisor_hostname': compute_nodes[0].hypervisor_hostname, 'hypervisor_hostname': compute_nodes[0].hypervisor_hostname,
'state': 'up', 'state': 'up',
'status': 'enabled', 'status': 'enabled',
@ -461,31 +483,25 @@ class HypervisorsTestV21(test.NoDBTestCase):
don't fail when listing hypervisors. don't fail when listing hypervisors.
""" """
# two computes, a matching service only exists for the first one
compute_nodes = objects.ComputeNodeList(objects=[
objects.ComputeNode(**TEST_HYPERS[0]),
objects.ComputeNode(**TEST_HYPERS[1])
])
def fake_service_get_by_compute_host(context, host):
return TEST_SERVICES[0]
@mock.patch.object(self.controller.host_api, 'compute_node_get', @mock.patch.object(self.controller.host_api, 'compute_node_get',
fake_service_get_by_compute_host) return_value=self.TEST_HYPERS_OBJ[0])
@mock.patch.object(self.controller.host_api, @mock.patch.object(self.controller.host_api,
'service_get_by_compute_host') 'service_get_by_compute_host')
def _test(self, mock_service): def _test(self, mock_service, mock_compute_node_get):
req = self._get_request(True) req = self._get_request(True)
mock_service.side_effect = exception.HostMappingNotFound( mock_service.side_effect = exception.HostMappingNotFound(
name='foo') name='foo')
hyper_id = self._get_hyper_id()
self.assertRaises(exc.HTTPNotFound, self.controller.show, self.assertRaises(exc.HTTPNotFound, self.controller.show,
req, compute_nodes[0].id) req, hyper_id)
self.assertTrue(mock_service.called) self.assertTrue(mock_service.called)
mock_compute_node_get.assert_called_once_with(mock.ANY, hyper_id)
_test(self) _test(self)
def test_show_noid(self): def test_show_noid(self):
req = self._get_request(True) req = self._get_request(True)
self.assertRaises(exc.HTTPNotFound, self.controller.show, req, '3') hyperid = uuids.hyper3 if self.expect_uuid_for_id else '3'
self.assertRaises(exc.HTTPNotFound, self.controller.show, req, hyperid)
def test_show_non_integer_id(self): def test_show_non_integer_id(self):
req = self._get_request(True) req = self._get_request(True)
@ -493,7 +509,8 @@ class HypervisorsTestV21(test.NoDBTestCase):
def test_show_withid(self): def test_show_withid(self):
req = self._get_request(True) req = self._get_request(True)
result = self.controller.show(req, self.TEST_HYPERS_OBJ[0].id) hyper_id = self._get_hyper_id()
result = self.controller.show(req, hyper_id)
self.assertEqual(dict(hypervisor=self.DETAIL_HYPERS_DICTS[0]), result) self.assertEqual(dict(hypervisor=self.DETAIL_HYPERS_DICTS[0]), result)
@ -501,20 +518,22 @@ class HypervisorsTestV21(test.NoDBTestCase):
req = self._get_request(False) req = self._get_request(False)
self.assertRaises(exception.PolicyNotAuthorized, self.assertRaises(exception.PolicyNotAuthorized,
self.controller.show, req, self.controller.show, req,
self.TEST_HYPERS_OBJ[0].id) self._get_hyper_id())
def test_uptime_noid(self): def test_uptime_noid(self):
req = self._get_request(True) req = self._get_request(True)
self.assertRaises(exc.HTTPNotFound, self.controller.uptime, req, '3') hyper_id = uuids.hyper3 if self.expect_uuid_for_id else '3'
self.assertRaises(exc.HTTPNotFound, self.controller.uptime, req,
hyper_id)
def test_uptime_notimplemented(self): def test_uptime_notimplemented(self):
with mock.patch.object(self.controller.host_api, 'get_host_uptime', with mock.patch.object(self.controller.host_api, 'get_host_uptime',
side_effect=exc.HTTPNotImplemented() side_effect=exc.HTTPNotImplemented()
) as mock_get_uptime: ) as mock_get_uptime:
req = self._get_request(True) req = self._get_request(True)
hyper_id = self._get_hyper_id()
self.assertRaises(exc.HTTPNotImplemented, self.assertRaises(exc.HTTPNotImplemented,
self.controller.uptime, req, self.controller.uptime, req, hyper_id)
self.TEST_HYPERS_OBJ[0].id)
self.assertEqual(1, mock_get_uptime.call_count) self.assertEqual(1, mock_get_uptime.call_count)
def test_uptime_implemented(self): def test_uptime_implemented(self):
@ -522,7 +541,8 @@ class HypervisorsTestV21(test.NoDBTestCase):
return_value="fake uptime" return_value="fake uptime"
) as mock_get_uptime: ) as mock_get_uptime:
req = self._get_request(True) req = self._get_request(True)
result = self.controller.uptime(req, self.TEST_HYPERS_OBJ[0].id) hyper_id = self._get_hyper_id()
result = self.controller.uptime(req, hyper_id)
expected_dict = copy.deepcopy(self.INDEX_HYPER_DICTS[0]) expected_dict = copy.deepcopy(self.INDEX_HYPER_DICTS[0])
expected_dict.update({'uptime': "fake uptime"}) expected_dict.update({'uptime': "fake uptime"})
@ -544,9 +564,9 @@ class HypervisorsTestV21(test.NoDBTestCase):
side_effect=exception.ComputeServiceUnavailable(host='dummy') side_effect=exception.ComputeServiceUnavailable(host='dummy')
) as mock_get_uptime: ) as mock_get_uptime:
req = self._get_request(True) req = self._get_request(True)
hyper_id = self._get_hyper_id()
self.assertRaises(exc.HTTPBadRequest, self.assertRaises(exc.HTTPBadRequest,
self.controller.uptime, req, self.controller.uptime, req, hyper_id)
self.TEST_HYPERS_OBJ[0].id)
mock_get_uptime.assert_called_once_with( mock_get_uptime.assert_called_once_with(
mock.ANY, self.TEST_HYPERS_OBJ[0].host) mock.ANY, self.TEST_HYPERS_OBJ[0].host)
@ -559,9 +579,9 @@ class HypervisorsTestV21(test.NoDBTestCase):
name='dummy')) name='dummy'))
def _test(mock_get, _, __): def _test(mock_get, _, __):
req = self._get_request(True) req = self._get_request(True)
hyper_id = self._get_hyper_id()
self.assertRaises(exc.HTTPNotFound, self.assertRaises(exc.HTTPNotFound,
self.controller.uptime, req, self.controller.uptime, req, hyper_id)
self.TEST_HYPERS_OBJ[0].id)
self.assertTrue(mock_get.called) self.assertTrue(mock_get.called)
_test() _test()
@ -571,9 +591,9 @@ class HypervisorsTestV21(test.NoDBTestCase):
side_effect=exception.HostMappingNotFound(name='dummy') side_effect=exception.HostMappingNotFound(name='dummy')
) as mock_get_uptime: ) as mock_get_uptime:
req = self._get_request(True) req = self._get_request(True)
hyper_id = self._get_hyper_id()
self.assertRaises(exc.HTTPNotFound, self.assertRaises(exc.HTTPNotFound,
self.controller.uptime, req, self.controller.uptime, req, hyper_id)
self.TEST_HYPERS_OBJ[0].id)
mock_get_uptime.assert_called_once_with( mock_get_uptime.assert_called_once_with(
mock.ANY, self.TEST_HYPERS_OBJ[0].host) mock.ANY, self.TEST_HYPERS_OBJ[0].host)
@ -872,3 +892,398 @@ class HypervisorsTestV233(HypervisorsTestV228):
'/v2/1234/os-hypervisors/detail?marker=99999') '/v2/1234/os-hypervisors/detail?marker=99999')
self.assertRaises(exc.HTTPBadRequest, self.assertRaises(exc.HTTPBadRequest,
self.controller.detail, req) self.controller.detail, req)
class HypervisorsTestV252(HypervisorsTestV233):
"""This is a boundary test to make sure 2.52 works like 2.33."""
api_version = '2.52'
class HypervisorsTestV253(HypervisorsTestV252):
api_version = hypervisors_v21.UUID_FOR_ID_MIN_VERSION
expect_uuid_for_id = True
# This is an expected response for index().
INDEX_HYPER_DICTS = [
dict(id=uuids.hyper1, hypervisor_hostname="hyper1",
state='up', status='enabled'),
dict(id=uuids.hyper2, hypervisor_hostname="hyper2",
state='up', status='enabled')]
def setUp(self):
super(HypervisorsTestV253, self).setUp()
# This is an expected response for detail().
for index, detail_hyper_dict in enumerate(self.DETAIL_HYPERS_DICTS):
detail_hyper_dict['id'] = TEST_HYPERS[index]['uuid']
detail_hyper_dict['service']['id'] = TEST_SERVICES[index].uuid
def test_servers(self):
"""Asserts that calling the servers route after 2.48 fails."""
self.assertRaises(exception.VersionNotFoundForAPIMethod,
self.controller.servers,
self._get_request(True), 'hyper')
def test_servers_with_no_server(self):
"""Tests GET /os-hypervisors?with_servers=1 when there are no
instances on the given host.
"""
with mock.patch.object(self.controller.host_api,
'instance_get_all_by_host',
return_value=[]) as mock_inst_get_all:
req = self._get_request(use_admin_context=True,
url='/os-hypervisors?with_servers=1')
result = self.controller.index(req)
self.assertEqual(dict(hypervisors=self.INDEX_HYPER_DICTS), result)
# instance_get_all_by_host is called for each hypervisor
self.assertEqual(2, mock_inst_get_all.call_count)
mock_inst_get_all.assert_has_calls((
mock.call(req.environ['nova.context'], TEST_HYPERS_OBJ[0].host),
mock.call(req.environ['nova.context'], TEST_HYPERS_OBJ[1].host)))
def test_servers_not_mapped(self):
"""Tests that instance_get_all_by_host fails with HostMappingNotFound.
"""
req = self._get_request(use_admin_context=True,
url='/os-hypervisors?with_servers=1')
with mock.patch.object(
self.controller.host_api, 'instance_get_all_by_host',
side_effect=exception.HostMappingNotFound(name='something')):
result = self.controller.index(req)
self.assertEqual(dict(hypervisors=[]), result)
def test_list_with_servers(self):
"""Tests GET /os-hypervisors?with_servers=True"""
instances = [
objects.InstanceList(objects=[objects.Instance(
id=1, uuid=uuids.hyper1_instance1)]),
objects.InstanceList(objects=[objects.Instance(
id=2, uuid=uuids.hyper2_instance1)])]
with mock.patch.object(self.controller.host_api,
'instance_get_all_by_host',
side_effect=instances) as mock_inst_get_all:
req = self._get_request(use_admin_context=True,
url='/os-hypervisors?with_servers=True')
result = self.controller.index(req)
index_with_servers = copy.deepcopy(self.INDEX_HYPER_DICTS)
index_with_servers[0]['servers'] = [
{'name': 'instance-00000001', 'uuid': uuids.hyper1_instance1}]
index_with_servers[1]['servers'] = [
{'name': 'instance-00000002', 'uuid': uuids.hyper2_instance1}]
self.assertEqual(dict(hypervisors=index_with_servers), result)
# instance_get_all_by_host is called for each hypervisor
self.assertEqual(2, mock_inst_get_all.call_count)
mock_inst_get_all.assert_has_calls((
mock.call(req.environ['nova.context'], TEST_HYPERS_OBJ[0].host),
mock.call(req.environ['nova.context'], TEST_HYPERS_OBJ[1].host)))
def test_list_with_servers_invalid_parameter(self):
"""Tests using an invalid with_servers query parameter."""
req = self._get_request(use_admin_context=True,
url='/os-hypervisors?with_servers=invalid')
self.assertRaises(
exception.ValidationError, self.controller.index, req)
def test_list_with_hostname_pattern_and_paging_parameters(self):
"""This is a negative test to validate that trying to list hypervisors
with a hostname pattern and paging parameters results in a 400 error.
"""
req = self._get_request(
use_admin_context=True,
url='/os-hypervisors?hypervisor_hostname_pattern=foo&'
'limit=1&marker=%s' % uuids.marker)
ex = self.assertRaises(exc.HTTPBadRequest, self.controller.index, req)
self.assertIn('Paging over hypervisors with the '
'hypervisor_hostname_pattern query parameter is not '
'supported.', six.text_type(ex))
def test_servers_with_non_integer_hypervisor_id(self):
"""This is a poorly named test, it's really checking the 404 case where
there is no match for the hostname pattern.
"""
req = self._get_request(
use_admin_context=True,
url='/os-hypervisors?with_servers=yes&'
'hypervisor_hostname_pattern=shenzhen')
with mock.patch.object(self.controller.host_api,
'compute_node_search_by_hypervisor',
return_value=objects.ComputeNodeList()) as s:
self.assertRaises(exc.HTTPNotFound, self.controller.index, req)
s.assert_called_once_with(req.environ['nova.context'], 'shenzhen')
def test_servers_non_admin(self):
"""There is no reason to test this for 2.53 since the
/os-hypervisors/servers route is deprecated.
"""
pass
def test_servers_non_id(self):
"""There is no reason to test this for 2.53 since the
/os-hypervisors/servers route is deprecated.
"""
pass
def test_search_old_route(self):
"""Asserts that calling the search route after 2.48 fails."""
self.assertRaises(exception.VersionNotFoundForAPIMethod,
self.controller.search,
self._get_request(True), 'hyper')
def test_search(self):
"""Test listing hypervisors with details and using the
hypervisor_hostname_pattern query string.
"""
req = self._get_request(
use_admin_context=True,
url='/os-hypervisors?hypervisor_hostname_pattern=shenzhen')
with mock.patch.object(self.controller.host_api,
'compute_node_search_by_hypervisor',
return_value=objects.ComputeNodeList(
objects=[TEST_HYPERS_OBJ[0]])) as s:
result = self.controller.detail(req)
s.assert_called_once_with(req.environ['nova.context'], 'shenzhen')
expected = {
'hypervisors': [
{'cpu_info': {'arch': 'x86_64',
'features': [],
'model': '',
'topology': {'cores': 1,
'sockets': 1,
'threads': 1},
'vendor': 'fake'},
'current_workload': 2,
'disk_available_least': 100,
'free_disk_gb': 125,
'free_ram_mb': 5120,
'host_ip': netaddr.IPAddress('1.1.1.1'),
'hypervisor_hostname': 'hyper1',
'hypervisor_type': 'xen',
'hypervisor_version': 3,
'id': TEST_HYPERS_OBJ[0].uuid,
'local_gb': 250,
'local_gb_used': 125,
'memory_mb': 10240,
'memory_mb_used': 5120,
'running_vms': 2,
'service': {'disabled_reason': None,
'host': 'compute1',
'id': TEST_SERVICES[0].uuid},
'state': 'up',
'status': 'enabled',
'vcpus': 4,
'vcpus_used': 2}
]
}
# There are no links when using the hypervisor_hostname_pattern
# query string since we can't page using a pattern matcher.
self.assertNotIn('hypervisors_links', result)
self.assertDictEqual(expected, result)
def test_search_invalid_hostname_pattern_parameter(self):
"""Tests passing an invalid hypervisor_hostname_pattern query
parameter.
"""
req = self._get_request(
use_admin_context=True,
url='/os-hypervisors?hypervisor_hostname_pattern=invalid~host')
self.assertRaises(
exception.ValidationError, self.controller.detail, req)
def test_search_non_exist(self):
"""This is a duplicate of test_servers_with_non_integer_hypervisor_id.
"""
pass
def test_search_non_admin(self):
"""There is no reason to test this for 2.53 since the
/os-hypervisors/search route is deprecated.
"""
pass
def test_search_unmapped(self):
"""This is already tested with test_index_compute_host_not_mapped."""
pass
def test_show_non_integer_id(self):
"""There is no reason to test this for 2.53 since 2.53 requires a
non-integer id (requires a uuid).
"""
pass
def test_show_integer_id(self):
"""Tests that we get a 400 if passed a hypervisor integer id to show().
"""
req = self._get_request(True)
ex = self.assertRaises(exc.HTTPBadRequest,
self.controller.show, req, '1')
self.assertIn('Invalid uuid 1', six.text_type(ex))
def test_show_with_servers_invalid_parameter(self):
"""Tests passing an invalid value for the with_servers query parameter
to the show() method to make sure the query parameter is validated.
"""
hyper_id = self._get_hyper_id()
req = self._get_request(
use_admin_context=True,
url='/os-hypervisors/%s?with_servers=invalid' % hyper_id)
ex = self.assertRaises(
exception.ValidationError, self.controller.show, req, hyper_id)
self.assertIn('with_servers', six.text_type(ex))
def test_show_with_servers_host_mapping_not_found(self):
"""Tests that a 404 is returned if instance_get_all_by_host raises
HostMappingNotFound.
"""
hyper_id = self._get_hyper_id()
req = self._get_request(
use_admin_context=True,
url='/os-hypervisors/%s?with_servers=true' % hyper_id)
with mock.patch.object(
self.controller.host_api, 'instance_get_all_by_host',
side_effect=exception.HostMappingNotFound(name=hyper_id)):
self.assertRaises(exc.HTTPNotFound, self.controller.show,
req, hyper_id)
def test_show_with_servers(self):
"""Tests the show() result when servers are included in the output."""
instances = objects.InstanceList(objects=[objects.Instance(
id=1, uuid=uuids.hyper1_instance1)])
hyper_id = self._get_hyper_id()
req = self._get_request(
use_admin_context=True,
url='/os-hypervisors/%s?with_servers=on' % hyper_id)
with mock.patch.object(self.controller.host_api,
'instance_get_all_by_host',
return_value=instances) as mock_inst_get_all:
result = self.controller.show(req, hyper_id)
show_with_servers = copy.deepcopy(self.DETAIL_HYPERS_DICTS[0])
show_with_servers['servers'] = [
{'name': 'instance-00000001', 'uuid': uuids.hyper1_instance1}]
self.assertDictEqual(dict(hypervisor=show_with_servers), result)
# instance_get_all_by_host is called
mock_inst_get_all.assert_called_once_with(
req.environ['nova.context'], TEST_HYPERS_OBJ[0].host)
def test_uptime_non_integer_id(self):
"""There is no reason to test this for 2.53 since 2.53 requires a
non-integer id (requires a uuid).
"""
pass
def test_uptime_integer_id(self):
"""Tests that we get a 400 if passed a hypervisor integer id to
uptime().
"""
req = self._get_request(True)
ex = self.assertRaises(exc.HTTPBadRequest,
self.controller.uptime, req, '1')
self.assertIn('Invalid uuid 1', six.text_type(ex))
def test_detail_pagination(self):
"""Tests details paging with uuid markers."""
req = self._get_request(
use_admin_context=True,
url='/os-hypervisors/detail?limit=1&marker=%s' %
TEST_HYPERS_OBJ[0].uuid)
result = self.controller.detail(req)
link = ('http://localhost/v2/hypervisors/detail?limit=1&marker=%s' %
TEST_HYPERS_OBJ[1].uuid)
expected = {
'hypervisors': [
{'cpu_info': {'arch': 'x86_64',
'features': [],
'model': '',
'topology': {'cores': 1,
'sockets': 1,
'threads': 1},
'vendor': 'fake'},
'current_workload': 2,
'disk_available_least': 100,
'free_disk_gb': 125,
'free_ram_mb': 5120,
'host_ip': netaddr.IPAddress('2.2.2.2'),
'hypervisor_hostname': 'hyper2',
'hypervisor_type': 'xen',
'hypervisor_version': 3,
'id': TEST_HYPERS_OBJ[1].uuid,
'local_gb': 250,
'local_gb_used': 125,
'memory_mb': 10240,
'memory_mb_used': 5120,
'running_vms': 2,
'service': {'disabled_reason': None,
'host': 'compute2',
'id': TEST_SERVICES[1].uuid},
'state': 'up',
'status': 'enabled',
'vcpus': 4,
'vcpus_used': 2}
],
'hypervisors_links': [{'href': link, 'rel': 'next'}]
}
self.assertEqual(expected, result)
def test_detail_pagination_with_invalid_marker(self):
"""Tests detail paging with an invalid marker (not found)."""
req = self._get_request(
use_admin_context=True,
url='/os-hypervisors/detail?marker=%s' % uuids.invalid_marker)
self.assertRaises(exc.HTTPBadRequest,
self.controller.detail, req)
def test_index_pagination(self):
"""Tests index paging with uuid markers."""
req = self._get_request(
use_admin_context=True,
url='/os-hypervisors?limit=1&marker=%s' %
TEST_HYPERS_OBJ[0].uuid)
result = self.controller.index(req)
link = ('http://localhost/v2/hypervisors?limit=1&marker=%s' %
TEST_HYPERS_OBJ[1].uuid)
expected = {
'hypervisors': [{
'hypervisor_hostname': 'hyper2',
'id': TEST_HYPERS_OBJ[1].uuid,
'state': 'up',
'status': 'enabled'
}],
'hypervisors_links': [{'href': link, 'rel': 'next'}]
}
self.assertEqual(expected, result)
def test_index_pagination_with_invalid_marker(self):
"""Tests index paging with an invalid marker (not found)."""
req = self._get_request(
use_admin_context=True,
url='/os-hypervisors?marker=%s' % uuids.invalid_marker)
self.assertRaises(exc.HTTPBadRequest,
self.controller.index, req)
def test_list_duplicate_query_parameters_validation(self):
"""Tests that the list query parameter schema enforces only a single
entry for any query parameter.
"""
params = {
'limit': 1,
'marker': uuids.marker,
'hypervisor_hostname_pattern': 'foo',
'with_servers': 'true'
}
for param, value in params.items():
req = self._get_request(
use_admin_context=True,
url='/os-hypervisors?%s=%s&%s=%s' %
(param, value, param, value))
self.assertRaises(exception.ValidationError,
self.controller.index, req)
def test_show_duplicate_query_parameters_validation(self):
"""Tests that the show query parameter schema enforces only a single
entry for any query parameter.
"""
req = self._get_request(
use_admin_context=True,
url='/os-hypervisors/%s?with_servers=1&with_servers=1' %
uuids.hyper1)
self.assertRaises(exception.ValidationError,
self.controller.show, req, uuids.hyper1)

View File

@ -147,7 +147,7 @@ class CellsUtilsTestCase(test.NoDBTestCase):
cell = cells_utils.PATH_CELL_SEP.join(path) cell = cells_utils.PATH_CELL_SEP.join(path)
item = 'host_5' item = 'host_5'
together = cells_utils.cell_with_item(cell, item) together = cells_utils.cell_with_item(cell, item)
self.assertEqual(cells_utils._CELL_ITEM_SEP.join([cell, item]), self.assertEqual(cells_utils.CELL_ITEM_SEP.join([cell, item]),
together) together)
# Test normal usage # Test normal usage

View File

@ -1,9 +1,9 @@
--- ---
features: features:
- | - |
Microversion 2.53 changes service IDs to UUIDs to ensure uniqueness across Microversion 2.53 changes service and hypervisor IDs to UUIDs to ensure
cells. Prior to this, ID collisions were possible in multi-cell uniqueness across cells. Prior to this, ID collisions were possible in
deployments. See the `REST API Version History`_ and multi-cell deployments. See the `REST API Version History`_ and
`Compute API reference`_ for details. `Compute API reference`_ for details.
.. _REST API Version History: https://docs.openstack.org/developer/nova/api_microversion_history.html .. _REST API Version History: https://docs.openstack.org/developer/nova/api_microversion_history.html