Nova_APIGW REST API Microversion Support
1. What is the problem The current Nova_APIGW does not support microversion function, the service controller uses a fixed API version number to initialize novaclient 2. What is the solution to the problem When the service controller receives an API request, it will get the microversion number from request headers , and use this to initialize novaclient. For how to get the microversion number, please refer to: https://specs.openstack.org/openstack/nova-specs/specs/kilo/implemented/api-microversions.html The microversion supported range is 2.1 to latest version 3. What the features need to be implemented to the Tricircle to realize the solution Nova_APIGW microversion support added Change-Id: Idf44c91100e5cb8ad0355164c9be991aa54a652b
This commit is contained in:
parent
361f7f7a27
commit
707c914e30
@ -81,3 +81,19 @@ JT_PORT_DELETE = 'port_delete'
|
||||
# network type
|
||||
NT_LOCAL = 'local'
|
||||
NT_SHARED_VLAN = 'shared_vlan'
|
||||
|
||||
|
||||
# nova microverson headers key word
|
||||
NOVA_API_VERSION_REQUEST_HEADER = 'OpenStack-API-Version'
|
||||
LEGACY_NOVA_API_VERSION_REQUEST_HEADER = 'X-OpenStack-Nova-API-Version'
|
||||
HTTP_NOVA_API_VERSION_REQUEST_HEADER = 'HTTP_OPENSTACK_API_VERSION'
|
||||
HTTP_LEGACY_NOVA_API_VERSION_REQUEST_HEADER = \
|
||||
'HTTP_X_OPENSTACK_NOVA_API_VERSION'
|
||||
|
||||
# nova microverson prefix
|
||||
NOVA_MICRO_VERSION_PREFIX = 'compute'
|
||||
|
||||
|
||||
# support nova version range
|
||||
NOVA_APIGW_MIN_VERSION = '2.1'
|
||||
NOVA_APIGW_MAX_VERSION = '2.36'
|
||||
|
@ -19,6 +19,7 @@ from pecan import request
|
||||
|
||||
import oslo_context.context as oslo_ctx
|
||||
|
||||
from tricircle.common import constants
|
||||
from tricircle.common.i18n import _
|
||||
from tricircle.db import core
|
||||
|
||||
@ -46,7 +47,9 @@ def extract_context_from_environ():
|
||||
'domain': 'HTTP_X_DOMAIN_ID',
|
||||
'user_domain': 'HTTP_X_USER_DOMAIN_ID',
|
||||
'project_domain': 'HTTP_X_PROJECT_DOMAIN_ID',
|
||||
'request_id': 'openstack.request_id'}
|
||||
'request_id': 'openstack.request_id',
|
||||
'nova_micro_version':
|
||||
constants.NOVA_API_VERSION_REQUEST_HEADER}
|
||||
|
||||
environ = request.environ
|
||||
|
||||
@ -100,6 +103,8 @@ class ContextBase(oslo_ctx.RequestContext):
|
||||
self.tenant_name = tenant_name
|
||||
self.quota_class = quota_class
|
||||
self.read_deleted = read_deleted
|
||||
self.nova_micro_version = kwargs.get('nova_micro_version',
|
||||
constants.NOVA_APIGW_MIN_VERSION)
|
||||
|
||||
def _get_read_deleted(self):
|
||||
return self._read_deleted
|
||||
|
@ -215,7 +215,7 @@ class NovaResourceHandle(ResourceHandle):
|
||||
'server_volume': ACTION}
|
||||
|
||||
def _get_client(self, cxt):
|
||||
cli = n_client.Client(api_versions.APIVersion('2.1'),
|
||||
cli = n_client.Client(api_versions.APIVersion(cxt.nova_micro_version),
|
||||
auth_token=cxt.auth_token,
|
||||
auth_url=self.auth_url,
|
||||
timeout=cfg.CONF.client.nova_timeout)
|
||||
|
@ -18,7 +18,9 @@ from oslo_config import cfg
|
||||
|
||||
from tricircle.common.i18n import _
|
||||
from tricircle.common import restapp
|
||||
from tricircle.nova_apigw.controllers import micro_versions
|
||||
from tricircle.nova_apigw.controllers import root
|
||||
from tricircle.nova_apigw.controllers import root_versions
|
||||
|
||||
|
||||
common_opts = [
|
||||
@ -73,4 +75,9 @@ def setup_app(*args, **kwargs):
|
||||
guess_content_type_from_ext=True
|
||||
)
|
||||
|
||||
# get nova api version
|
||||
app = micro_versions.MicroVersion(app)
|
||||
# version can be unauthenticated so it goes outside of auth
|
||||
app = root_versions.Versions(app)
|
||||
|
||||
return app
|
||||
|
120
tricircle/nova_apigw/controllers/micro_versions.py
Normal file
120
tricircle/nova_apigw/controllers/micro_versions.py
Normal file
@ -0,0 +1,120 @@
|
||||
# Copyright 2015 Huawei Technologies Co., Ltd.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# 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.
|
||||
|
||||
from novaclient import api_versions
|
||||
from novaclient import exceptions
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_service import wsgi
|
||||
from oslo_utils import encodeutils
|
||||
|
||||
import webob.dec
|
||||
|
||||
from tricircle.common import constants
|
||||
|
||||
|
||||
class MicroVersion(object):
|
||||
|
||||
@staticmethod
|
||||
def _format_error(code, message, error_type='computeFault'):
|
||||
return {error_type: {'message': message, 'code': code}}
|
||||
|
||||
@classmethod
|
||||
def factory(cls, global_config, **local_config):
|
||||
return cls(app=None)
|
||||
|
||||
@webob.dec.wsgify(RequestClass=wsgi.Request)
|
||||
def __call__(self, req):
|
||||
"""Get the nova micro version number
|
||||
|
||||
* If neither "X-OpenStack-Nova-API-Version" nor
|
||||
"OpenStack-API-Version" (specifying "compute") is provided,
|
||||
act as if the minimum supported microversion was specified.
|
||||
|
||||
* If both headers are provided,
|
||||
"OpenStack-API-Version" will be preferred.
|
||||
|
||||
* If "X-OpenStack-Nova-API-Version" or "OpenStack-API-Version"
|
||||
is provided, respond with the API at that microversion.
|
||||
If that's outside of the range of microversions supported,
|
||||
return 406 Not Acceptable.
|
||||
|
||||
* If "X-OpenStack-Nova-API-Version" or "OpenStack-API-Version"
|
||||
has a value of "latest" (special keyword),
|
||||
act as if maximum was specified.
|
||||
"""
|
||||
version_num = req.environ.get(
|
||||
constants.HTTP_NOVA_API_VERSION_REQUEST_HEADER)
|
||||
legacy_version_num = req.environ.get(
|
||||
constants.HTTP_LEGACY_NOVA_API_VERSION_REQUEST_HEADER)
|
||||
message = None
|
||||
api_version = None
|
||||
|
||||
if version_num is None and legacy_version_num is None:
|
||||
micro_version = constants.NOVA_APIGW_MIN_VERSION
|
||||
elif version_num is not None:
|
||||
err_msg = ("Invalid format of client version '%s'. "
|
||||
"Expected format 'compute X.Y',"
|
||||
"where X is a major part and Y "
|
||||
"is a minor part of version.") % version_num
|
||||
try:
|
||||
nova_version_prefix = version_num.split()[0]
|
||||
micro_version = ''.join(version_num.split()[1:])
|
||||
if nova_version_prefix != 'compute':
|
||||
message = err_msg
|
||||
except Exception:
|
||||
message = err_msg
|
||||
else:
|
||||
micro_version = legacy_version_num
|
||||
|
||||
if message is None:
|
||||
try:
|
||||
# Returns checked APIVersion object,
|
||||
# or raise UnsupportedVersion exceptions.
|
||||
api_version = api_versions.get_api_version(micro_version)
|
||||
except exceptions.UnsupportedVersion as e:
|
||||
message = e.message
|
||||
|
||||
if message is None and api_version is not None:
|
||||
min_minor = int(constants.NOVA_APIGW_MIN_VERSION.split('.')[1])
|
||||
max_minor = int(constants.NOVA_APIGW_MAX_VERSION.split('.')[1])
|
||||
if api_version.is_latest():
|
||||
micro_version = constants.NOVA_APIGW_MAX_VERSION
|
||||
api_version.ver_minor = max_minor
|
||||
|
||||
if api_version.ver_minor < min_minor or \
|
||||
api_version.ver_minor > max_minor:
|
||||
message = ("Version %s is not supported by the API. "
|
||||
"Minimum is %s, and maximum is %s"
|
||||
% (micro_version, constants.NOVA_APIGW_MIN_VERSION,
|
||||
constants.NOVA_APIGW_MAX_VERSION))
|
||||
|
||||
if message is None:
|
||||
req.environ[constants.NOVA_API_VERSION_REQUEST_HEADER] = \
|
||||
micro_version
|
||||
if self.app:
|
||||
return req.get_response(self.app)
|
||||
else:
|
||||
content_type = 'application/json'
|
||||
body = jsonutils.dumps(
|
||||
self._format_error('406', message, 'computeFault'))
|
||||
response = webob.Response()
|
||||
response.content_type = content_type
|
||||
response.body = encodeutils.to_utf8(body)
|
||||
response.status_code = 406
|
||||
return response
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
@ -23,6 +23,7 @@ import oslo_log.log as logging
|
||||
|
||||
import webob.exc as web_exc
|
||||
|
||||
from tricircle.common import constants
|
||||
from tricircle.common import context as ctx
|
||||
from tricircle.common import xrpcapi
|
||||
from tricircle.nova_apigw.controllers import action
|
||||
@ -54,34 +55,6 @@ class RootController(object):
|
||||
if version == 'v2.1':
|
||||
return V21Controller(), remainder
|
||||
|
||||
@pecan.expose(generic=True, template='json')
|
||||
def index(self):
|
||||
return {
|
||||
"versions": [
|
||||
{
|
||||
"status": "CURRENT",
|
||||
"updated": "2013-07-23T11:33:21Z",
|
||||
"links": [
|
||||
{
|
||||
"href": pecan.request.application_url + "/v2.1/",
|
||||
"rel": "self"
|
||||
}
|
||||
],
|
||||
"min_version": "2.1",
|
||||
"version": "2.12",
|
||||
"id": "v2.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@index.when(method='POST')
|
||||
@index.when(method='PUT')
|
||||
@index.when(method='DELETE')
|
||||
@index.when(method='HEAD')
|
||||
@index.when(method='PATCH')
|
||||
def not_supported(self):
|
||||
pecan.abort(405)
|
||||
|
||||
|
||||
class V21Controller(object):
|
||||
|
||||
@ -154,8 +127,8 @@ class V21Controller(object):
|
||||
"rel": "describedby"
|
||||
}
|
||||
],
|
||||
"min_version": "2.1",
|
||||
"version": "2.12",
|
||||
"min_version": constants.NOVA_APIGW_MIN_VERSION,
|
||||
"version": constants.NOVA_APIGW_MAX_VERSION,
|
||||
"media-types": [
|
||||
{
|
||||
"base": "application/json",
|
||||
@ -172,7 +145,7 @@ class V21Controller(object):
|
||||
@index.when(method='HEAD')
|
||||
@index.when(method='PATCH')
|
||||
def not_supported(self):
|
||||
pecan.abort(405)
|
||||
pecan.abort(404)
|
||||
|
||||
|
||||
class TestRPCController(rest.RestController):
|
||||
|
82
tricircle/nova_apigw/controllers/root_versions.py
Normal file
82
tricircle/nova_apigw/controllers/root_versions.py
Normal file
@ -0,0 +1,82 @@
|
||||
# Copyright 2015 Huawei Technologies Co., Ltd.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# 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.
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_service import wsgi
|
||||
from oslo_utils import encodeutils
|
||||
|
||||
import webob.dec
|
||||
|
||||
from tricircle.common import constants
|
||||
|
||||
|
||||
class Versions(object):
|
||||
|
||||
@classmethod
|
||||
def factory(cls, global_config, **local_config):
|
||||
return cls(app=None)
|
||||
|
||||
@webob.dec.wsgify(RequestClass=wsgi.Request)
|
||||
def __call__(self, req):
|
||||
if req.path != '/':
|
||||
if self.app:
|
||||
return req.get_response(self.app)
|
||||
|
||||
method = req.environ.get('REQUEST_METHOD')
|
||||
not_allowed_methods = ['POST', 'PUT', 'DELETE', 'HEAD', 'PATCH']
|
||||
if method in not_allowed_methods:
|
||||
response = webob.Response()
|
||||
response.status_code = 404
|
||||
return response
|
||||
|
||||
versions = {
|
||||
"versions": [
|
||||
{
|
||||
"status": "SUPPORTED",
|
||||
"updated": "2011-01-21T11:33:21Z",
|
||||
"links": [
|
||||
{"href": "http://127.0.0.1:8774/v2/",
|
||||
"rel": "self"}
|
||||
],
|
||||
"min_version": "",
|
||||
"version": "",
|
||||
"id": "v2.0"
|
||||
},
|
||||
{
|
||||
"status": "CURRENT",
|
||||
"updated": "2013-07-23T11:33:21Z",
|
||||
"links": [
|
||||
{
|
||||
"href": req.application_url + "/v2.1/",
|
||||
"rel": "self"
|
||||
}
|
||||
],
|
||||
"min_version": constants.NOVA_APIGW_MIN_VERSION,
|
||||
"version": constants.NOVA_APIGW_MAX_VERSION,
|
||||
"id": "v2.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
content_type = 'application/json'
|
||||
body = jsonutils.dumps(versions)
|
||||
response = webob.Response()
|
||||
response.content_type = content_type
|
||||
response.body = encodeutils.to_utf8(body)
|
||||
|
||||
return response
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
@ -1,3 +1,62 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
export DEST=$BASE/new
|
||||
export TEMPEST_DIR=$DEST/tempest
|
||||
export TEMPEST_CONF=$TEMPEST_DIR/etc/tempest.conf
|
||||
|
||||
# preparation for the tests
|
||||
cd $TEMPEST_DIR
|
||||
|
||||
# Run functional test
|
||||
echo "Running Tricircle functional test suite..."
|
||||
|
||||
# all test cases with following prefix
|
||||
TESTCASES="(tempest.api.compute.test_versions"
|
||||
#TESTCASES="$TESTCASES|tempest.api.volume.test_volumes_get"
|
||||
# add new test cases like following line for volume_type test
|
||||
# TESTCASES="$TESTCASES|tempest.api.volume.admin.test_volumes_type"
|
||||
TESTCASES="$TESTCASES)"
|
||||
|
||||
ostestr --regex $TESTCASES
|
||||
|
||||
# --------------------- IMPORTANT begin -------------------- #
|
||||
# all following test cases are from Cinder tempest test cases,
|
||||
# the purpose to list them here is to check which test cases
|
||||
# are still not covered and tested in Cinder-APIGW.
|
||||
#
|
||||
# Those test cases which have been covered by ostestr running
|
||||
# above should be marked with **DONE** after the "#".
|
||||
# please leave the length of each line > 80 characters in order
|
||||
# to keep one test case one line.
|
||||
#
|
||||
# When you add new feature to Cinder-APIGW, please select
|
||||
# proper test cases to test against the feature, and marked
|
||||
# these test cases with **DONE** after the "#". For those test
|
||||
# cases which are not needed to be tested in Cinder-APIGW, for
|
||||
# example V1(which has been deprecated) should be marked with
|
||||
# **SKIP** after "#"
|
||||
#
|
||||
# The test cases running through ostestr could be filtered
|
||||
# by regex expression, for example, for Cinder volume type
|
||||
# releated test cases could be executed by a single clause:
|
||||
# ostestr --regex tempest.api.volume.admin.test_volume_types
|
||||
# --------------------- IMPORTANT end -----------------------#
|
||||
|
||||
|
||||
|
||||
# tempest.api.compute.admin.test_agents.AgentsAdminTestJSON.test_create_agent[id-1fc6bdc8-0b6d-4cc7-9f30-9b04fabe5b90]
|
||||
# tempest.api.compute.admin.test_agents.AgentsAdminTestJSON.test_delete_agent[id-470e0b89-386f-407b-91fd-819737d0b335]
|
||||
# tempest.api.compute.admin.test_agents.AgentsAdminTestJSON.test_list_agents[id-6a326c69-654b-438a-80a3-34bcc454e138]
|
||||
@ -494,8 +553,8 @@
|
||||
# tempest.api.compute.test_quotas.QuotasTestJSON.test_get_default_quotas[id-9bfecac7-b966-4f47-913f-1a9e2c12134a]
|
||||
# tempest.api.compute.test_quotas.QuotasTestJSON.test_get_quotas[id-f1ef0a97-dbbb-4cca-adc5-c9fbc4f76107]
|
||||
# tempest.api.compute.test_tenant_networks.ComputeTenantNetworksTest.test_list_show_tenant_networks[id-edfea98e-bbe3-4c7a-9739-87b986baff26,network]
|
||||
# tempest.api.compute.test_versions.TestVersions.test_get_version_details[id-b953a29e-929c-4a8e-81be-ec3a7e03cb76]
|
||||
# tempest.api.compute.test_versions.TestVersions.test_list_api_versions[id-6c0a0990-43b6-4529-9b61-5fd8daf7c55c]
|
||||
# **DONE** tempest.api.compute.test_versions.TestVersions.test_get_version_details[id-b953a29e-929c-4a8e-81be-ec3a7e03cb76]
|
||||
# **DONE** tempest.api.compute.test_versions.TestVersions.test_list_api_versions[id-6c0a0990-43b6-4529-9b61-5fd8daf7c55c]
|
||||
# tempest.api.compute.volumes.test_attach_volume.AttachVolumeShelveTestJSON.test_attach_detach_volume[id-52e9045a-e90d-4c0d-9087-79d657faffff]
|
||||
# tempest.api.compute.volumes.test_attach_volume.AttachVolumeShelveTestJSON.test_attach_volume_shelved_or_offload_server[id-13a940b6-3474-4c3c-b03f-29b89112bfee]
|
||||
# tempest.api.compute.volumes.test_attach_volume.AttachVolumeShelveTestJSON.test_detach_volume_shelved_or_offload_server[id-b54e86dd-a070-49c4-9c07-59ae6dae15aa]
|
||||
|
@ -0,0 +1,447 @@
|
||||
# Copyright (c) 2015 Huawei Tech. Co., Ltd.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
from novaclient import api_versions
|
||||
from novaclient.v2 import client as n_client
|
||||
import pecan
|
||||
from pecan.configuration import set_config
|
||||
from pecan.testing import load_test_app
|
||||
|
||||
from tricircle.common import constants
|
||||
from tricircle.common import constants as cons
|
||||
from tricircle.common import context
|
||||
from tricircle.common import resource_handle
|
||||
from tricircle.db import api as db_api
|
||||
from tricircle.db import core
|
||||
from tricircle.nova_apigw import app
|
||||
from tricircle.nova_apigw.controllers import server
|
||||
from tricircle.tests import base
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_config import fixture as fixture_config
|
||||
|
||||
FAKE_AZ = 'fake_az'
|
||||
|
||||
|
||||
def get_tricircle_client(self, pod):
|
||||
return FakeTricircleClient()
|
||||
|
||||
|
||||
class FakeTricircleClient(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def list_servers(self, cxt, filters=None):
|
||||
handle = FakeNovaAPIGWResourceHandle()
|
||||
return handle.handle_list(cxt, 'server', filters)
|
||||
|
||||
|
||||
class FakeNovaAPIGWResourceHandle(resource_handle.NovaResourceHandle):
|
||||
def __init__(self):
|
||||
self.auth_url = 'auth_url'
|
||||
self.endpoint_url = 'endpoint_url'
|
||||
|
||||
def handle_list(self, cxt, resource, filters):
|
||||
super(FakeNovaAPIGWResourceHandle, self).handle_list(
|
||||
cxt, resource, filters)
|
||||
return []
|
||||
|
||||
|
||||
class FakeNovaClient(object):
|
||||
def __init__(self):
|
||||
self.servers = FakeNovaServer()
|
||||
|
||||
def set_management_url(self, url):
|
||||
pass
|
||||
|
||||
|
||||
class FakeNovaServer(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def list(self, detailed=True, search_opts=None, marker=None, limit=None,
|
||||
sort_keys=None, sort_dirs=None):
|
||||
return []
|
||||
|
||||
|
||||
class MicroVersionFunctionTest(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(MicroVersionFunctionTest, self).setUp()
|
||||
|
||||
self.addCleanup(set_config, {}, overwrite=True)
|
||||
|
||||
cfg.CONF.register_opts(app.common_opts)
|
||||
|
||||
self.CONF = self.useFixture(fixture_config.Config()).conf
|
||||
|
||||
self.CONF.set_override('auth_strategy', 'noauth')
|
||||
self.CONF.set_override('tricircle_db_connection', 'sqlite:///:memory:')
|
||||
|
||||
core.initialize()
|
||||
core.ModelBase.metadata.create_all(core.get_engine())
|
||||
|
||||
self.app = self._make_app()
|
||||
|
||||
self._init_db()
|
||||
|
||||
def _make_app(self, enable_acl=False):
|
||||
self.config = {
|
||||
'app': {
|
||||
'root': 'tricircle.nova_apigw.controllers.root.RootController',
|
||||
'modules': ['tricircle.nova_apigw'],
|
||||
'enable_acl': enable_acl,
|
||||
'errors': {
|
||||
400: '/error',
|
||||
'__force_dict__': True
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return load_test_app(self.config)
|
||||
|
||||
def _init_db(self):
|
||||
core.initialize()
|
||||
core.ModelBase.metadata.create_all(core.get_engine())
|
||||
# enforce foreign key constraint for sqlite
|
||||
core.get_engine().execute('pragma foreign_keys=on')
|
||||
self.context = context.Context()
|
||||
|
||||
pod_dict = {
|
||||
'pod_id': 'fake_pod_id',
|
||||
'pod_name': 'fake_pod_name',
|
||||
'az_name': FAKE_AZ
|
||||
}
|
||||
|
||||
config_dict = {
|
||||
'service_id': 'fake_service_id',
|
||||
'pod_id': 'fake_pod_id',
|
||||
'service_type': cons.ST_NOVA,
|
||||
'service_url': 'http://127.0.0.1:8774/v2/$(tenant_id)s'
|
||||
}
|
||||
|
||||
pod_dict2 = {
|
||||
'pod_id': 'fake_pod_id' + '2',
|
||||
'pod_name': 'fake_pod_name' + '2',
|
||||
'az_name': FAKE_AZ + '2'
|
||||
}
|
||||
|
||||
config_dict2 = {
|
||||
'service_id': 'fake_service_id' + '2',
|
||||
'pod_id': 'fake_pod_id' + '2',
|
||||
'service_type': cons.ST_CINDER,
|
||||
'service_url': 'http://10.0.0.2:8774/v2/$(tenant_id)s'
|
||||
}
|
||||
|
||||
top_pod = {
|
||||
'pod_id': 'fake_top_pod_id',
|
||||
'pod_name': 'RegionOne',
|
||||
'az_name': ''
|
||||
}
|
||||
|
||||
top_config = {
|
||||
'service_id': 'fake_top_service_id',
|
||||
'pod_id': 'fake_top_pod_id',
|
||||
'service_type': cons.ST_CINDER,
|
||||
'service_url': 'http://127.0.0.1:19998/v2/$(tenant_id)s'
|
||||
}
|
||||
|
||||
db_api.create_pod(self.context, pod_dict)
|
||||
db_api.create_pod(self.context, pod_dict2)
|
||||
db_api.create_pod(self.context, top_pod)
|
||||
db_api.create_pod_service_configuration(self.context, config_dict)
|
||||
db_api.create_pod_service_configuration(self.context, config_dict2)
|
||||
db_api.create_pod_service_configuration(self.context, top_config)
|
||||
|
||||
def tearDown(self):
|
||||
super(MicroVersionFunctionTest, self).tearDown()
|
||||
cfg.CONF.unregister_opts(app.common_opts)
|
||||
pecan.set_config({}, overwrite=True)
|
||||
core.ModelBase.metadata.drop_all(core.get_engine())
|
||||
|
||||
|
||||
class MicroversionsTest(MicroVersionFunctionTest):
|
||||
|
||||
min_version = constants.NOVA_APIGW_MIN_VERSION
|
||||
max_version = 'compute %s' % constants.NOVA_APIGW_MAX_VERSION
|
||||
lower_boundary = str(float(constants.NOVA_APIGW_MIN_VERSION) - 0.1)
|
||||
upper_boundary = 'compute %s' % str(
|
||||
float(constants.NOVA_APIGW_MAX_VERSION) + 0.1)
|
||||
vaild_version = 'compute 2.30'
|
||||
vaild_leagcy_version = '2.5'
|
||||
invaild_major = 'compute a.2'
|
||||
invaild_minor = 'compute 2.a'
|
||||
latest_version = 'compute 2.latest'
|
||||
invaild_compute_format = 'compute2.30'
|
||||
only_major = '2'
|
||||
invaild_major2 = '1.5'
|
||||
invaild_major3 = 'compute 3.2'
|
||||
invaild_version = '2.30'
|
||||
invaild_leagecy_version = 'compute 2.5'
|
||||
invaild_version2 = 'aa 2.30'
|
||||
invaild_version3 = 'compute 2.30 2.31'
|
||||
invaild_version4 = 'acompute 2.30'
|
||||
|
||||
tenant_id = 'tenant_id'
|
||||
|
||||
def _make_headers(self, version, type='current'):
|
||||
headers = {}
|
||||
headers['X_TENANT_ID'] = self.tenant_id
|
||||
if version is None:
|
||||
type = 'leagecy'
|
||||
version = constants.NOVA_APIGW_MIN_VERSION
|
||||
|
||||
if type == 'both':
|
||||
headers[constants.NOVA_API_VERSION_REQUEST_HEADER] = version
|
||||
headers[constants.LEGACY_NOVA_API_VERSION_REQUEST_HEADER] = '2.5'
|
||||
elif type == 'current':
|
||||
headers[constants.NOVA_API_VERSION_REQUEST_HEADER] = version
|
||||
else:
|
||||
headers[constants.LEGACY_NOVA_API_VERSION_REQUEST_HEADER] = version
|
||||
|
||||
return headers
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_no_header(self, mock_client):
|
||||
headers = self._make_headers(None)
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
self.app.get(url, headers=headers)
|
||||
mock_client.assert_called_with(
|
||||
api_version=api_versions.APIVersion(
|
||||
constants.NOVA_APIGW_MIN_VERSION),
|
||||
auth_token=None, auth_url='auth_url',
|
||||
direct_use=False, project_id=None,
|
||||
timeout=60, username=None, api_key=None)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_vaild_version(self, mock_client):
|
||||
headers = self._make_headers(self.vaild_version)
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
self.app.get(url, headers=headers)
|
||||
mock_client.assert_called_with(
|
||||
api_version=api_versions.APIVersion(self.vaild_version.split()[1]),
|
||||
auth_token=None, auth_url='auth_url',
|
||||
direct_use=False, project_id=None,
|
||||
timeout=60, username=None, api_key=None)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_vaild_leagcy_version(self, mock_client):
|
||||
headers = self._make_headers(self.vaild_leagcy_version, 'leagcy')
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
self.app.get(url, headers=headers)
|
||||
mock_client.assert_called_with(
|
||||
api_version=api_versions.APIVersion(self.vaild_leagcy_version),
|
||||
auth_token=None, auth_url='auth_url',
|
||||
direct_use=False, project_id=None,
|
||||
timeout=60, username=None, api_key=None)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_latest_version(self, mock_client):
|
||||
headers = self._make_headers(self.latest_version)
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
self.app.get(url, headers=headers)
|
||||
mock_client.assert_called_with(
|
||||
api_version=api_versions.APIVersion(
|
||||
constants.NOVA_APIGW_MAX_VERSION),
|
||||
auth_token=None, auth_url='auth_url',
|
||||
direct_use=False, project_id=None,
|
||||
timeout=60, username=None, api_key=None)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_min_version(self, mock_client):
|
||||
headers = self._make_headers(self.min_version, 'leagecy')
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
self.app.get(url, headers=headers)
|
||||
mock_client.assert_called_with(
|
||||
api_version=api_versions.APIVersion(self.min_version),
|
||||
auth_token=None, auth_url='auth_url',
|
||||
direct_use=False, project_id=None,
|
||||
timeout=60, username=None, api_key=None)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_max_version(self, mock_client):
|
||||
headers = self._make_headers(self.max_version)
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
self.app.get(url, headers=headers)
|
||||
mock_client.assert_called_with(
|
||||
api_version=api_versions.APIVersion(self.max_version.split()[1]),
|
||||
auth_token=None, auth_url='auth_url',
|
||||
direct_use=False, project_id=None,
|
||||
timeout=60, username=None, api_key=None)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_invaild_major(self, mock_client):
|
||||
headers = self._make_headers(self.invaild_major)
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
res = self.app.get(url, headers=headers, expect_errors=True)
|
||||
self.assertEqual(406, res.status_int)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_invaild_major2(self, mock_client):
|
||||
headers = self._make_headers(self.invaild_major2, 'leagecy')
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
res = self.app.get(url, headers=headers, expect_errors=True)
|
||||
self.assertEqual(406, res.status_int)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_invaild_major3(self, mock_client):
|
||||
headers = self._make_headers(self.invaild_major3)
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
res = self.app.get(url, headers=headers, expect_errors=True)
|
||||
self.assertEqual(406, res.status_int)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_invaild_minor(self, mock_client):
|
||||
headers = self._make_headers(self.invaild_minor)
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
res = self.app.get(url, headers=headers, expect_errors=True)
|
||||
self.assertEqual(406, res.status_int)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_lower_boundary(self, mock_client):
|
||||
headers = self._make_headers(self.lower_boundary)
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
res = self.app.get(url, headers=headers, expect_errors=True)
|
||||
self.assertEqual(406, res.status_int)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_upper_boundary(self, mock_client):
|
||||
headers = self._make_headers(self.upper_boundary)
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
res = self.app.get(url, headers=headers, expect_errors=True)
|
||||
self.assertEqual(406, res.status_int)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_invaild_compute_format(self, mock_client):
|
||||
headers = self._make_headers(self.invaild_compute_format)
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
res = self.app.get(url, headers=headers, expect_errors=True)
|
||||
self.assertEqual(406, res.status_int)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_only_major(self, mock_client):
|
||||
headers = self._make_headers(self.only_major, 'leagecy')
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
res = self.app.get(url, headers=headers, expect_errors=True)
|
||||
self.assertEqual(406, res.status_int)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_invaild_version(self, mock_client):
|
||||
headers = self._make_headers(self.invaild_version)
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
res = self.app.get(url, headers=headers, expect_errors=True)
|
||||
self.assertEqual(406, res.status_int)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_invaild_leagecy_version(self, mock_client):
|
||||
headers = self._make_headers(self.invaild_leagecy_version, 'leagecy')
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
res = self.app.get(url, headers=headers, expect_errors=True)
|
||||
self.assertEqual(406, res.status_int)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_both_version(self, mock_client):
|
||||
headers = self._make_headers(self.vaild_version, 'both')
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
self.app.get(url, headers=headers, expect_errors=True)
|
||||
# The new format microversion priority to leagecy
|
||||
mock_client.assert_called_with(
|
||||
api_version=api_versions.APIVersion(self.vaild_version.split()[1]),
|
||||
auth_token=None, auth_url='auth_url',
|
||||
direct_use=False, project_id=None,
|
||||
timeout=60, username=None, api_key=None)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_invaild_version2(self, mock_client):
|
||||
headers = self._make_headers(self.invaild_version2)
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
res = self.app.get(url, headers=headers, expect_errors=True)
|
||||
self.assertEqual(406, res.status_int)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_invaild_version3(self, mock_client):
|
||||
headers = self._make_headers(self.invaild_version3)
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
res = self.app.get(url, headers=headers, expect_errors=True)
|
||||
self.assertEqual(406, res.status_int)
|
||||
|
||||
@mock.patch.object(server.ServerController, '_get_client',
|
||||
new=get_tricircle_client)
|
||||
@mock.patch.object(n_client, 'Client')
|
||||
def test_microversions_invaild_version4(self, mock_client):
|
||||
headers = self._make_headers(self.invaild_version4)
|
||||
url = '/v2.1/' + self.tenant_id + '/servers/detail'
|
||||
mock_client.return_value = FakeNovaClient()
|
||||
res = self.app.get(url, headers=headers, expect_errors=True)
|
||||
self.assertEqual(406, res.status_int)
|
@ -74,29 +74,30 @@ class TestRootController(Nova_API_GW_FunctionalTest):
|
||||
self.assertEqual(response.status_int, 200)
|
||||
json_body = jsonutils.loads(response.body)
|
||||
versions = json_body.get('versions')
|
||||
self.assertEqual(1, len(versions))
|
||||
self.assertEqual(versions[0]["min_version"], "2.1")
|
||||
self.assertEqual(versions[0]["id"], "v2.1")
|
||||
self.assertEqual(2, len(versions))
|
||||
self.assertEqual(versions[0]["id"], "v2.0")
|
||||
self.assertEqual(versions[1]["min_version"], "2.1")
|
||||
self.assertEqual(versions[1]["id"], "v2.1")
|
||||
|
||||
def _test_method_returns_405(self, method):
|
||||
def _test_method_returns_404(self, method):
|
||||
api_method = getattr(self.app, method)
|
||||
response = api_method('/', expect_errors=True)
|
||||
self.assertEqual(response.status_int, 405)
|
||||
self.assertEqual(response.status_int, 404)
|
||||
|
||||
def test_post(self):
|
||||
self._test_method_returns_405('post')
|
||||
self._test_method_returns_404('post')
|
||||
|
||||
def test_put(self):
|
||||
self._test_method_returns_405('put')
|
||||
self._test_method_returns_404('put')
|
||||
|
||||
def test_patch(self):
|
||||
self._test_method_returns_405('patch')
|
||||
self._test_method_returns_404('patch')
|
||||
|
||||
def test_delete(self):
|
||||
self._test_method_returns_405('delete')
|
||||
self._test_method_returns_404('delete')
|
||||
|
||||
def test_head(self):
|
||||
self._test_method_returns_405('head')
|
||||
self._test_method_returns_404('head')
|
||||
|
||||
|
||||
class TestV21Controller(Nova_API_GW_FunctionalTest):
|
||||
@ -109,25 +110,25 @@ class TestV21Controller(Nova_API_GW_FunctionalTest):
|
||||
self.assertEqual(version["min_version"], "2.1")
|
||||
self.assertEqual(version["id"], "v2.1")
|
||||
|
||||
def _test_method_returns_405(self, method):
|
||||
def _test_method_returns_404(self, method):
|
||||
api_method = getattr(self.app, method)
|
||||
response = api_method('/v2.1', expect_errors=True)
|
||||
self.assertEqual(response.status_int, 405)
|
||||
response = api_method('/', expect_errors=True)
|
||||
self.assertEqual(response.status_int, 404)
|
||||
|
||||
def test_post(self):
|
||||
self._test_method_returns_405('post')
|
||||
self._test_method_returns_404('post')
|
||||
|
||||
def test_put(self):
|
||||
self._test_method_returns_405('put')
|
||||
self._test_method_returns_404('put')
|
||||
|
||||
def test_patch(self):
|
||||
self._test_method_returns_405('patch')
|
||||
self._test_method_returns_404('patch')
|
||||
|
||||
def test_delete(self):
|
||||
self._test_method_returns_405('delete')
|
||||
self._test_method_returns_404('delete')
|
||||
|
||||
def test_head(self):
|
||||
self._test_method_returns_405('head')
|
||||
self._test_method_returns_404('head')
|
||||
|
||||
|
||||
class TestErrors(Nova_API_GW_FunctionalTest):
|
||||
@ -145,7 +146,7 @@ class TestErrors(Nova_API_GW_FunctionalTest):
|
||||
class TestRequestID(Nova_API_GW_FunctionalTest):
|
||||
|
||||
def test_request_id(self):
|
||||
response = self.app.get('/')
|
||||
response = self.app.get('/v2.1/')
|
||||
self.assertIn('x-openstack-request-id', response.headers)
|
||||
self.assertTrue(
|
||||
response.headers['x-openstack-request-id'].startswith('req-'))
|
||||
@ -169,5 +170,5 @@ class TestKeystoneAuth(Nova_API_GW_FunctionalTest):
|
||||
self.app = self._make_app()
|
||||
|
||||
def test_auth_enforced(self):
|
||||
response = self.app.get('/', expect_errors=True)
|
||||
response = self.app.get('/v2.1/', expect_errors=True)
|
||||
self.assertEqual(response.status_int, 401)
|
||||
|
Loading…
Reference in New Issue
Block a user