Enable fm-rest-api to return json format error message

Closes-Bug: 1789979

fm-rest-api returns json format message for the correct api
access. For the invalid api access, the message is with html
format, need add wrap_app to convert it to json format.
Fix:
The implementation code is copied from upstream Ironic API,
and minor modification to pass tox pep8 check.
Also remove "x" attribute for the py file.
Test:
Pass build and multi node deploy test. Confirmed the return
message is json format.

Change-Id: I36fa89b82377d52008a467316c42c06caa65cd90
Signed-off-by: Shuicheng Lin <shuicheng.lin@intel.com>
This commit is contained in:
Shuicheng Lin 2018-11-29 23:14:51 +08:00
parent e196077d84
commit aebc9ee1fa
5 changed files with 115 additions and 0 deletions

View File

@ -23,6 +23,7 @@ from oslo_log import log
import pecan
from fm.api import config
from fm.api import middleware
from fm.common import policy
from fm.common.i18n import _
@ -53,6 +54,7 @@ def setup_app(config=None):
debug=CONF.debug,
logging=getattr(config, 'logging', {}),
force_canonical=getattr(config.app, 'force_canonical', True),
wrap_app=middleware.ParsableErrorMiddleware,
guess_content_type_from_ext=False,
**app_conf
)

0
fm-rest-api/fm/fm/api/controllers/v1/alarm.py Executable file → Normal file
View File

0
fm-rest-api/fm/fm/api/controllers/v1/utils.py Executable file → Normal file
View File

View File

@ -3,3 +3,18 @@
#
# SPDX-License-Identifier: Apache-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from fm.api.middleware import auth_token
from fm.api.middleware import parsable_error
ParsableErrorMiddleware = parsable_error.ParsableErrorMiddleware
AuthTokenMiddleware = auth_token.AuthTokenMiddleware
__all__ = (ParsableErrorMiddleware,
AuthTokenMiddleware)

View File

@ -0,0 +1,98 @@
# -*- encoding: utf-8 -*-
#
# Copyright © 2012 New Dream Network, LLC (DreamHost)
#
# 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.
"""
Middleware to replace the plain text message body of an error
response with one formatted so the client can parse it.
Based on pecan.middleware.errordocument
"""
import json
from xml import etree as et
from oslo_log import log
import six
import webob
from fm.common.i18n import _
LOG = log.getLogger(__name__)
class ParsableErrorMiddleware(object):
"""Replace error body with something the client can parse."""
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
# Request for this state, modified by replace_start_response()
# and used when an error is being reported.
state = {}
def replacement_start_response(status, headers, exc_info=None):
"""Overrides the default response to make errors parsable."""
try:
status_code = int(status.split(' ')[0])
state['status_code'] = status_code
except (ValueError, TypeError): # pragma: nocover
raise Exception(_(
'ErrorDocumentMiddleware received an invalid '
'status %s') % status)
else:
if (state['status_code'] // 100) not in (2, 3):
# Remove some headers so we can replace them later
# when we have the full error message and can
# compute the length.
headers = [(h, v)
for (h, v) in headers
if h not in ('Content-Length', 'Content-Type')
]
# Save the headers in case we need to modify them.
state['headers'] = headers
return start_response(status, headers, exc_info)
# The default output is application/json. However, Pecan will try
# to output HTML errors if no Accept header is provided.
if 'HTTP_ACCEPT' not in environ or environ['HTTP_ACCEPT'] == '*/*':
environ['HTTP_ACCEPT'] = 'application/json'
app_iter = self.app(environ, replacement_start_response)
if (state['status_code'] // 100) not in (2, 3):
req = webob.Request(environ)
if (req.accept.best_match(['application/json',
'application/xml']) == 'application/xml'):
try:
# simple check xml is valid
body = [et.ElementTree.tostring(
et.ElementTree.fromstring('<error_message>' +
'\n'.join(app_iter) +
'</error_message>'))]
except et.ElementTree.ParseError as err:
LOG.error('Error parsing HTTP response: %s', err)
body = ['<error_message>%s' % state['status_code'] +
'</error_message>']
state['headers'].append(('Content-Type', 'application/xml'))
else:
if six.PY3:
app_iter = [i.decode('utf-8') for i in app_iter]
body = [json.dumps({'error_message': '\n'.join(app_iter)})]
if six.PY3:
body = [item.encode('utf-8') for item in body]
state['headers'].append(('Content-Type', 'application/json'))
state['headers'].append(('Content-Length', str(len(body[0]))))
else:
body = app_iter
return body