Merge "Give the illusion of microversion support"

This commit is contained in:
Zuul 2019-01-11 00:57:41 +00:00 committed by Gerrit Code Review
commit ca032db94c
6 changed files with 110 additions and 2 deletions

View File

@ -57,6 +57,7 @@ logilab-common==1.4.1
Mako==1.0.7
MarkupSafe==1.0
mccabe==0.2.1
microversion-parse==0.2.1
mock==2.0.0
monotonic==1.4
mox3==0.25.0

View File

@ -0,0 +1,5 @@
---
features:
- |
Users of Sahara's APIv2 may request a microversion of that API, with
"OpenStack-API-Version: data-processing [version]" in the request headers.

View File

@ -15,6 +15,7 @@ Jinja2>=2.10 # BSD License (3 clause)
jsonschema<3.0.0,>=2.6.0 # MIT
keystoneauth1>=3.4.0 # Apache-2.0
keystonemiddleware>=4.17.0 # Apache-2.0
microversion-parse>=0.2.1 # Apache-2.0
oslo.config>=5.2.0 # Apache-2.0
oslo.concurrency>=3.26.0 # Apache-2.0
oslo.context>=2.19.2 # Apache-2.0

View File

@ -0,0 +1,30 @@
# Copyright 2018 OpenStack Contributors
#
# 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.
API_VERSIONS = ["2.0"]
MIN_API_VERSION = API_VERSIONS[0]
MAX_API_VERSION = API_VERSIONS[-1]
LATEST = "latest"
VERSION_STRING_REGEX = r"^([1-9]\d*).([1-9]\d*|0)$"
OPENSTACK_API_VERSION_HEADER = "OpenStack-API-Version"
VARY_HEADER = "Vary"
SAHARA_SERVICE_TYPE = "data-processing"
BAD_REQUEST_STATUS_CODE = 400
BAD_REQUEST_STATUS_NAME = "BAD_REQUEST"
NOT_ACCEPTABLE_STATUS_CODE = 406
NOT_ACCEPTABLE_STATUS_NAME = "NOT_ACCEPTABLE"

View File

@ -20,6 +20,8 @@ from oslo_serialization import jsonutils
import webob
import webob.dec
from sahara.api import microversion as mv
class VersionResponseMiddlewareV1(base.Middleware):
@ -67,7 +69,9 @@ class VersionResponseMiddlewareV2(VersionResponseMiddlewareV1):
version_response["versions"].append(
{"id": "v2",
"status": "EXPERIMENTAL",
"links": self._get_links("2", req)
"links": self._get_links("2", req),
"min_version": mv.MIN_API_VERSION,
"max_version": mv.MAX_API_VERSION
}
)
return version_response

View File

@ -13,14 +13,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import re
import traceback
import flask
import microversion_parse
from oslo_log import log as logging
from oslo_middleware import request_id as oslo_req_id
import six
from werkzeug import datastructures
from sahara.api import microversion as mv
from sahara import context
from sahara import exceptions as ex
from sahara.i18n import _
@ -114,7 +117,27 @@ class Rest(flask.Blueprint):
return decorator
def check_microversion_header():
requested_version = get_requested_microversion()
if not re.match(mv.VERSION_STRING_REGEX, requested_version):
bad_request_microversion(requested_version)
if requested_version not in mv.API_VERSIONS:
not_acceptable_microversion(requested_version)
def add_vary_header(response):
response.headers[mv.VARY_HEADER] = mv.OPENSTACK_API_VERSION_HEADER
response.headers[mv.OPENSTACK_API_VERSION_HEADER] = "{} {}".format(
mv.SAHARA_SERVICE_TYPE, get_requested_microversion())
return response
class RestV2(Rest):
def __init__(self, *args, **kwargs):
super(RestV2, self).__init__(*args, **kwargs)
self.before_request(check_microversion_header)
self.after_request(add_vary_header)
def route(self, rule, **options):
status = options.pop('status_code', None)
file_upload = options.pop('file_upload', False)
@ -266,6 +289,18 @@ def get_request_args():
return flask.request.args
def get_requested_microversion():
requested_version = microversion_parse.get_version(
flask.request.headers,
mv.SAHARA_SERVICE_TYPE
)
if requested_version is None:
requested_version = mv.MIN_API_VERSION
elif requested_version == mv.LATEST:
requested_version = mv.MAX_API_VERSION
return requested_version
def abort_and_log(status_code, descr, exc=None):
LOG.error("Request aborted with status code {code} and "
"message '{message}'".format(code=status_code, message=descr))
@ -276,19 +311,51 @@ def abort_and_log(status_code, descr, exc=None):
flask.abort(status_code, description=descr)
def render_error_message(error_code, error_message, error_name):
def render_error_message(error_code, error_message, error_name, **msg_kwargs):
message = {
"error_code": error_code,
"error_message": error_message,
"error_name": error_name
}
message.update(**msg_kwargs)
resp = render(message)
resp.status_code = error_code
return resp
def not_acceptable_microversion(requested_version):
message = ("Version {} is not supported by the API. "
"Minimum is {} and maximum is {}.".format(
requested_version,
mv.MIN_API_VERSION,
mv.MAX_API_VERSION
))
resp = render_error_message(
mv.NOT_ACCEPTABLE_STATUS_CODE,
message,
mv.NOT_ACCEPTABLE_STATUS_NAME,
max_version=mv.MAX_API_VERSION,
min_version=mv.MIN_API_VERSION
)
flask.abort(resp)
def bad_request_microversion(requested_version):
message = ("API Version String {} is of invalid format. Must be of format"
" MajorNum.MinorNum.").format(requested_version)
resp = render_error_message(
mv.BAD_REQUEST_STATUS_CODE,
message,
mv.BAD_REQUEST_STATUS_NAME,
max_version=mv.MAX_API_VERSION,
min_version=mv.MIN_API_VERSION
)
flask.abort(resp)
def invalid_param_error(status_code, descr, exc=None):
LOG.error("Request aborted with status code {code} and "
"message '{message}'".format(code=status_code, message=descr))