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
|
||||
|
||||
- services: services
|
||||
- id: service_id_body
|
||||
- id: service_id_body_2_52
|
||||
- id: service_id_body_2_53
|
||||
- binary: binary
|
||||
- disabled_reason: disabled_reason_body
|
||||
- host: host_name_body
|
||||
@ -48,7 +49,7 @@ Response
|
||||
- status: service_status
|
||||
- updated_at: updated
|
||||
- zone: OS-EXT-AZ:availability_zone
|
||||
- forced_down: forced_down
|
||||
- forced_down: forced_down_2_11
|
||||
|
||||
**Example List Compute Services**
|
||||
|
||||
@ -64,6 +65,9 @@ Disables scheduling for a Compute service.
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
.. note:: Starting with microversion 2.53 this API is superseded by
|
||||
``PUT /os-services/{service_id}``.
|
||||
|
||||
Normal response codes: 200
|
||||
|
||||
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.
|
||||
|
||||
.. note:: Starting with microversion 2.53 this API is superseded by
|
||||
``PUT /os-services/{service_id}``.
|
||||
|
||||
Normal response codes: 200
|
||||
|
||||
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.
|
||||
|
||||
.. note:: Starting with microversion 2.53 this API is superseded by
|
||||
``PUT /os-services/{service_id}``.
|
||||
|
||||
Normal response codes: 200
|
||||
|
||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404)
|
||||
@ -202,7 +215,7 @@ Request
|
||||
|
||||
- host: host_name_body
|
||||
- binary: binary
|
||||
- forced_down: forced_down
|
||||
- forced_down: forced_down_2_11
|
||||
|
||||
**Example Update Forced Down**
|
||||
|
||||
@ -217,7 +230,7 @@ Response
|
||||
- service: service
|
||||
- binary: binary
|
||||
- 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
|
||||
: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
|
||||
======================
|
||||
|
||||
@ -243,7 +327,8 @@ Request
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- service_id: service_id_path
|
||||
- service_id: service_id_path_2_52
|
||||
- service_id: service_id_path_2_53
|
||||
|
||||
Response
|
||||
--------
|
||||
|
@ -125,6 +125,8 @@ console_token:
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
# Used in the request path for PUT /os-services/disable-log-reason before
|
||||
# microversion 2.53.
|
||||
disabled_reason:
|
||||
description: |
|
||||
The reason for disabling a service.
|
||||
@ -283,12 +285,31 @@ server_id_path:
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
service_id_path:
|
||||
service_id_path_2_52:
|
||||
description: |
|
||||
The id of the service.
|
||||
|
||||
.. note:: This may not uniquely identify a service in a multi-cell
|
||||
deployment.
|
||||
in: path
|
||||
required: true
|
||||
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:
|
||||
description: |
|
||||
The UUID of the snapshot.
|
||||
@ -1898,6 +1919,15 @@ device_tag_nic_attachment:
|
||||
required: false
|
||||
type: string
|
||||
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:
|
||||
description: |
|
||||
The reason for disabling a service.
|
||||
@ -2633,7 +2663,9 @@ force_snapshot:
|
||||
in: body
|
||||
required: false
|
||||
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: |
|
||||
Whether or not this service was forced down manually by an
|
||||
administrator. This value is useful to know that some 3rd party has
|
||||
@ -2642,6 +2674,26 @@ forced_down:
|
||||
required: true
|
||||
type: boolean
|
||||
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:
|
||||
description: |
|
||||
The action.
|
||||
@ -5064,6 +5116,26 @@ service_id_body:
|
||||
in: body
|
||||
required: true
|
||||
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:
|
||||
description: |
|
||||
The state of the service. One of ``up`` or ``down``.
|
||||
@ -5076,6 +5148,14 @@ service_status:
|
||||
in: body
|
||||
required: true
|
||||
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:
|
||||
description: |
|
||||
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",
|
||||
"version": "2.52",
|
||||
"version": "2.53",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.52",
|
||||
"version": "2.53",
|
||||
"min_version": "2.1",
|
||||
"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
|
||||
traceback field.
|
||||
* 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
|
||||
@ -132,7 +135,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
||||
# Note(cyeoh): This only applies for the v2.1 API once microversions
|
||||
# support is fully merged. It does not affect the V2 API.
|
||||
_MIN_API_VERSION = "2.1"
|
||||
_MAX_API_VERSION = "2.52"
|
||||
_MAX_API_VERSION = "2.53"
|
||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||
|
||||
# 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
|
||||
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'],
|
||||
'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.
|
||||
|
||||
from oslo_utils import strutils
|
||||
from oslo_utils import uuidutils
|
||||
import webob.exc
|
||||
|
||||
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 wsgi
|
||||
from nova.api import validation
|
||||
from nova import availability_zones
|
||||
from nova import compute
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
@ -27,6 +29,8 @@ from nova.policies import services as services_policies
|
||||
from nova import servicegroup
|
||||
from nova import utils
|
||||
|
||||
UUID_FOR_ID_MIN_VERSION = '2.53'
|
||||
|
||||
|
||||
class ServiceController(wsgi.Controller):
|
||||
|
||||
@ -67,7 +71,7 @@ class ServiceController(wsgi.Controller):
|
||||
|
||||
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)
|
||||
state = (alive and "up") or "down"
|
||||
active = 'enabled'
|
||||
@ -75,9 +79,22 @@ class ServiceController(wsgi.Controller):
|
||||
active = 'disabled'
|
||||
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'],
|
||||
'host': svc['host'],
|
||||
'id': svc['id'],
|
||||
'id': svc['uuid' if uuid_for_id else 'id'],
|
||||
'zone': svc['availability_zone'],
|
||||
'status': active,
|
||||
'state': state,
|
||||
@ -91,7 +108,7 @@ class ServiceController(wsgi.Controller):
|
||||
|
||||
def _get_services_list(self, req, additional_fields=()):
|
||||
_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]
|
||||
|
||||
def _enable(self, body, context):
|
||||
@ -179,10 +196,17 @@ class ServiceController(wsgi.Controller):
|
||||
context = req.environ['nova.context']
|
||||
context.can(services_policies.BASE_POLICY_NAME)
|
||||
|
||||
try:
|
||||
utils.validate_integer(id, 'id')
|
||||
except exception.InvalidInput as exc:
|
||||
raise webob.exc.HTTPBadRequest(explanation=exc.format_message())
|
||||
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:
|
||||
utils.validate_integer(id, 'id')
|
||||
except exception.InvalidInput as exc:
|
||||
raise webob.exc.HTTPBadRequest(
|
||||
explanation=exc.format_message())
|
||||
|
||||
try:
|
||||
service = self.host_api.service_get_by_id(context, id)
|
||||
@ -215,11 +239,18 @@ class ServiceController(wsgi.Controller):
|
||||
|
||||
return {'services': _services}
|
||||
|
||||
@wsgi.Controller.api_version('2.1', '2.52')
|
||||
@extensions.expected_errors((400, 404))
|
||||
@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):
|
||||
"""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'):
|
||||
actions = self.actions.copy()
|
||||
actions["force-down"] = self._forced_down
|
||||
@ -227,3 +258,89 @@ class ServiceController(wsgi.Controller):
|
||||
actions = self.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)
|
||||
|
||||
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):
|
||||
obj_p = self._obj.obj_to_primitive()
|
||||
obj_p['cell_proxy.class_name'] = self.__class__.__name__
|
||||
|
@ -50,6 +50,11 @@ services_policies = [
|
||||
'method': 'PUT',
|
||||
'path': '/os-services/force-down'
|
||||
},
|
||||
{
|
||||
# Added in microversion 2.53.
|
||||
'method': 'PUT',
|
||||
'path': '/os-services/{service_id}'
|
||||
},
|
||||
{
|
||||
'method': 'DELETE',
|
||||
'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 nova import exception
|
||||
from nova.tests.functional.api_sample_tests import api_sample_base
|
||||
from nova.tests.unit.api.openstack.compute import test_services
|
||||
|
||||
@ -104,3 +105,57 @@ class ServicesV211JsonTest(ServicesJsonTest):
|
||||
'service-force-down-put-req', subs)
|
||||
self._verify_response('service-force-down-put-resp', subs,
|
||||
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
|
||||
|
||||
# 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):
|
||||
super(NotificationSampleTestBase, self).setUp()
|
||||
|
||||
@ -63,11 +70,7 @@ class NotificationSampleTestBase(test.TestCase,
|
||||
self.api = api_fixture.api
|
||||
self.admin_api = api_fixture.admin_api
|
||||
|
||||
# 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.
|
||||
max_version = 'latest'
|
||||
max_version = self.MAX_MICROVERSION
|
||||
self.api.microversion = max_version
|
||||
self.admin_api.microversion = max_version
|
||||
|
||||
|
@ -14,17 +14,22 @@
|
||||
|
||||
from oslo_utils import fixture as utils_fixture
|
||||
|
||||
from nova import exception
|
||||
from nova.tests import fixtures
|
||||
from nova.tests.functional.notification_sample_tests \
|
||||
import notification_sample_base
|
||||
from nova.tests.unit.api.openstack.compute import test_services
|
||||
|
||||
|
||||
class TestServiceUpdateNotificationSample(
|
||||
class TestServiceUpdateNotificationSamplev2_52(
|
||||
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):
|
||||
super(TestServiceUpdateNotificationSample, self).setUp()
|
||||
super(TestServiceUpdateNotificationSamplev2_52, self).setUp()
|
||||
self.stub_out("nova.db.service_get_by_host_and_binary",
|
||||
test_services.fake_service_get_by_host_binary)
|
||||
self.stub_out("nova.db.service_update",
|
||||
@ -69,3 +74,51 @@ class TestServiceUpdateNotificationSample(
|
||||
'disabled': True,
|
||||
'disabled_reason': 'test2',
|
||||
'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 mock
|
||||
from oslo_utils import fixture as utils_fixture
|
||||
import six
|
||||
import webob.exc
|
||||
|
||||
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.unit.api.openstack import fakes
|
||||
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 = [
|
||||
@ -43,6 +48,7 @@ fake_services_list = [
|
||||
binary='nova-scheduler',
|
||||
host='host1',
|
||||
id=1,
|
||||
uuid=uuidsentinel.svc1,
|
||||
disabled=True,
|
||||
topic='scheduler',
|
||||
updated_at=datetime.datetime(2012, 10, 29, 13, 42, 2),
|
||||
@ -54,6 +60,7 @@ fake_services_list = [
|
||||
binary='nova-compute',
|
||||
host='host1',
|
||||
id=2,
|
||||
uuid=FAKE_UUID_COMPUTE_HOST1,
|
||||
disabled=True,
|
||||
topic='compute',
|
||||
updated_at=datetime.datetime(2012, 10, 29, 13, 42, 5),
|
||||
@ -65,6 +72,7 @@ fake_services_list = [
|
||||
binary='nova-scheduler',
|
||||
host='host2',
|
||||
id=3,
|
||||
uuid=uuidsentinel.svc3,
|
||||
disabled=False,
|
||||
topic='scheduler',
|
||||
updated_at=datetime.datetime(2012, 9, 19, 6, 55, 34),
|
||||
@ -76,6 +84,7 @@ fake_services_list = [
|
||||
binary='nova-compute',
|
||||
host='host2',
|
||||
id=4,
|
||||
uuid=uuidsentinel.svc4,
|
||||
disabled=True,
|
||||
topic='compute',
|
||||
updated_at=datetime.datetime(2012, 9, 18, 8, 3, 38),
|
||||
@ -88,6 +97,7 @@ fake_services_list = [
|
||||
binary='nova-osapi_compute',
|
||||
host='host2',
|
||||
id=5,
|
||||
uuid=uuidsentinel.svc5,
|
||||
disabled=False,
|
||||
topic=None,
|
||||
updated_at=None,
|
||||
@ -99,6 +109,7 @@ fake_services_list = [
|
||||
binary='nova-metadata',
|
||||
host='host2',
|
||||
id=6,
|
||||
uuid=uuidsentinel.svc6,
|
||||
disabled=False,
|
||||
topic=None,
|
||||
updated_at=None,
|
||||
@ -927,6 +938,235 @@ class ServicesTestV211(ServicesTestV21):
|
||||
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):
|
||||
|
||||
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