Give the illusion of microversion support
Understand and react to microversions in accordance with http://specs.openstack.org/openstack/api-wg/guidelines/microversion_specification.html The actual mechanism allowing for new microversions of APIv2 will come later. Story: 2002178 Task: 20044 Change-Id: I2b664189e45ac4ffd02c3a176787b4bfb78b3871
This commit is contained in:
parent
69d74c1a66
commit
33489a1f9f
@ -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
|
||||
|
@ -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.
|
@ -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
|
||||
|
30
sahara/api/microversion.py
Normal file
30
sahara/api/microversion.py
Normal 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"
|
@ -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
|
||||
|
@ -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))
|
||||
|
Loading…
Reference in New Issue
Block a user