Use uuid for id in os-services API
This patch introduces a new microversion to identify services by uuid instead of id, to ensure uniqueness across cells. GET /os-services returns uuid in the id field, and uuid must be provided to delete a service with DELETE /os-services/{service_uuid}. The old PUT /os-services/* APIs are now capped and replaced with a new PUT /os-services/{service_uuid} which takes a uuid path parameter to uniquely identify the service to update. It also restricts updates to nova-compute services only, since disabling or forcing-down a non-compute service like nova-scheduler doesn't make sense as it doesn't do anything. The new update() method in this microversion also avoids trying to re-use the existing private action methods like _enable and _disable since those are predicated on looking up the service by host/binary, are confusing to follow for code flow, and just don't really make sense with a pure PUT resource update method. Part of blueprint service-hyper-uuid-in-api Co-Authored-By: Matt Riedemann <mriedem.os@gmail.com> Change-Id: I45494a4df7ee4454edb3ef8e7c5817d8c4e9e5ad
This commit is contained in:
parent
430ec6504b
commit
2f7bf29d47
@ -40,7 +40,8 @@ Response
|
|||||||
.. rest_parameters:: parameters.yaml
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
- services: services
|
- services: services
|
||||||
- id: service_id_body
|
- id: service_id_body_2_52
|
||||||
|
- id: service_id_body_2_53
|
||||||
- binary: binary
|
- binary: binary
|
||||||
- disabled_reason: disabled_reason_body
|
- disabled_reason: disabled_reason_body
|
||||||
- host: host_name_body
|
- host: host_name_body
|
||||||
@ -48,7 +49,7 @@ Response
|
|||||||
- status: service_status
|
- status: service_status
|
||||||
- updated_at: updated
|
- updated_at: updated
|
||||||
- zone: OS-EXT-AZ:availability_zone
|
- zone: OS-EXT-AZ:availability_zone
|
||||||
- forced_down: forced_down
|
- forced_down: forced_down_2_11
|
||||||
|
|
||||||
**Example List Compute Services**
|
**Example List Compute Services**
|
||||||
|
|
||||||
@ -64,6 +65,9 @@ Disables scheduling for a Compute service.
|
|||||||
|
|
||||||
Specify the service by its host name and binary name.
|
Specify the service by its host name and binary name.
|
||||||
|
|
||||||
|
.. note:: Starting with microversion 2.53 this API is superseded by
|
||||||
|
``PUT /os-services/{service_id}``.
|
||||||
|
|
||||||
Normal response codes: 200
|
Normal response codes: 200
|
||||||
|
|
||||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)
|
Error response codes: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)
|
||||||
@ -105,6 +109,9 @@ Logs information to the Compute service table about why a Compute service was di
|
|||||||
|
|
||||||
Specify the service by its host name and binary name.
|
Specify the service by its host name and binary name.
|
||||||
|
|
||||||
|
.. note:: Starting with microversion 2.53 this API is superseded by
|
||||||
|
``PUT /os-services/{service_id}``.
|
||||||
|
|
||||||
Normal response codes: 200
|
Normal response codes: 200
|
||||||
|
|
||||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)
|
Error response codes: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)
|
||||||
@ -148,6 +155,9 @@ Enables scheduling for a Compute service.
|
|||||||
|
|
||||||
Specify the service by its host name and binary name.
|
Specify the service by its host name and binary name.
|
||||||
|
|
||||||
|
.. note:: Starting with microversion 2.53 this API is superseded by
|
||||||
|
``PUT /os-services/{service_id}``.
|
||||||
|
|
||||||
Normal response codes: 200
|
Normal response codes: 200
|
||||||
|
|
||||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)
|
Error response codes: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)
|
||||||
@ -191,6 +201,9 @@ Action ``force-down`` available as of microversion 2.11.
|
|||||||
|
|
||||||
Specify the service by its host name and binary name.
|
Specify the service by its host name and binary name.
|
||||||
|
|
||||||
|
.. note:: Starting with microversion 2.53 this API is superseded by
|
||||||
|
``PUT /os-services/{service_id}``.
|
||||||
|
|
||||||
Normal response codes: 200
|
Normal response codes: 200
|
||||||
|
|
||||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)
|
Error response codes: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)
|
||||||
@ -202,7 +215,7 @@ Request
|
|||||||
|
|
||||||
- host: host_name_body
|
- host: host_name_body
|
||||||
- binary: binary
|
- binary: binary
|
||||||
- forced_down: forced_down
|
- forced_down: forced_down_2_11
|
||||||
|
|
||||||
**Example Update Forced Down**
|
**Example Update Forced Down**
|
||||||
|
|
||||||
@ -217,7 +230,7 @@ Response
|
|||||||
- service: service
|
- service: service
|
||||||
- binary: binary
|
- binary: binary
|
||||||
- host: host_name_body
|
- host: host_name_body
|
||||||
- forced_down: forced_down
|
- forced_down: forced_down_2_11
|
||||||
|
|
||||||
|
|
|
|
||||||
|
|
||||||
@ -226,6 +239,77 @@ Response
|
|||||||
.. literalinclude:: ../../doc/api_samples/os-services/v2.11/service-force-down-put-resp.json
|
.. literalinclude:: ../../doc/api_samples/os-services/v2.11/service-force-down-put-resp.json
|
||||||
:language: javascript
|
:language: javascript
|
||||||
|
|
||||||
|
Update Compute Service
|
||||||
|
======================
|
||||||
|
|
||||||
|
.. rest_method:: PUT /os-services/{service_id}
|
||||||
|
|
||||||
|
Update a compute service to enable or disable scheduling, including recording a
|
||||||
|
reason why a compute service was disabled from scheduling. Set or unset the
|
||||||
|
``forced_down`` flag for the service.
|
||||||
|
|
||||||
|
This API is available starting with microversion 2.53.
|
||||||
|
|
||||||
|
Normal response codes: 200
|
||||||
|
|
||||||
|
Error response codes: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)
|
||||||
|
|
||||||
|
Request
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
|
- service_id: service_id_path_2_53_no_version
|
||||||
|
- status: service_status_2_53_in
|
||||||
|
- disabled_reason: disabled_reason_2_53_in
|
||||||
|
- forced_down: forced_down_2_53_in
|
||||||
|
|
||||||
|
**Example Disable Scheduling For A Compute Service (v2.53)**
|
||||||
|
|
||||||
|
.. literalinclude:: ../../doc/api_samples/os-services/v2.53/service-disable-log-put-req.json
|
||||||
|
:language: javascript
|
||||||
|
|
||||||
|
**Example Enable Scheduling For A Compute Service (v2.53)**
|
||||||
|
|
||||||
|
.. literalinclude:: ../../doc/api_samples/os-services/v2.53/service-enable-put-req.json
|
||||||
|
:language: javascript
|
||||||
|
|
||||||
|
**Example Update Forced Down (v2.53)**
|
||||||
|
|
||||||
|
.. literalinclude:: ../../doc/api_samples/os-services/v2.53/service-force-down-put-req.json
|
||||||
|
:language: javascript
|
||||||
|
|
||||||
|
Response
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
|
- service: service
|
||||||
|
- id: service_id_body_2_53_no_version
|
||||||
|
- binary: binary
|
||||||
|
- disabled_reason: disabled_reason_body
|
||||||
|
- host: host_name_body
|
||||||
|
- state: service_state
|
||||||
|
- status: service_status
|
||||||
|
- updated_at: updated
|
||||||
|
- zone: OS-EXT-AZ:availability_zone
|
||||||
|
- forced_down: forced_down_2_53_out
|
||||||
|
|
||||||
|
**Example Disable Scheduling For A Compute Service (v2.53)**
|
||||||
|
|
||||||
|
.. literalinclude:: ../../doc/api_samples/os-services/v2.53/service-disable-log-put-resp.json
|
||||||
|
:language: javascript
|
||||||
|
|
||||||
|
**Example Enable Scheduling For A Compute Service (v2.53)**
|
||||||
|
|
||||||
|
.. literalinclude:: ../../doc/api_samples/os-services/v2.53/service-enable-put-resp.json
|
||||||
|
:language: javascript
|
||||||
|
|
||||||
|
**Example Update Forced Down (v2.53)**
|
||||||
|
|
||||||
|
.. literalinclude:: ../../doc/api_samples/os-services/v2.53/service-force-down-put-resp.json
|
||||||
|
:language: javascript
|
||||||
|
|
||||||
Delete Compute Service
|
Delete Compute Service
|
||||||
======================
|
======================
|
||||||
|
|
||||||
@ -243,7 +327,8 @@ Request
|
|||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
- service_id: service_id_path
|
- service_id: service_id_path_2_52
|
||||||
|
- service_id: service_id_path_2_53
|
||||||
|
|
||||||
Response
|
Response
|
||||||
--------
|
--------
|
||||||
|
@ -125,6 +125,8 @@ console_token:
|
|||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
# Used in the request path for PUT /os-services/disable-log-reason before
|
||||||
|
# microversion 2.53.
|
||||||
disabled_reason:
|
disabled_reason:
|
||||||
description: |
|
description: |
|
||||||
The reason for disabling a service.
|
The reason for disabling a service.
|
||||||
@ -283,12 +285,31 @@ server_id_path:
|
|||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
service_id_path:
|
service_id_path_2_52:
|
||||||
description: |
|
description: |
|
||||||
The id of the service.
|
The id of the service.
|
||||||
|
|
||||||
|
.. note:: This may not uniquely identify a service in a multi-cell
|
||||||
|
deployment.
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
type: integer
|
type: integer
|
||||||
|
max_version: 2.52
|
||||||
|
service_id_path_2_53:
|
||||||
|
description: |
|
||||||
|
The id of the service as a uuid. This uniquely identifies the service in a
|
||||||
|
multi-cell deployment.
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
min_version: 2.53
|
||||||
|
service_id_path_2_53_no_version:
|
||||||
|
description: |
|
||||||
|
The id of the service as a uuid. This uniquely identifies the service in a
|
||||||
|
multi-cell deployment.
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
snapshot_id_path:
|
snapshot_id_path:
|
||||||
description: |
|
description: |
|
||||||
The UUID of the snapshot.
|
The UUID of the snapshot.
|
||||||
@ -1898,6 +1919,15 @@ device_tag_nic_attachment:
|
|||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
min_version: 2.49
|
min_version: 2.49
|
||||||
|
# Optional input parameter in the body for PUT /os-services/{service_id} added
|
||||||
|
# in microversion 2.53.
|
||||||
|
disabled_reason_2_53_in:
|
||||||
|
description: |
|
||||||
|
The reason for disabling a service. The minimum length is 1 and the
|
||||||
|
maximum length is 255. This may only be requested with ``status=disabled``.
|
||||||
|
in: body
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
disabled_reason_body:
|
disabled_reason_body:
|
||||||
description: |
|
description: |
|
||||||
The reason for disabling a service.
|
The reason for disabling a service.
|
||||||
@ -2633,7 +2663,9 @@ force_snapshot:
|
|||||||
in: body
|
in: body
|
||||||
required: false
|
required: false
|
||||||
type: boolean
|
type: boolean
|
||||||
forced_down:
|
# This is both the request and response parameter for
|
||||||
|
# PUT /os-services/force-down which was added in 2.11.
|
||||||
|
forced_down_2_11:
|
||||||
description: |
|
description: |
|
||||||
Whether or not this service was forced down manually by an
|
Whether or not this service was forced down manually by an
|
||||||
administrator. This value is useful to know that some 3rd party has
|
administrator. This value is useful to know that some 3rd party has
|
||||||
@ -2642,6 +2674,26 @@ forced_down:
|
|||||||
required: true
|
required: true
|
||||||
type: boolean
|
type: boolean
|
||||||
min_version: 2.11
|
min_version: 2.11
|
||||||
|
# This is the optional request input parameter for
|
||||||
|
# PUT /os-services/{service_id} added in 2.53.
|
||||||
|
forced_down_2_53_in:
|
||||||
|
description: |
|
||||||
|
Whether or not this service was forced down manually by an
|
||||||
|
administrator. This value is useful to know that some 3rd party has
|
||||||
|
verified the service should be marked down.
|
||||||
|
in: body
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
# This is the response output parameter for
|
||||||
|
# PUT /os-services/{service_id} added in 2.53.
|
||||||
|
forced_down_2_53_out:
|
||||||
|
description: |
|
||||||
|
Whether or not this service was forced down manually by an
|
||||||
|
administrator. This value is useful to know that some 3rd party has
|
||||||
|
verified the service should be marked down.
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
type: boolean
|
||||||
forceDelete:
|
forceDelete:
|
||||||
description: |
|
description: |
|
||||||
The action.
|
The action.
|
||||||
@ -5064,6 +5116,26 @@ service_id_body:
|
|||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: integer
|
type: integer
|
||||||
|
service_id_body_2_52:
|
||||||
|
description: |
|
||||||
|
The id of the service.
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
max_version: 2.52
|
||||||
|
service_id_body_2_53:
|
||||||
|
description: |
|
||||||
|
The id of the service as a uuid.
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
min_version: 2.53
|
||||||
|
service_id_body_2_53_no_version:
|
||||||
|
description: |
|
||||||
|
The id of the service as a uuid.
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
service_state:
|
service_state:
|
||||||
description: |
|
description: |
|
||||||
The state of the service. One of ``up`` or ``down``.
|
The state of the service. One of ``up`` or ``down``.
|
||||||
@ -5076,6 +5148,14 @@ service_status:
|
|||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
# This is an optional input parameter to PUT /os-services/{service_id} added
|
||||||
|
# in microversion 2.53.
|
||||||
|
service_status_2_53_in:
|
||||||
|
description: |
|
||||||
|
The status of the service. One of ``enabled`` or ``disabled``.
|
||||||
|
in: body
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
services:
|
services:
|
||||||
description: |
|
description: |
|
||||||
A list of service objects.
|
A list of service objects.
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"status": "disabled",
|
||||||
|
"disabled_reason": "maintenance"
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"service": {
|
||||||
|
"id": "e81d66a4-ddd3-4aba-8a84-171d1cb4d339",
|
||||||
|
"binary": "nova-compute",
|
||||||
|
"disabled_reason": "maintenance",
|
||||||
|
"host": "host1",
|
||||||
|
"state": "up",
|
||||||
|
"status": "disabled",
|
||||||
|
"updated_at": "2012-10-29T13:42:05.000000",
|
||||||
|
"forced_down": false,
|
||||||
|
"zone": "nova"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"status": "disabled"
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"service": {
|
||||||
|
"id": "e81d66a4-ddd3-4aba-8a84-171d1cb4d339",
|
||||||
|
"binary": "nova-compute",
|
||||||
|
"disabled_reason": null,
|
||||||
|
"host": "host1",
|
||||||
|
"state": "up",
|
||||||
|
"status": "disabled",
|
||||||
|
"updated_at": "2012-10-29T13:42:05.000000",
|
||||||
|
"forced_down": false,
|
||||||
|
"zone": "nova"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"status": "enabled"
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"service": {
|
||||||
|
"id": "e81d66a4-ddd3-4aba-8a84-171d1cb4d339",
|
||||||
|
"binary": "nova-compute",
|
||||||
|
"disabled_reason": null,
|
||||||
|
"host": "host1",
|
||||||
|
"state": "up",
|
||||||
|
"status": "enabled",
|
||||||
|
"updated_at": "2012-10-29T13:42:05.000000",
|
||||||
|
"forced_down": false,
|
||||||
|
"zone": "nova"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"forced_down": true
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"service": {
|
||||||
|
"id": "e81d66a4-ddd3-4aba-8a84-171d1cb4d339",
|
||||||
|
"binary": "nova-compute",
|
||||||
|
"disabled_reason": "test2",
|
||||||
|
"host": "host1",
|
||||||
|
"state": "down",
|
||||||
|
"status": "disabled",
|
||||||
|
"updated_at": "2012-10-29T13:42:05.000000",
|
||||||
|
"forced_down": true,
|
||||||
|
"zone": "nova"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"id": "c4726392-27de-4ff9-b2e0-5aa1d08a520f",
|
||||||
|
"binary": "nova-scheduler",
|
||||||
|
"disabled_reason": "test1",
|
||||||
|
"host": "host1",
|
||||||
|
"state": "up",
|
||||||
|
"status": "disabled",
|
||||||
|
"updated_at": "2012-10-29T13:42:02.000000",
|
||||||
|
"forced_down": false,
|
||||||
|
"zone": "internal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e81d66a4-ddd3-4aba-8a84-171d1cb4d339",
|
||||||
|
"binary": "nova-compute",
|
||||||
|
"disabled_reason": "test2",
|
||||||
|
"host": "host1",
|
||||||
|
"state": "up",
|
||||||
|
"status": "disabled",
|
||||||
|
"updated_at": "2012-10-29T13:42:05.000000",
|
||||||
|
"forced_down": false,
|
||||||
|
"zone": "nova"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bbd684ff-d3f6-492e-a30a-a12a2d2db0e0",
|
||||||
|
"binary": "nova-scheduler",
|
||||||
|
"disabled_reason": null,
|
||||||
|
"host": "host2",
|
||||||
|
"state": "down",
|
||||||
|
"status": "enabled",
|
||||||
|
"updated_at": "2012-09-19T06:55:34.000000",
|
||||||
|
"forced_down": false,
|
||||||
|
"zone": "internal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "13aa304e-5340-45a7-a7fb-b6d6e914d272",
|
||||||
|
"binary": "nova-compute",
|
||||||
|
"disabled_reason": "test4",
|
||||||
|
"host": "host2",
|
||||||
|
"state": "down",
|
||||||
|
"status": "disabled",
|
||||||
|
"updated_at": "2012-09-18T08:03:38.000000",
|
||||||
|
"forced_down": false,
|
||||||
|
"zone": "nova"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -19,7 +19,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "CURRENT",
|
"status": "CURRENT",
|
||||||
"version": "2.52",
|
"version": "2.53",
|
||||||
"min_version": "2.1",
|
"min_version": "2.1",
|
||||||
"updated": "2013-07-23T11:33:21Z"
|
"updated": "2013-07-23T11:33:21Z"
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "CURRENT",
|
"status": "CURRENT",
|
||||||
"version": "2.52",
|
"version": "2.53",
|
||||||
"min_version": "2.1",
|
"min_version": "2.1",
|
||||||
"updated": "2013-07-23T11:33:21Z"
|
"updated": "2013-07-23T11:33:21Z"
|
||||||
}
|
}
|
||||||
|
@ -124,6 +124,9 @@ 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
|
||||||
|
a uuid in the id field, and takes a uuid in
|
||||||
|
DELETE /services/{service_uuid}.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# The minimum and maximum versions of the API supported
|
# The minimum and maximum versions of the API supported
|
||||||
@ -132,7 +135,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
|||||||
# Note(cyeoh): This only applies for the v2.1 API once microversions
|
# Note(cyeoh): This only applies for the v2.1 API once microversions
|
||||||
# support is fully merged. It does not affect the V2 API.
|
# support is fully merged. It does not affect the V2 API.
|
||||||
_MIN_API_VERSION = "2.1"
|
_MIN_API_VERSION = "2.1"
|
||||||
_MAX_API_VERSION = "2.52"
|
_MAX_API_VERSION = "2.53"
|
||||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||||
|
|
||||||
# Almost all proxy APIs which related to network, images and baremetal
|
# Almost all proxy APIs which related to network, images and baremetal
|
||||||
|
@ -627,3 +627,33 @@ user documentation.
|
|||||||
|
|
||||||
Adds support for applying tags when creating a server. The tag schema is
|
Adds support for applying tags when creating a server. The tag schema is
|
||||||
the same as in the `2.26`_ microversion.
|
the same as in the `2.26`_ microversion.
|
||||||
|
|
||||||
|
2.53
|
||||||
|
----
|
||||||
|
|
||||||
|
**os-services**
|
||||||
|
|
||||||
|
Services are now identified by uuid instead of database id to ensure
|
||||||
|
uniqueness across cells. This microversion brings the following changes:
|
||||||
|
|
||||||
|
* ``GET /os-services`` returns a uuid in the ``id`` field of the response
|
||||||
|
* ``DELETE /os-services/{service_uuid}`` requires a service uuid in the path
|
||||||
|
* The following APIs have been superseded by
|
||||||
|
``PUT /os-services/{service_uuid}/``:
|
||||||
|
|
||||||
|
* ``PUT /os-services/disable``
|
||||||
|
* ``PUT /os-services/disable-log-reason``
|
||||||
|
* ``PUT /os-services/enable``
|
||||||
|
* ``PUT /os-services/force-down``
|
||||||
|
|
||||||
|
``PUT /os-services/{service_uuid}`` takes the following fields in the body:
|
||||||
|
|
||||||
|
* ``status`` - can be either "enabled" or "disabled" to enable or disable
|
||||||
|
the given service
|
||||||
|
* ``disabled_reason`` - specify with status="disabled" to log a reason for
|
||||||
|
why the service is disabled
|
||||||
|
* ``forced_down`` - boolean indicating if the service was forced down by
|
||||||
|
an external service
|
||||||
|
|
||||||
|
* ``PUT /os-services/{service_uuid}`` will now return a full service resource
|
||||||
|
representation like in a ``GET`` response
|
||||||
|
@ -44,3 +44,24 @@ service_update_v211 = {
|
|||||||
'required': ['host', 'binary'],
|
'required': ['host', 'binary'],
|
||||||
'additionalProperties': False
|
'additionalProperties': False
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# The 2.53 body is for updating a service's status and/or forced_down fields.
|
||||||
|
# There are no required attributes since the service is identified using a
|
||||||
|
# unique service_id on the request path, and status and/or forced_down can
|
||||||
|
# be specified in the body. If status=='disabled', then 'disabled_reason' is
|
||||||
|
# also checked in the body but is not required. Requesting status='enabled' and
|
||||||
|
# including a 'disabled_reason' results in a 400, but this is checked in code.
|
||||||
|
service_update_v2_53 = {
|
||||||
|
'type': 'object',
|
||||||
|
'properties': {
|
||||||
|
'status': {
|
||||||
|
'type': 'string',
|
||||||
|
'enum': ['enabled', 'disabled'],
|
||||||
|
},
|
||||||
|
'disabled_reason': {
|
||||||
|
'type': 'string', 'minLength': 1, 'maxLength': 255,
|
||||||
|
},
|
||||||
|
'forced_down': parameter_types.boolean
|
||||||
|
},
|
||||||
|
'additionalProperties': False
|
||||||
|
}
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from oslo_utils import strutils
|
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
|
||||||
@ -20,6 +21,7 @@ from nova.api.openstack.compute.schemas import services
|
|||||||
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.api import validation
|
||||||
|
from nova import availability_zones
|
||||||
from nova import compute
|
from nova import compute
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova.i18n import _
|
from nova.i18n import _
|
||||||
@ -27,6 +29,8 @@ from nova.policies import services as services_policies
|
|||||||
from nova import servicegroup
|
from nova import servicegroup
|
||||||
from nova import utils
|
from nova import utils
|
||||||
|
|
||||||
|
UUID_FOR_ID_MIN_VERSION = '2.53'
|
||||||
|
|
||||||
|
|
||||||
class ServiceController(wsgi.Controller):
|
class ServiceController(wsgi.Controller):
|
||||||
|
|
||||||
@ -67,7 +71,7 @@ class ServiceController(wsgi.Controller):
|
|||||||
|
|
||||||
return _services
|
return _services
|
||||||
|
|
||||||
def _get_service_detail(self, svc, additional_fields):
|
def _get_service_detail(self, svc, additional_fields, req):
|
||||||
alive = self.servicegroup_api.service_is_up(svc)
|
alive = self.servicegroup_api.service_is_up(svc)
|
||||||
state = (alive and "up") or "down"
|
state = (alive and "up") or "down"
|
||||||
active = 'enabled'
|
active = 'enabled'
|
||||||
@ -75,9 +79,22 @@ class ServiceController(wsgi.Controller):
|
|||||||
active = 'disabled'
|
active = 'disabled'
|
||||||
updated_time = self.servicegroup_api.get_updated_time(svc)
|
updated_time = self.servicegroup_api.get_updated_time(svc)
|
||||||
|
|
||||||
|
uuid_for_id = api_version_request.is_supported(
|
||||||
|
req, min_version=UUID_FOR_ID_MIN_VERSION)
|
||||||
|
|
||||||
|
if 'availability_zone' not in svc:
|
||||||
|
# The service wasn't loaded with the AZ so we need to do it here.
|
||||||
|
# Yes this looks weird, but set_availability_zones makes a copy of
|
||||||
|
# the list passed in and mutates the objects within it, so we have
|
||||||
|
# to pull it back out from the resulting copied list.
|
||||||
|
svc.availability_zone = (
|
||||||
|
availability_zones.set_availability_zones(
|
||||||
|
req.environ['nova.context'],
|
||||||
|
[svc])[0]['availability_zone'])
|
||||||
|
|
||||||
service_detail = {'binary': svc['binary'],
|
service_detail = {'binary': svc['binary'],
|
||||||
'host': svc['host'],
|
'host': svc['host'],
|
||||||
'id': svc['id'],
|
'id': svc['uuid' if uuid_for_id else 'id'],
|
||||||
'zone': svc['availability_zone'],
|
'zone': svc['availability_zone'],
|
||||||
'status': active,
|
'status': active,
|
||||||
'state': state,
|
'state': state,
|
||||||
@ -91,7 +108,7 @@ class ServiceController(wsgi.Controller):
|
|||||||
|
|
||||||
def _get_services_list(self, req, additional_fields=()):
|
def _get_services_list(self, req, additional_fields=()):
|
||||||
_services = self._get_services(req)
|
_services = self._get_services(req)
|
||||||
return [self._get_service_detail(svc, additional_fields)
|
return [self._get_service_detail(svc, additional_fields, req)
|
||||||
for svc in _services]
|
for svc in _services]
|
||||||
|
|
||||||
def _enable(self, body, context):
|
def _enable(self, body, context):
|
||||||
@ -179,10 +196,17 @@ class ServiceController(wsgi.Controller):
|
|||||||
context = req.environ['nova.context']
|
context = req.environ['nova.context']
|
||||||
context.can(services_policies.BASE_POLICY_NAME)
|
context.can(services_policies.BASE_POLICY_NAME)
|
||||||
|
|
||||||
|
if api_version_request.is_supported(
|
||||||
|
req, min_version=UUID_FOR_ID_MIN_VERSION):
|
||||||
|
if not uuidutils.is_uuid_like(id):
|
||||||
|
msg = _('Invalid uuid %s') % id
|
||||||
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
utils.validate_integer(id, 'id')
|
utils.validate_integer(id, 'id')
|
||||||
except exception.InvalidInput as exc:
|
except exception.InvalidInput as exc:
|
||||||
raise webob.exc.HTTPBadRequest(explanation=exc.format_message())
|
raise webob.exc.HTTPBadRequest(
|
||||||
|
explanation=exc.format_message())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
service = self.host_api.service_get_by_id(context, id)
|
service = self.host_api.service_get_by_id(context, id)
|
||||||
@ -215,11 +239,18 @@ class ServiceController(wsgi.Controller):
|
|||||||
|
|
||||||
return {'services': _services}
|
return {'services': _services}
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version('2.1', '2.52')
|
||||||
@extensions.expected_errors((400, 404))
|
@extensions.expected_errors((400, 404))
|
||||||
@validation.schema(services.service_update, '2.0', '2.10')
|
@validation.schema(services.service_update, '2.0', '2.10')
|
||||||
@validation.schema(services.service_update_v211, '2.11')
|
@validation.schema(services.service_update_v211, '2.11', '2.52')
|
||||||
def update(self, req, id, body):
|
def update(self, req, id, body):
|
||||||
"""Perform service update"""
|
"""Perform service update
|
||||||
|
|
||||||
|
Before microversion 2.53, the body contains a host and binary value
|
||||||
|
to identify the service on which to perform the action. There is no
|
||||||
|
service ID passed on the path, just the action, for example
|
||||||
|
PUT /os-services/disable.
|
||||||
|
"""
|
||||||
if api_version_request.is_supported(req, min_version='2.11'):
|
if api_version_request.is_supported(req, min_version='2.11'):
|
||||||
actions = self.actions.copy()
|
actions = self.actions.copy()
|
||||||
actions["force-down"] = self._forced_down
|
actions["force-down"] = self._forced_down
|
||||||
@ -227,3 +258,89 @@ class ServiceController(wsgi.Controller):
|
|||||||
actions = self.actions
|
actions = self.actions
|
||||||
|
|
||||||
return self._perform_action(req, id, body, actions)
|
return self._perform_action(req, id, body, actions)
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version(UUID_FOR_ID_MIN_VERSION) # noqa F811
|
||||||
|
@extensions.expected_errors((400, 404))
|
||||||
|
@validation.schema(services.service_update_v2_53, UUID_FOR_ID_MIN_VERSION)
|
||||||
|
def update(self, req, id, body):
|
||||||
|
"""Perform service update
|
||||||
|
|
||||||
|
Starting with microversion 2.53, the service uuid is passed in on the
|
||||||
|
path of the request to uniquely identify the service record on which to
|
||||||
|
perform a given update, which is defined in the body of the request.
|
||||||
|
"""
|
||||||
|
service_id = id
|
||||||
|
# Validate that the service ID is a UUID.
|
||||||
|
if not uuidutils.is_uuid_like(service_id):
|
||||||
|
msg = _('Invalid uuid %s') % service_id
|
||||||
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||||
|
|
||||||
|
# Validate the request context against the policy.
|
||||||
|
context = req.environ['nova.context']
|
||||||
|
context.can(services_policies.BASE_POLICY_NAME)
|
||||||
|
|
||||||
|
# Get the service by uuid.
|
||||||
|
try:
|
||||||
|
service = self.host_api.service_get_by_id(context, service_id)
|
||||||
|
# At this point the context is targeted to the cell that the
|
||||||
|
# service was found in so we don't need to do any explicit cell
|
||||||
|
# targeting below.
|
||||||
|
except exception.ServiceNotFound as e:
|
||||||
|
raise webob.exc.HTTPNotFound(explanation=e.format_message())
|
||||||
|
|
||||||
|
# Return 400 if service.binary is not nova-compute.
|
||||||
|
# Before the earlier PUT handlers were made cells-aware, you could
|
||||||
|
# technically disable a nova-scheduler service, although that doesn't
|
||||||
|
# really do anything within Nova and is just confusing. Now trying to
|
||||||
|
# do that will fail as a nova-scheduler service won't have a host
|
||||||
|
# mapping so you'll get a 404. In this new microversion, we close that
|
||||||
|
# old gap and make sure you can only enable/disable and set forced_down
|
||||||
|
# on nova-compute services since those are the only ones that make
|
||||||
|
# sense to update for those operations.
|
||||||
|
if service.binary != 'nova-compute':
|
||||||
|
msg = (_('Updating a %(binary)s service is not supported. Only '
|
||||||
|
'nova-compute services can be updated.') %
|
||||||
|
{'binary': service.binary})
|
||||||
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||||
|
|
||||||
|
# Now determine the update to perform based on the body. We are
|
||||||
|
# intentionally not using _perform_action or the other old-style
|
||||||
|
# action functions.
|
||||||
|
if 'status' in body:
|
||||||
|
# This is a status update for either enabled or disabled.
|
||||||
|
if body['status'] == 'enabled':
|
||||||
|
|
||||||
|
# Fail if 'disabled_reason' was requested when enabling the
|
||||||
|
# service since those two combined don't make sense.
|
||||||
|
if body.get('disabled_reason'):
|
||||||
|
msg = _("Specifying 'disabled_reason' with status "
|
||||||
|
"'enabled' is invalid.")
|
||||||
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||||
|
|
||||||
|
service.disabled = False
|
||||||
|
service.disabled_reason = None
|
||||||
|
elif body['status'] == 'disabled':
|
||||||
|
service.disabled = True
|
||||||
|
# The disabled reason is optional.
|
||||||
|
service.disabled_reason = body.get('disabled_reason')
|
||||||
|
|
||||||
|
# This is intentionally not an elif, i.e. it's in addition to the
|
||||||
|
# status update.
|
||||||
|
if 'forced_down' in body:
|
||||||
|
service.forced_down = strutils.bool_from_string(
|
||||||
|
body['forced_down'], strict=True)
|
||||||
|
|
||||||
|
# Check to see if anything was actually updated since the schema does
|
||||||
|
# not define any required fields.
|
||||||
|
if not service.obj_what_changed():
|
||||||
|
msg = _("No updates were requested. Fields 'status' or "
|
||||||
|
"'forced_down' should be specified.")
|
||||||
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||||
|
|
||||||
|
# Now save our updates to the service record in the database.
|
||||||
|
service.save()
|
||||||
|
|
||||||
|
# Return the full service record details.
|
||||||
|
additional_fields = ['forced_down']
|
||||||
|
return {'service': self._get_service_detail(
|
||||||
|
service, additional_fields, req)}
|
||||||
|
@ -69,6 +69,20 @@ class _CellProxy(object):
|
|||||||
|
|
||||||
return getattr(self._obj, key)
|
return getattr(self._obj, key)
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
"""Pass-through "in" check to the wrapped object.
|
||||||
|
|
||||||
|
This is needed to proxy any types of checks in the calling code
|
||||||
|
like::
|
||||||
|
|
||||||
|
if 'availability_zone' in service:
|
||||||
|
...
|
||||||
|
|
||||||
|
:param key: They key to look for in the wrapped object.
|
||||||
|
:returns: True if key is in the wrapped object, False otherwise.
|
||||||
|
"""
|
||||||
|
return key in self._obj
|
||||||
|
|
||||||
def obj_to_primitive(self):
|
def obj_to_primitive(self):
|
||||||
obj_p = self._obj.obj_to_primitive()
|
obj_p = self._obj.obj_to_primitive()
|
||||||
obj_p['cell_proxy.class_name'] = self.__class__.__name__
|
obj_p['cell_proxy.class_name'] = self.__class__.__name__
|
||||||
|
@ -50,6 +50,11 @@ services_policies = [
|
|||||||
'method': 'PUT',
|
'method': 'PUT',
|
||||||
'path': '/os-services/force-down'
|
'path': '/os-services/force-down'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
# Added in microversion 2.53.
|
||||||
|
'method': 'PUT',
|
||||||
|
'path': '/os-services/{service_id}'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'method': 'DELETE',
|
'method': 'DELETE',
|
||||||
'path': '/os-services/{service_id}'
|
'path': '/os-services/{service_id}'
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"status": "disabled",
|
||||||
|
"disabled_reason": "%(disabled_reason)s"
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"service": {
|
||||||
|
"id": "e81d66a4-ddd3-4aba-8a84-171d1cb4d339",
|
||||||
|
"binary": "nova-compute",
|
||||||
|
"disabled_reason": "maintenance",
|
||||||
|
"host": "host1",
|
||||||
|
"state": "up",
|
||||||
|
"status": "disabled",
|
||||||
|
"updated_at": "%(strtime)s",
|
||||||
|
"forced_down": false,
|
||||||
|
"zone": "nova"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"status": "disabled"
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"service": {
|
||||||
|
"id": "e81d66a4-ddd3-4aba-8a84-171d1cb4d339",
|
||||||
|
"binary": "nova-compute",
|
||||||
|
"disabled_reason": null,
|
||||||
|
"host": "host1",
|
||||||
|
"state": "up",
|
||||||
|
"status": "disabled",
|
||||||
|
"updated_at": "%(strtime)s",
|
||||||
|
"forced_down": false,
|
||||||
|
"zone": "nova"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"status": "enabled"
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"service": {
|
||||||
|
"id": "e81d66a4-ddd3-4aba-8a84-171d1cb4d339",
|
||||||
|
"binary": "nova-compute",
|
||||||
|
"disabled_reason": null,
|
||||||
|
"host": "host1",
|
||||||
|
"state": "up",
|
||||||
|
"status": "enabled",
|
||||||
|
"updated_at": "2012-10-29T13:42:05.000000",
|
||||||
|
"forced_down": false,
|
||||||
|
"zone": "nova"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"forced_down": %(forced_down)s
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"service": {
|
||||||
|
"id": "e81d66a4-ddd3-4aba-8a84-171d1cb4d339",
|
||||||
|
"binary": "nova-compute",
|
||||||
|
"disabled_reason": "test2",
|
||||||
|
"host": "host1",
|
||||||
|
"state": "down",
|
||||||
|
"status": "disabled",
|
||||||
|
"updated_at": "%(strtime)s",
|
||||||
|
"forced_down": true,
|
||||||
|
"zone": "nova"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"binary": "nova-scheduler",
|
||||||
|
"disabled_reason": "test1",
|
||||||
|
"forced_down": false,
|
||||||
|
"host": "host1",
|
||||||
|
"id": "%(id)s",
|
||||||
|
"state": "up",
|
||||||
|
"status": "disabled",
|
||||||
|
"updated_at": "%(strtime)s",
|
||||||
|
"zone": "internal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"binary": "nova-compute",
|
||||||
|
"disabled_reason": "test2",
|
||||||
|
"forced_down": false,
|
||||||
|
"host": "host1",
|
||||||
|
"id": "%(id)s",
|
||||||
|
"state": "up",
|
||||||
|
"status": "disabled",
|
||||||
|
"updated_at": "%(strtime)s",
|
||||||
|
"zone": "nova"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"binary": "nova-scheduler",
|
||||||
|
"disabled_reason": null,
|
||||||
|
"forced_down": false,
|
||||||
|
"host": "host2",
|
||||||
|
"id": "%(id)s",
|
||||||
|
"state": "down",
|
||||||
|
"status": "enabled",
|
||||||
|
"updated_at": "%(strtime)s",
|
||||||
|
"zone": "internal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"binary": "nova-compute",
|
||||||
|
"disabled_reason": "test4",
|
||||||
|
"forced_down": false,
|
||||||
|
"host": "host2",
|
||||||
|
"id": "%(id)s",
|
||||||
|
"state": "down",
|
||||||
|
"status": "disabled",
|
||||||
|
"updated_at": "%(strtime)s",
|
||||||
|
"zone": "nova"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
from oslo_utils import fixture as utils_fixture
|
from oslo_utils import fixture as utils_fixture
|
||||||
|
|
||||||
|
from nova import exception
|
||||||
from nova.tests.functional.api_sample_tests import api_sample_base
|
from nova.tests.functional.api_sample_tests import api_sample_base
|
||||||
from nova.tests.unit.api.openstack.compute import test_services
|
from nova.tests.unit.api.openstack.compute import test_services
|
||||||
|
|
||||||
@ -104,3 +105,57 @@ class ServicesV211JsonTest(ServicesJsonTest):
|
|||||||
'service-force-down-put-req', subs)
|
'service-force-down-put-req', subs)
|
||||||
self._verify_response('service-force-down-put-resp', subs,
|
self._verify_response('service-force-down-put-resp', subs,
|
||||||
response, 200)
|
response, 200)
|
||||||
|
|
||||||
|
|
||||||
|
class ServicesV253JsonTest(ServicesV211JsonTest):
|
||||||
|
microversion = '2.53'
|
||||||
|
scenarios = [('v2_53', {'api_major_version': 'v2.1'})]
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(ServicesV253JsonTest, self).setUp()
|
||||||
|
|
||||||
|
def db_service_get_by_uuid(ctxt, service_uuid):
|
||||||
|
for svc in test_services.fake_services_list:
|
||||||
|
if svc['uuid'] == service_uuid:
|
||||||
|
return svc
|
||||||
|
raise exception.ServiceNotFound(service_id=service_uuid)
|
||||||
|
self.stub_out('nova.db.service_get_by_uuid', db_service_get_by_uuid)
|
||||||
|
|
||||||
|
def test_service_enable(self):
|
||||||
|
"""Enable an existing service."""
|
||||||
|
response = self._do_put(
|
||||||
|
'os-services/%s' % test_services.FAKE_UUID_COMPUTE_HOST1,
|
||||||
|
'service-enable-put-req', subs={})
|
||||||
|
self._verify_response('service-enable-put-resp', {}, response, 200)
|
||||||
|
|
||||||
|
def test_service_disable(self):
|
||||||
|
"""Disable an existing service."""
|
||||||
|
response = self._do_put(
|
||||||
|
'os-services/%s' % test_services.FAKE_UUID_COMPUTE_HOST1,
|
||||||
|
'service-disable-put-req', subs={})
|
||||||
|
self._verify_response('service-disable-put-resp', {}, response, 200)
|
||||||
|
|
||||||
|
def test_service_disable_log_reason(self):
|
||||||
|
"""Disable an existing service and log the reason."""
|
||||||
|
subs = {'disabled_reason': 'maintenance'}
|
||||||
|
response = self._do_put(
|
||||||
|
'os-services/%s' % test_services.FAKE_UUID_COMPUTE_HOST1,
|
||||||
|
'service-disable-log-put-req', subs)
|
||||||
|
self._verify_response('service-disable-log-put-resp',
|
||||||
|
subs, response, 200)
|
||||||
|
|
||||||
|
def test_service_delete(self):
|
||||||
|
"""Delete an existing service."""
|
||||||
|
response = self._do_delete(
|
||||||
|
'os-services/%s' % test_services.FAKE_UUID_COMPUTE_HOST1)
|
||||||
|
self.assertEqual(204, response.status_code)
|
||||||
|
self.assertEqual("", response.text)
|
||||||
|
|
||||||
|
def test_force_down(self):
|
||||||
|
"""Set forced_down flag"""
|
||||||
|
subs = {'forced_down': 'true'}
|
||||||
|
response = self._do_put(
|
||||||
|
'os-services/%s' % test_services.FAKE_UUID_COMPUTE_HOST1,
|
||||||
|
'service-force-down-put-req', subs)
|
||||||
|
self._verify_response('service-force-down-put-resp', subs,
|
||||||
|
response, 200)
|
||||||
|
@ -54,6 +54,13 @@ class NotificationSampleTestBase(test.TestCase,
|
|||||||
|
|
||||||
REQUIRES_LOCKING = True
|
REQUIRES_LOCKING = True
|
||||||
|
|
||||||
|
# NOTE(gibi): Notification payloads always reflect the data needed
|
||||||
|
# for every supported API microversion so we can safe to use the latest
|
||||||
|
# API version in the tests. This helps the test to use the new API
|
||||||
|
# features too. This can be overridden by subclasses that need to cap
|
||||||
|
# at a specific microversion for older APIs.
|
||||||
|
MAX_MICROVERSION = 'latest'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(NotificationSampleTestBase, self).setUp()
|
super(NotificationSampleTestBase, self).setUp()
|
||||||
|
|
||||||
@ -63,11 +70,7 @@ class NotificationSampleTestBase(test.TestCase,
|
|||||||
self.api = api_fixture.api
|
self.api = api_fixture.api
|
||||||
self.admin_api = api_fixture.admin_api
|
self.admin_api = api_fixture.admin_api
|
||||||
|
|
||||||
# NOTE(gibi): Notification payloads always reflect the data needed
|
max_version = self.MAX_MICROVERSION
|
||||||
# for every supported API microversion so we can safe to use the latest
|
|
||||||
# API version in the tests. This helps the test to use the new API
|
|
||||||
# features too.
|
|
||||||
max_version = 'latest'
|
|
||||||
self.api.microversion = max_version
|
self.api.microversion = max_version
|
||||||
self.admin_api.microversion = max_version
|
self.admin_api.microversion = max_version
|
||||||
|
|
||||||
|
@ -14,17 +14,22 @@
|
|||||||
|
|
||||||
from oslo_utils import fixture as utils_fixture
|
from oslo_utils import fixture as utils_fixture
|
||||||
|
|
||||||
|
from nova import exception
|
||||||
from nova.tests import fixtures
|
from nova.tests import fixtures
|
||||||
from nova.tests.functional.notification_sample_tests \
|
from nova.tests.functional.notification_sample_tests \
|
||||||
import notification_sample_base
|
import notification_sample_base
|
||||||
from nova.tests.unit.api.openstack.compute import test_services
|
from nova.tests.unit.api.openstack.compute import test_services
|
||||||
|
|
||||||
|
|
||||||
class TestServiceUpdateNotificationSample(
|
class TestServiceUpdateNotificationSamplev2_52(
|
||||||
notification_sample_base.NotificationSampleTestBase):
|
notification_sample_base.NotificationSampleTestBase):
|
||||||
|
|
||||||
|
# These tests have to be capped at 2.52 since the PUT format changes in
|
||||||
|
# the 2.53 microversion.
|
||||||
|
MAX_MICROVERSION = '2.52'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestServiceUpdateNotificationSample, self).setUp()
|
super(TestServiceUpdateNotificationSamplev2_52, self).setUp()
|
||||||
self.stub_out("nova.db.service_get_by_host_and_binary",
|
self.stub_out("nova.db.service_get_by_host_and_binary",
|
||||||
test_services.fake_service_get_by_host_binary)
|
test_services.fake_service_get_by_host_binary)
|
||||||
self.stub_out("nova.db.service_update",
|
self.stub_out("nova.db.service_update",
|
||||||
@ -69,3 +74,51 @@ class TestServiceUpdateNotificationSample(
|
|||||||
'disabled': True,
|
'disabled': True,
|
||||||
'disabled_reason': 'test2',
|
'disabled_reason': 'test2',
|
||||||
'uuid': self.service_uuid})
|
'uuid': self.service_uuid})
|
||||||
|
|
||||||
|
|
||||||
|
class TestServiceUpdateNotificationSampleLatest(
|
||||||
|
TestServiceUpdateNotificationSamplev2_52):
|
||||||
|
"""Tests the PUT /os-services/{service_id} API notifications."""
|
||||||
|
|
||||||
|
MAX_MICROVERSION = 'latest'
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestServiceUpdateNotificationSampleLatest, self).setUp()
|
||||||
|
|
||||||
|
def db_service_get_by_uuid(ctxt, service_uuid):
|
||||||
|
for svc in test_services.fake_services_list:
|
||||||
|
if svc['uuid'] == service_uuid:
|
||||||
|
return svc
|
||||||
|
raise exception.ServiceNotFound(service_id=service_uuid)
|
||||||
|
self.stub_out('nova.db.service_get_by_uuid', db_service_get_by_uuid)
|
||||||
|
|
||||||
|
def test_service_enable(self):
|
||||||
|
body = {'status': 'enabled'}
|
||||||
|
self.admin_api.api_put('os-services/%s' % self.service_uuid, body)
|
||||||
|
self._verify_notification('service-update',
|
||||||
|
replacements={'uuid': self.service_uuid})
|
||||||
|
|
||||||
|
def test_service_disabled(self):
|
||||||
|
body = {'status': 'disabled'}
|
||||||
|
self.admin_api.api_put('os-services/%s' % self.service_uuid, body)
|
||||||
|
self._verify_notification('service-update',
|
||||||
|
replacements={'disabled': True,
|
||||||
|
'uuid': self.service_uuid})
|
||||||
|
|
||||||
|
def test_service_disabled_log_reason(self):
|
||||||
|
body = {'status': 'disabled',
|
||||||
|
'disabled_reason': 'test2'}
|
||||||
|
self.admin_api.api_put('os-services/%s' % self.service_uuid, body)
|
||||||
|
self._verify_notification('service-update',
|
||||||
|
replacements={'disabled': True,
|
||||||
|
'disabled_reason': 'test2',
|
||||||
|
'uuid': self.service_uuid})
|
||||||
|
|
||||||
|
def test_service_force_down(self):
|
||||||
|
body = {'forced_down': True}
|
||||||
|
self.admin_api.api_put('os-services/%s' % self.service_uuid, body)
|
||||||
|
self._verify_notification('service-update',
|
||||||
|
replacements={'forced_down': True,
|
||||||
|
'disabled': True,
|
||||||
|
'disabled_reason': 'test2',
|
||||||
|
'uuid': self.service_uuid})
|
||||||
|
@ -19,6 +19,7 @@ import datetime
|
|||||||
import iso8601
|
import iso8601
|
||||||
import mock
|
import mock
|
||||||
from oslo_utils import fixture as utils_fixture
|
from oslo_utils import fixture as utils_fixture
|
||||||
|
import six
|
||||||
import webob.exc
|
import webob.exc
|
||||||
|
|
||||||
from nova.api.openstack import api_version_request as api_version
|
from nova.api.openstack import api_version_request as api_version
|
||||||
@ -36,6 +37,10 @@ from nova import test
|
|||||||
from nova.tests import fixtures
|
from nova.tests import fixtures
|
||||||
from nova.tests.unit.api.openstack import fakes
|
from nova.tests.unit.api.openstack import fakes
|
||||||
from nova.tests.unit.objects import test_service
|
from nova.tests.unit.objects import test_service
|
||||||
|
from nova.tests import uuidsentinel
|
||||||
|
|
||||||
|
# This is tied into the os-services API samples functional tests.
|
||||||
|
FAKE_UUID_COMPUTE_HOST1 = 'e81d66a4-ddd3-4aba-8a84-171d1cb4d339'
|
||||||
|
|
||||||
|
|
||||||
fake_services_list = [
|
fake_services_list = [
|
||||||
@ -43,6 +48,7 @@ fake_services_list = [
|
|||||||
binary='nova-scheduler',
|
binary='nova-scheduler',
|
||||||
host='host1',
|
host='host1',
|
||||||
id=1,
|
id=1,
|
||||||
|
uuid=uuidsentinel.svc1,
|
||||||
disabled=True,
|
disabled=True,
|
||||||
topic='scheduler',
|
topic='scheduler',
|
||||||
updated_at=datetime.datetime(2012, 10, 29, 13, 42, 2),
|
updated_at=datetime.datetime(2012, 10, 29, 13, 42, 2),
|
||||||
@ -54,6 +60,7 @@ fake_services_list = [
|
|||||||
binary='nova-compute',
|
binary='nova-compute',
|
||||||
host='host1',
|
host='host1',
|
||||||
id=2,
|
id=2,
|
||||||
|
uuid=FAKE_UUID_COMPUTE_HOST1,
|
||||||
disabled=True,
|
disabled=True,
|
||||||
topic='compute',
|
topic='compute',
|
||||||
updated_at=datetime.datetime(2012, 10, 29, 13, 42, 5),
|
updated_at=datetime.datetime(2012, 10, 29, 13, 42, 5),
|
||||||
@ -65,6 +72,7 @@ fake_services_list = [
|
|||||||
binary='nova-scheduler',
|
binary='nova-scheduler',
|
||||||
host='host2',
|
host='host2',
|
||||||
id=3,
|
id=3,
|
||||||
|
uuid=uuidsentinel.svc3,
|
||||||
disabled=False,
|
disabled=False,
|
||||||
topic='scheduler',
|
topic='scheduler',
|
||||||
updated_at=datetime.datetime(2012, 9, 19, 6, 55, 34),
|
updated_at=datetime.datetime(2012, 9, 19, 6, 55, 34),
|
||||||
@ -76,6 +84,7 @@ fake_services_list = [
|
|||||||
binary='nova-compute',
|
binary='nova-compute',
|
||||||
host='host2',
|
host='host2',
|
||||||
id=4,
|
id=4,
|
||||||
|
uuid=uuidsentinel.svc4,
|
||||||
disabled=True,
|
disabled=True,
|
||||||
topic='compute',
|
topic='compute',
|
||||||
updated_at=datetime.datetime(2012, 9, 18, 8, 3, 38),
|
updated_at=datetime.datetime(2012, 9, 18, 8, 3, 38),
|
||||||
@ -88,6 +97,7 @@ fake_services_list = [
|
|||||||
binary='nova-osapi_compute',
|
binary='nova-osapi_compute',
|
||||||
host='host2',
|
host='host2',
|
||||||
id=5,
|
id=5,
|
||||||
|
uuid=uuidsentinel.svc5,
|
||||||
disabled=False,
|
disabled=False,
|
||||||
topic=None,
|
topic=None,
|
||||||
updated_at=None,
|
updated_at=None,
|
||||||
@ -99,6 +109,7 @@ fake_services_list = [
|
|||||||
binary='nova-metadata',
|
binary='nova-metadata',
|
||||||
host='host2',
|
host='host2',
|
||||||
id=6,
|
id=6,
|
||||||
|
uuid=uuidsentinel.svc6,
|
||||||
disabled=False,
|
disabled=False,
|
||||||
topic=None,
|
topic=None,
|
||||||
updated_at=None,
|
updated_at=None,
|
||||||
@ -927,6 +938,235 @@ class ServicesTestV211(ServicesTestV21):
|
|||||||
self.controller.update, req, 'force-down', body=req_body)
|
self.controller.update, req, 'force-down', body=req_body)
|
||||||
|
|
||||||
|
|
||||||
|
class ServicesTestV252(ServicesTestV211):
|
||||||
|
"""This is a boundary test to ensure that 2.52 behaves the same as 2.11."""
|
||||||
|
wsgi_api_version = '2.52'
|
||||||
|
|
||||||
|
|
||||||
|
class FakeServiceGroupAPI(object):
|
||||||
|
def service_is_up(self, *args, **kwargs):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_updated_time(self, *args, **kwargs):
|
||||||
|
return mock.sentinel.updated_time
|
||||||
|
|
||||||
|
|
||||||
|
class ServicesTestV253(test.TestCase):
|
||||||
|
"""Tests for the 2.53 microversion in the os-services API."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(ServicesTestV253, self).setUp()
|
||||||
|
self.controller = services_v21.ServiceController()
|
||||||
|
self.controller.servicegroup_api = FakeServiceGroupAPI()
|
||||||
|
self.req = fakes.HTTPRequest.blank(
|
||||||
|
'', version=services_v21.UUID_FOR_ID_MIN_VERSION)
|
||||||
|
|
||||||
|
def assert_services_equal(self, s1, s2):
|
||||||
|
for k in ('binary', 'host'):
|
||||||
|
self.assertEqual(s1[k], s2[k])
|
||||||
|
|
||||||
|
def test_list_has_uuid_in_id_field(self):
|
||||||
|
"""Tests that a GET response includes an id field but the value is
|
||||||
|
the service uuid rather than the id integer primary key.
|
||||||
|
"""
|
||||||
|
service_uuids = [s['uuid'] for s in fake_services_list]
|
||||||
|
with mock.patch.object(
|
||||||
|
self.controller.host_api, 'service_get_all',
|
||||||
|
side_effect=fake_service_get_all(fake_services_list)):
|
||||||
|
resp = self.controller.index(self.req)
|
||||||
|
|
||||||
|
for service in resp['services']:
|
||||||
|
# Make sure a uuid field wasn't returned.
|
||||||
|
self.assertNotIn('uuid', service)
|
||||||
|
# Make sure the id field is one of our known uuids.
|
||||||
|
self.assertIn(service['id'], service_uuids)
|
||||||
|
# Make sure this service was in our known list of fake services.
|
||||||
|
expected = next(iter(filter(
|
||||||
|
lambda s: s['uuid'] == service['id'],
|
||||||
|
fake_services_list)))
|
||||||
|
self.assert_services_equal(expected, service)
|
||||||
|
|
||||||
|
def test_delete_takes_uuid_for_id(self):
|
||||||
|
"""Tests that a DELETE request correctly deletes a service when a valid
|
||||||
|
service uuid is provided for an existing service.
|
||||||
|
"""
|
||||||
|
service = self.start_service(
|
||||||
|
'compute', 'fake-compute-host').service_ref
|
||||||
|
with mock.patch.object(self.controller.host_api,
|
||||||
|
'service_delete') as service_delete:
|
||||||
|
self.controller.delete(self.req, service.uuid)
|
||||||
|
service_delete.assert_called_once_with(
|
||||||
|
self.req.environ['nova.context'], service.uuid)
|
||||||
|
self.assertEqual(204, self.controller.delete.wsgi_code)
|
||||||
|
|
||||||
|
def test_delete_uuid_not_found(self):
|
||||||
|
"""Tests that we get a 404 response when attempting to delete a service
|
||||||
|
that is not found by the given uuid.
|
||||||
|
"""
|
||||||
|
self.assertRaises(webob.exc.HTTPNotFound,
|
||||||
|
self.controller.delete, self.req, uuidsentinel.svc2)
|
||||||
|
|
||||||
|
def test_delete_invalid_uuid(self):
|
||||||
|
"""Tests that the service uuid is validated in a DELETE request."""
|
||||||
|
ex = self.assertRaises(webob.exc.HTTPBadRequest,
|
||||||
|
self.controller.delete, self.req, 1234)
|
||||||
|
self.assertIn('Invalid uuid', six.text_type(ex))
|
||||||
|
|
||||||
|
def test_update_invalid_service_uuid(self):
|
||||||
|
"""Tests that the service uuid is validated in a PUT request."""
|
||||||
|
ex = self.assertRaises(webob.exc.HTTPBadRequest,
|
||||||
|
self.controller.update, self.req, 1234, body={})
|
||||||
|
self.assertIn('Invalid uuid', six.text_type(ex))
|
||||||
|
|
||||||
|
def test_update_policy_failed(self):
|
||||||
|
"""Tests that policy is checked with microversion 2.53."""
|
||||||
|
rule_name = "os_compute_api:os-services"
|
||||||
|
self.policy.set_rules({rule_name: "project_id:non_fake"})
|
||||||
|
exc = self.assertRaises(
|
||||||
|
exception.PolicyNotAuthorized,
|
||||||
|
self.controller.update, self.req, uuidsentinel.service_uuid,
|
||||||
|
body={})
|
||||||
|
self.assertEqual(
|
||||||
|
"Policy doesn't allow %s to be performed." % rule_name,
|
||||||
|
exc.format_message())
|
||||||
|
|
||||||
|
def test_update_service_not_found(self):
|
||||||
|
"""Tests that we get a 404 response if the service is not found by
|
||||||
|
the given uuid when handling a PUT request.
|
||||||
|
"""
|
||||||
|
self.assertRaises(webob.exc.HTTPNotFound, self.controller.update,
|
||||||
|
self.req, uuidsentinel.service_uuid, body={})
|
||||||
|
|
||||||
|
def test_update_invalid_status(self):
|
||||||
|
"""Tests that jsonschema validates the status field in the request body
|
||||||
|
and fails if it's not "enabled" or "disabled".
|
||||||
|
"""
|
||||||
|
service = self.start_service(
|
||||||
|
'compute', 'fake-compute-host').service_ref
|
||||||
|
self.assertRaises(
|
||||||
|
exception.ValidationError, self.controller.update, self.req,
|
||||||
|
service.uuid, body={'status': 'invalid'})
|
||||||
|
|
||||||
|
def test_update_disabled_no_reason_then_enable(self):
|
||||||
|
"""Tests disabling a service with no reason given. Then enables it
|
||||||
|
to see the change in the response body.
|
||||||
|
"""
|
||||||
|
service = self.start_service(
|
||||||
|
'compute', 'fake-compute-host').service_ref
|
||||||
|
resp = self.controller.update(self.req, service.uuid,
|
||||||
|
body={'status': 'disabled'})
|
||||||
|
expected_resp = {
|
||||||
|
'service': {
|
||||||
|
'status': 'disabled',
|
||||||
|
'state': 'up',
|
||||||
|
'binary': 'nova-compute',
|
||||||
|
'host': 'fake-compute-host',
|
||||||
|
'zone': 'nova', # Comes from CONF.default_availability_zone
|
||||||
|
'updated_at': mock.sentinel.updated_time,
|
||||||
|
'disabled_reason': None,
|
||||||
|
'id': service.uuid,
|
||||||
|
'forced_down': False
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.assertDictEqual(expected_resp, resp)
|
||||||
|
|
||||||
|
# Now enable the service to see the response change.
|
||||||
|
req = fakes.HTTPRequest.blank(
|
||||||
|
'', version=services_v21.UUID_FOR_ID_MIN_VERSION)
|
||||||
|
resp = self.controller.update(req, service.uuid,
|
||||||
|
body={'status': 'enabled'})
|
||||||
|
expected_resp['service']['status'] = 'enabled'
|
||||||
|
self.assertDictEqual(expected_resp, resp)
|
||||||
|
|
||||||
|
def test_update_enable_with_disabled_reason_fails(self):
|
||||||
|
"""Validates that requesting to both enable a service and set the
|
||||||
|
disabled_reason results in a 400 BadRequest error.
|
||||||
|
"""
|
||||||
|
service = self.start_service(
|
||||||
|
'compute', 'fake-compute-host').service_ref
|
||||||
|
ex = self.assertRaises(webob.exc.HTTPBadRequest,
|
||||||
|
self.controller.update, self.req, service.uuid,
|
||||||
|
body={'status': 'enabled',
|
||||||
|
'disabled_reason': 'invalid'})
|
||||||
|
self.assertIn("Specifying 'disabled_reason' with status 'enabled' "
|
||||||
|
"is invalid.", six.text_type(ex))
|
||||||
|
|
||||||
|
def test_update_disabled_reason_and_forced_down(self):
|
||||||
|
"""Tests disabling a service with a reason and forcing it down is
|
||||||
|
reflected back in the response.
|
||||||
|
"""
|
||||||
|
service = self.start_service(
|
||||||
|
'compute', 'fake-compute-host').service_ref
|
||||||
|
resp = self.controller.update(self.req, service.uuid,
|
||||||
|
body={'status': 'disabled',
|
||||||
|
'disabled_reason': 'maintenance',
|
||||||
|
# Also tests bool_from_string usage
|
||||||
|
'forced_down': 'yes'})
|
||||||
|
expected_resp = {
|
||||||
|
'service': {
|
||||||
|
'status': 'disabled',
|
||||||
|
'state': 'up',
|
||||||
|
'binary': 'nova-compute',
|
||||||
|
'host': 'fake-compute-host',
|
||||||
|
'zone': 'nova', # Comes from CONF.default_availability_zone
|
||||||
|
'updated_at': mock.sentinel.updated_time,
|
||||||
|
'disabled_reason': 'maintenance',
|
||||||
|
'id': service.uuid,
|
||||||
|
'forced_down': True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.assertDictEqual(expected_resp, resp)
|
||||||
|
|
||||||
|
def test_update_forced_down_invalid_value(self):
|
||||||
|
"""Tests that passing an invalid value for forced_down results in
|
||||||
|
a validation error.
|
||||||
|
"""
|
||||||
|
service = self.start_service(
|
||||||
|
'compute', 'fake-compute-host').service_ref
|
||||||
|
self.assertRaises(exception.ValidationError,
|
||||||
|
self.controller.update,
|
||||||
|
self.req, service.uuid,
|
||||||
|
body={'status': 'disabled',
|
||||||
|
'disabled_reason': 'maintenance',
|
||||||
|
'forced_down': 'invalid'})
|
||||||
|
|
||||||
|
def test_update_forced_down_invalid_service(self):
|
||||||
|
"""Tests that you can't update a non-nova-compute service."""
|
||||||
|
service = self.start_service(
|
||||||
|
'scheduler', 'fake-scheduler-host').service_ref
|
||||||
|
ex = self.assertRaises(webob.exc.HTTPBadRequest,
|
||||||
|
self.controller.update,
|
||||||
|
self.req, service.uuid,
|
||||||
|
body={'forced_down': True})
|
||||||
|
self.assertEqual('Updating a nova-scheduler service is not supported. '
|
||||||
|
'Only nova-compute services can be updated.',
|
||||||
|
six.text_type(ex))
|
||||||
|
|
||||||
|
def test_update_empty_body(self):
|
||||||
|
"""Tests that the caller gets a 400 error if they don't request any
|
||||||
|
updates.
|
||||||
|
"""
|
||||||
|
service = self.start_service('compute').service_ref
|
||||||
|
ex = self.assertRaises(webob.exc.HTTPBadRequest,
|
||||||
|
self.controller.update,
|
||||||
|
self.req, service.uuid, body={})
|
||||||
|
self.assertEqual("No updates were requested. Fields 'status' or "
|
||||||
|
"'forced_down' should be specified.",
|
||||||
|
six.text_type(ex))
|
||||||
|
|
||||||
|
def test_update_only_disabled_reason(self):
|
||||||
|
"""Tests that the caller gets a 400 error if they only specify
|
||||||
|
disabled_reason but don't also specify status='disabled'.
|
||||||
|
"""
|
||||||
|
service = self.start_service('compute').service_ref
|
||||||
|
ex = self.assertRaises(webob.exc.HTTPBadRequest,
|
||||||
|
self.controller.update, self.req, service.uuid,
|
||||||
|
body={'disabled_reason': 'missing status'})
|
||||||
|
self.assertEqual("No updates were requested. Fields 'status' or "
|
||||||
|
"'forced_down' should be specified.",
|
||||||
|
six.text_type(ex))
|
||||||
|
|
||||||
|
|
||||||
class ServicesCellsTestV21(test.TestCase):
|
class ServicesCellsTestV21(test.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Microversion 2.53 changes service IDs to UUIDs to ensure uniqueness across
|
||||||
|
cells. Prior to this, ID collisions were possible in multi-cell
|
||||||
|
deployments. See the `REST API Version History`_ and
|
||||||
|
`Compute API reference`_ for details.
|
||||||
|
|
||||||
|
.. _REST API Version History: https://docs.openstack.org/developer/nova/api_microversion_history.html
|
||||||
|
.. _Compute API reference: https://developer.openstack.org/api-ref/compute/
|
Loading…
Reference in New Issue
Block a user