Provide framework for setting placement error codes
The API-sig has a guideline[1] for including error codes in error responses to help distinguish errors with the same status code from one another. This change provides a simplest-thing-that-could- possibly-work solution to make that go. This solution comes about after a few different constraints and attempts: * We would prefer to go on using the existing webob.exc exceptions, not make subclasses. * We already have a special wrapper around our wsgi apps to deal with setting the json_error_formatter. * Though webob allows custom Request and Response objects, it uses the default Response object as the parent of the HTTP exceptions. * The Response object accepts kwargs, but only if they can be associated with known attributes on the class. Since we can't subclass... * The json_error_formatter method is not passed the raw exception, but it does get the current WSGI environ * The webob.exc classes take a 'comment' kwarg that is not used, but is also not passed to the json_error_formatter. Therefore, when we raise an exception, we can set 'comment' to a code and then assign that comment to a well known field in the environ and if that environ is set in json_error_formatter, we can set 'code' in the output. This is done in a new microversion, 1.23. Every response gets a default code 'placement.undefined_code' from 1.23 on. Future development will add specific codes where required. This change adds a stub code for inventory in use when doing a PUT to .../inventories but the name may need improvement. [1] http://specs.openstack.org/openstack/api-wg/guidelines/errors.html Implements blueprint placement-api-error-handling Change-Id: I9a833aa35d474caa35e640bbad6c436a3b16ac5e
This commit is contained in:
parent
e03bb78f6f
commit
8a3e7c5a95
@ -252,6 +252,24 @@ environment. It can be retrieved as follows::
|
|||||||
changes in a patch that is separate from and prior to the HTTP API
|
changes in a patch that is separate from and prior to the HTTP API
|
||||||
change.
|
change.
|
||||||
|
|
||||||
|
If a handler needs to return an error response, with the advent of `link to
|
||||||
|
spec once it merges`_, it is possible to include a code in the JSON error
|
||||||
|
response. This can be used to distinguish different errors with the same HTTP
|
||||||
|
response status code (a common case is a generation conflict versus an
|
||||||
|
inventory in use conflict). Error codes are simple namespaced strings (e.g.,
|
||||||
|
``placement.inventory.inuse``) for which symbols are maintained in
|
||||||
|
``nova.api.openstack.placement.errors``. Adding a symbol to a response is done
|
||||||
|
by using the ``comment`` kwarg to a WebOb exception, like this::
|
||||||
|
|
||||||
|
except exception.InventoryInUse as exc:
|
||||||
|
raise webob.exc.HTTPConflict(
|
||||||
|
_('update conflict: %(error)s') % {'error': exc},
|
||||||
|
comment=errors.INVENTORY_INUSE)
|
||||||
|
|
||||||
|
Code that adds newly raised exceptions should include an error code. Find
|
||||||
|
additional guidelines on use in the docs for
|
||||||
|
``nova.api.openstack.placement.errors``.
|
||||||
|
|
||||||
Testing of handler code is described in the next section.
|
Testing of handler code is described in the next section.
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
|
42
nova/api/openstack/placement/errors.py
Normal file
42
nova/api/openstack/placement/errors.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
"""Error code symbols to be used in structured JSON error responses.
|
||||||
|
|
||||||
|
These are strings to be used in the 'code' attribute, as described by
|
||||||
|
the API guideline on `errors`_.
|
||||||
|
|
||||||
|
There must be only one instance of any string value and it should have
|
||||||
|
only one associated constant SYMBOL.
|
||||||
|
|
||||||
|
In a WSGI handler (representing the sole handler for an HTTP method and
|
||||||
|
URI) each error condition should get a separate error code. Reusing an
|
||||||
|
error code in a different handler is not just acceptable, but useful.
|
||||||
|
|
||||||
|
For example 'placement.inventory.inuse' is meaningful and correct in both
|
||||||
|
``PUT /resource_providers/{uuid}/inventories`` and ``DELETE`` on the same
|
||||||
|
URI.
|
||||||
|
|
||||||
|
.. _errors: http://specs.openstack.org/openstack/api-wg/guidelines/errors.html
|
||||||
|
"""
|
||||||
|
|
||||||
|
# NOTE(cdent): This is the simplest thing that can possibly work, for now.
|
||||||
|
# If it turns out we want to automate this, or put different resources in
|
||||||
|
# different files, or otherwise change things, that's fine. The only thing
|
||||||
|
# that needs to be maintained as the same are the strings that API end
|
||||||
|
# users use. How they are created is completely fungible.
|
||||||
|
|
||||||
|
|
||||||
|
# Do not change the string values. Once set, they are set.
|
||||||
|
# Do not reuse string values. There should be only one symbol for any
|
||||||
|
# value.
|
||||||
|
DEFAULT = 'placement.undefined_code'
|
||||||
|
INVENTORY_INUSE = 'placement.inventory.inuse'
|
@ -18,6 +18,7 @@ from oslo_serialization import jsonutils
|
|||||||
from oslo_utils import encodeutils
|
from oslo_utils import encodeutils
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
|
from nova.api.openstack.placement import errors
|
||||||
from nova.api.openstack.placement import exception
|
from nova.api.openstack.placement import exception
|
||||||
from nova.api.openstack.placement import microversion
|
from nova.api.openstack.placement import microversion
|
||||||
from nova.api.openstack.placement.objects import resource_provider as rp_obj
|
from nova.api.openstack.placement.objects import resource_provider as rp_obj
|
||||||
@ -317,10 +318,13 @@ def set_inventories(req):
|
|||||||
'%(rp_uuid)s: %(error)s') % {'rp_uuid': resource_provider.uuid,
|
'%(rp_uuid)s: %(error)s') % {'rp_uuid': resource_provider.uuid,
|
||||||
'error': exc})
|
'error': exc})
|
||||||
except (exception.ConcurrentUpdateDetected,
|
except (exception.ConcurrentUpdateDetected,
|
||||||
exception.InventoryInUse,
|
|
||||||
db_exc.DBDuplicateEntry) as exc:
|
db_exc.DBDuplicateEntry) as exc:
|
||||||
raise webob.exc.HTTPConflict(
|
raise webob.exc.HTTPConflict(
|
||||||
_('update conflict: %(error)s') % {'error': exc})
|
_('update conflict: %(error)s') % {'error': exc})
|
||||||
|
except exception.InventoryInUse as exc:
|
||||||
|
raise webob.exc.HTTPConflict(
|
||||||
|
_('update conflict: %(error)s') % {'error': exc},
|
||||||
|
comment=errors.INVENTORY_INUSE)
|
||||||
except exception.InvalidInventoryCapacity as exc:
|
except exception.InvalidInventoryCapacity as exc:
|
||||||
raise webob.exc.HTTPBadRequest(
|
raise webob.exc.HTTPBadRequest(
|
||||||
_('Unable to update inventory for resource provider '
|
_('Unable to update inventory for resource provider '
|
||||||
|
@ -63,6 +63,7 @@ VERSIONS = [
|
|||||||
# GET /allocation_candidates
|
# GET /allocation_candidates
|
||||||
'1.22', # Support forbidden traits in the required parameter of
|
'1.22', # Support forbidden traits in the required parameter of
|
||||||
# GET /resource_providers and GET /allocation_candidates
|
# GET /resource_providers and GET /allocation_candidates
|
||||||
|
'1.23', # Add support for error codes in error response JSON
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -270,3 +270,12 @@ Add support for expressing traits which are forbidden when filtering
|
|||||||
trait is a properly formatted trait in the existing ``required`` parameter,
|
trait is a properly formatted trait in the existing ``required`` parameter,
|
||||||
prefixed by a ``!``. For example ``required=!STORAGE_DISK_SSD`` asks that the
|
prefixed by a ``!``. For example ``required=!STORAGE_DISK_SSD`` asks that the
|
||||||
results not include any resource providers that provide solid state disk.
|
results not include any resource providers that provide solid state disk.
|
||||||
|
|
||||||
|
1.23 Include code attribute in JSON error responses
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
JSON formatted error responses gain a new attribute, ``code``, with a value
|
||||||
|
that identifies the type of this error. This can be used to distinguish errors
|
||||||
|
that are different but use the same HTTP status code. Any error response which
|
||||||
|
does not specifically define a code will have the code
|
||||||
|
``placement.undefined_code``.
|
||||||
|
@ -21,12 +21,16 @@ from oslo_utils import timeutils
|
|||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
|
from nova.api.openstack.placement import errors
|
||||||
from nova.api.openstack.placement import lib as placement_lib
|
from nova.api.openstack.placement import lib as placement_lib
|
||||||
# NOTE(cdent): avoid cyclical import conflict between util and
|
# NOTE(cdent): avoid cyclical import conflict between util and
|
||||||
# microversion
|
# microversion
|
||||||
import nova.api.openstack.placement.microversion
|
import nova.api.openstack.placement.microversion
|
||||||
from nova.i18n import _
|
from nova.i18n import _
|
||||||
|
|
||||||
|
# Error code handling constants
|
||||||
|
ENV_ERROR_CODE = 'placement.error_code'
|
||||||
|
ERROR_CODE_MICROVERSION = (1, 23)
|
||||||
|
|
||||||
# Querystring-related constants
|
# Querystring-related constants
|
||||||
_QS_RESOURCES = 'resources'
|
_QS_RESOURCES = 'resources'
|
||||||
@ -101,6 +105,9 @@ def json_error_formatter(body, status, title, environ):
|
|||||||
Follows API-WG guidelines at
|
Follows API-WG guidelines at
|
||||||
http://specs.openstack.org/openstack/api-wg/guidelines/errors.html
|
http://specs.openstack.org/openstack/api-wg/guidelines/errors.html
|
||||||
"""
|
"""
|
||||||
|
# Shortcut to microversion module, to avoid wraps below.
|
||||||
|
microversion = nova.api.openstack.placement.microversion
|
||||||
|
|
||||||
# Clear out the html that webob sneaks in.
|
# Clear out the html that webob sneaks in.
|
||||||
body = webob.exc.strip_tags(body)
|
body = webob.exc.strip_tags(body)
|
||||||
# Get status code out of status message. webob's error formatter
|
# Get status code out of status message. webob's error formatter
|
||||||
@ -111,6 +118,13 @@ def json_error_formatter(body, status, title, environ):
|
|||||||
'title': title,
|
'title': title,
|
||||||
'detail': body
|
'detail': body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Version may not be set if we have experienced an error before it
|
||||||
|
# is set.
|
||||||
|
want_version = environ.get(microversion.MICROVERSION_ENVIRON)
|
||||||
|
if want_version and want_version.matches(ERROR_CODE_MICROVERSION):
|
||||||
|
error_dict['code'] = environ.get(ENV_ERROR_CODE, errors.DEFAULT)
|
||||||
|
|
||||||
# If the request id middleware has had a chance to add an id,
|
# If the request id middleware has had a chance to add an id,
|
||||||
# put it in the error response.
|
# put it in the error response.
|
||||||
if request_id.ENV_REQUEST_ID in environ:
|
if request_id.ENV_REQUEST_ID in environ:
|
||||||
@ -119,7 +133,6 @@ def json_error_formatter(body, status, title, environ):
|
|||||||
# When there is a no microversion in the environment and a 406,
|
# When there is a no microversion in the environment and a 406,
|
||||||
# microversion parsing failed so we need to include microversion
|
# microversion parsing failed so we need to include microversion
|
||||||
# min and max information in the error response.
|
# min and max information in the error response.
|
||||||
microversion = nova.api.openstack.placement.microversion
|
|
||||||
if status_code == 406 and microversion.MICROVERSION_ENVIRON not in environ:
|
if status_code == 406 and microversion.MICROVERSION_ENVIRON not in environ:
|
||||||
error_dict['max_version'] = microversion.max_version_string()
|
error_dict['max_version'] = microversion.max_version_string()
|
||||||
error_dict['min_version'] = microversion.min_version_string()
|
error_dict['min_version'] = microversion.min_version_string()
|
||||||
|
@ -30,4 +30,9 @@ class PlacementWsgify(wsgify):
|
|||||||
except webob.exc.HTTPException as exc:
|
except webob.exc.HTTPException as exc:
|
||||||
LOG.debug("Placement API returning an error response: %s", exc)
|
LOG.debug("Placement API returning an error response: %s", exc)
|
||||||
exc.json_formatter = util.json_error_formatter
|
exc.json_formatter = util.json_error_formatter
|
||||||
|
# The exception itself is not passed to json_error_formatter
|
||||||
|
# but environ is, so set the environ.
|
||||||
|
if exc.comment:
|
||||||
|
req.environ[util.ENV_ERROR_CODE] = exc.comment
|
||||||
|
exc.comment = None
|
||||||
raise
|
raise
|
||||||
|
@ -26,6 +26,14 @@ tests:
|
|||||||
response_json_paths:
|
response_json_paths:
|
||||||
$.errors[0].request_id: /req-[a-fA-F0-9-]+/
|
$.errors[0].request_id: /req-[a-fA-F0-9-]+/
|
||||||
|
|
||||||
|
- name: error message has default code 1.23
|
||||||
|
GET: /barnabas
|
||||||
|
status: 404
|
||||||
|
request_headers:
|
||||||
|
openstack-api-version: placement 1.23
|
||||||
|
response_json_paths:
|
||||||
|
$.errors[0].code: placement.undefined_code
|
||||||
|
|
||||||
- name: 404 at no resource provider
|
- name: 404 at no resource provider
|
||||||
GET: /resource_providers/fd0dd55c-6330-463b-876c-31c54e95cb95
|
GET: /resource_providers/fd0dd55c-6330-463b-876c-31c54e95cb95
|
||||||
status: 404
|
status: 404
|
||||||
|
@ -39,13 +39,13 @@ tests:
|
|||||||
response_json_paths:
|
response_json_paths:
|
||||||
$.errors[0].title: Not Acceptable
|
$.errors[0].title: Not Acceptable
|
||||||
|
|
||||||
- name: latest microversion is 1.22
|
- name: latest microversion is 1.23
|
||||||
GET: /
|
GET: /
|
||||||
request_headers:
|
request_headers:
|
||||||
openstack-api-version: placement latest
|
openstack-api-version: placement latest
|
||||||
response_headers:
|
response_headers:
|
||||||
vary: /openstack-api-version/
|
vary: /openstack-api-version/
|
||||||
openstack-api-version: placement 1.22
|
openstack-api-version: placement 1.23
|
||||||
|
|
||||||
- name: other accept header bad version
|
- name: other accept header bad version
|
||||||
GET: /
|
GET: /
|
||||||
|
@ -31,6 +31,19 @@ tests:
|
|||||||
response_strings:
|
response_strings:
|
||||||
- "Unable to delete resource provider $ENVIRON['RP_UUID']: Resource provider has allocations."
|
- "Unable to delete resource provider $ENVIRON['RP_UUID']: Resource provider has allocations."
|
||||||
|
|
||||||
|
- name: fail to change inventory via put 1.23
|
||||||
|
PUT: /resource_providers/$ENVIRON['RP_UUID']/inventories
|
||||||
|
request_headers:
|
||||||
|
accept: application/json
|
||||||
|
content-type: application/json
|
||||||
|
openstack-api-version: placement 1.23
|
||||||
|
data:
|
||||||
|
resource_provider_generation: 5
|
||||||
|
inventories: {}
|
||||||
|
status: 409
|
||||||
|
response_json_paths:
|
||||||
|
$.errors[0].code: placement.inventory.inuse
|
||||||
|
|
||||||
- name: fail to delete all inventory
|
- name: fail to delete all inventory
|
||||||
DELETE: /resource_providers/$ENVIRON['RP_UUID']/inventories
|
DELETE: /resource_providers/$ENVIRON['RP_UUID']/inventories
|
||||||
request_headers:
|
request_headers:
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
In microversion 1.23 of the placement service, JSON formatted error
|
||||||
|
responses gain a new attribute, ``code``, with a value that identifies the
|
||||||
|
type of this error. This can be used to distinguish errors that are
|
||||||
|
different but use the same HTTP status code. Any error response which does
|
||||||
|
not specifically define a code will have the code
|
||||||
|
``placement.undefined_code``.
|
Loading…
Reference in New Issue
Block a user