Use oslo_middleware to support CORS configuration
Currently there is no way in Qinling configuration file to configure the CORS. oslo_middleware should be implemented to be able to set [cors] section in qinling.conf. Change-Id: Ib4664ba7cbda28a9dfda1c1dd9f0e7457c0ee7de Story: 2005788 Task: 33515
This commit is contained in:
parent
02b957f92e
commit
e18f80345a
|
@ -1,7 +1,10 @@
|
||||||
# The order of packages is significant, because pip processes them in the order
|
# The order of packages is significant, because pip processes them in the order
|
||||||
# of appearance. Changing the order has an impact on the overall integration
|
# of appearance. Changing the order has an impact on the overall integration
|
||||||
# process, which may cause wedges in the gate later.
|
# process, which may cause wedges in the gate later.
|
||||||
|
# this is required for the docs build jobs
|
||||||
sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
|
sphinx>=1.6.2,<2.0.0;python_version=='2.7' # BSD
|
||||||
openstackdocstheme>=1.24.0 # Apache-2.0
|
sphinx>=1.6.2;python_version>='3.4' # BSD
|
||||||
reno>=2.5.0 # Apache-2.0
|
sphinxcontrib-apidoc>=0.2.0 # BSD
|
||||||
|
openstackdocstheme>=1.11.0 # Apache-2.0
|
||||||
|
reno>=1.8.0 # Apache-2.0
|
||||||
|
os-api-ref>=1.0.0 # Apache-2.0
|
||||||
|
|
|
@ -67,6 +67,7 @@ oslo.serialization==2.18.0
|
||||||
oslo.service==1.24.0
|
oslo.service==1.24.0
|
||||||
oslo.upgradecheck==0.1.0
|
oslo.upgradecheck==0.1.0
|
||||||
oslo.utils==3.33.0
|
oslo.utils==3.33.0
|
||||||
|
oslosphinx==4.7.0
|
||||||
oslotest==3.2.0
|
oslotest==3.2.0
|
||||||
paramiko==2.4.1
|
paramiko==2.4.1
|
||||||
Paste==2.0.3
|
Paste==2.0.3
|
||||||
|
@ -106,6 +107,10 @@ setproctitle==1.1.10
|
||||||
setuptools==21.0.0
|
setuptools==21.0.0
|
||||||
simplegeneric==0.8.1
|
simplegeneric==0.8.1
|
||||||
six==1.10.0
|
six==1.10.0
|
||||||
|
Sphinx==1.6.2
|
||||||
|
sphinxcontrib-httpdomain==1.3.0
|
||||||
|
sphinxcontrib-pecanwsme==0.10.0
|
||||||
|
sphinxcontrib-websupport==1.0.1
|
||||||
SQLAlchemy==1.0.10
|
SQLAlchemy==1.0.10
|
||||||
sqlalchemy-migrate==0.11.0
|
sqlalchemy-migrate==0.11.0
|
||||||
sqlparse==0.2.4
|
sqlparse==0.2.4
|
||||||
|
|
|
@ -14,9 +14,12 @@
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
import oslo_middleware.cors as cors_middleware
|
||||||
|
import oslo_middleware.http_proxy_to_wsgi as http_proxy_to_wsgi_middleware
|
||||||
import pecan
|
import pecan
|
||||||
|
|
||||||
from qinling.api import access_control
|
from qinling.api import access_control
|
||||||
|
from qinling import config as q_config
|
||||||
from qinling import context as ctx
|
from qinling import context as ctx
|
||||||
from qinling.db import api as db_api
|
from qinling.db import api as db_api
|
||||||
from qinling.services import periodics
|
from qinling.services import periodics
|
||||||
|
@ -43,6 +46,9 @@ def get_pecan_config():
|
||||||
def setup_app(config=None):
|
def setup_app(config=None):
|
||||||
if not config:
|
if not config:
|
||||||
config = get_pecan_config()
|
config = get_pecan_config()
|
||||||
|
|
||||||
|
q_config.set_config_defaults()
|
||||||
|
|
||||||
app_conf = dict(config.app)
|
app_conf = dict(config.app)
|
||||||
|
|
||||||
db_api.setup_db()
|
db_api.setup_db()
|
||||||
|
@ -61,4 +67,18 @@ def setup_app(config=None):
|
||||||
# Set up access control.
|
# Set up access control.
|
||||||
app = access_control.setup(app)
|
app = access_control.setup(app)
|
||||||
|
|
||||||
return app
|
# Create HTTPProxyToWSGI wrapper
|
||||||
|
app = http_proxy_to_wsgi_middleware.HTTPProxyToWSGI(app, cfg.CONF)
|
||||||
|
|
||||||
|
# Create a CORS wrapper, and attach mistral-specific defaults that must be
|
||||||
|
# included in all CORS responses.
|
||||||
|
return cors_middleware.CORS(app, cfg.CONF)
|
||||||
|
|
||||||
|
|
||||||
|
def init_wsgi():
|
||||||
|
# By default, oslo.config parses the CLI args if no args is provided.
|
||||||
|
# As a result, invoking this wsgi script from gunicorn leads to the error
|
||||||
|
# with argparse complaining that the CLI options have already been parsed.
|
||||||
|
q_config.parse_args(args=[])
|
||||||
|
|
||||||
|
return setup_app()
|
||||||
|
|
|
@ -67,7 +67,7 @@ class RootController(object):
|
||||||
def index(self):
|
def index(self):
|
||||||
LOG.info("Fetching API versions.")
|
LOG.info("Fetching API versions.")
|
||||||
|
|
||||||
host_url_v1 = '%s/%s' % (pecan.request.host_url, 'v1')
|
host_url_v1 = '%s/%s' % (pecan.request.application_url, 'v1')
|
||||||
api_v1 = APIVersion(
|
api_v1 = APIVersion(
|
||||||
id='v1.0',
|
id='v1.0',
|
||||||
status='CURRENT',
|
status='CURRENT',
|
||||||
|
|
|
@ -43,4 +43,5 @@ class Controller(object):
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(RootResource)
|
@wsme_pecan.wsexpose(RootResource)
|
||||||
def index(self):
|
def index(self):
|
||||||
return RootResource(uri='%s/%s' % (pecan.request.host_url, 'v1'))
|
return RootResource(uri='%s/%s' % (pecan.request.application_url,
|
||||||
|
'v1'))
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Copyright 2019 - Ormuco, Inc.
|
||||||
|
#
|
||||||
|
# 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 qinling.api import app
|
||||||
|
|
||||||
|
application = app.init_wsgi()
|
|
@ -16,6 +16,7 @@ from keystonemiddleware import auth_token
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
from oslo_middleware import cors
|
||||||
|
|
||||||
from qinling import version
|
from qinling import version
|
||||||
|
|
||||||
|
@ -335,3 +336,34 @@ def parse_args(args=None, usage=None, default_config_files=None):
|
||||||
usage=usage,
|
usage=usage,
|
||||||
default_config_files=default_config_files
|
default_config_files=default_config_files
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def set_config_defaults():
|
||||||
|
"""This method updates all configuration default values."""
|
||||||
|
set_cors_middleware_defaults()
|
||||||
|
|
||||||
|
|
||||||
|
def set_cors_middleware_defaults():
|
||||||
|
"""Update default configuration options for oslo.middleware."""
|
||||||
|
cors.set_defaults(
|
||||||
|
allow_headers=['X-Auth-Token',
|
||||||
|
'X-Identity-Status',
|
||||||
|
'X-Roles',
|
||||||
|
'X-Service-Catalog',
|
||||||
|
'X-User-Id',
|
||||||
|
'X-Tenant-Id',
|
||||||
|
'X-Project-Id',
|
||||||
|
'X-User-Name',
|
||||||
|
'X-Project-Name'],
|
||||||
|
allow_methods=['GET',
|
||||||
|
'PUT',
|
||||||
|
'POST',
|
||||||
|
'DELETE',
|
||||||
|
'PATCH'],
|
||||||
|
expose_headers=['X-Auth-Token',
|
||||||
|
'X-Subject-Token',
|
||||||
|
'X-Service-Token',
|
||||||
|
'X-Project-Id',
|
||||||
|
'X-User-Name',
|
||||||
|
'X-Project-Name']
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Tests cors middleware."""
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_middleware import cors as cors_middleware
|
||||||
|
from qinling.tests.unit.api import base
|
||||||
|
|
||||||
|
|
||||||
|
class TestCORSMiddleware(base.APITest):
|
||||||
|
"""Provide a basic smoke test to ensure CORS middleware is active.
|
||||||
|
|
||||||
|
The tests below provide minimal confirmation that the CORS middleware
|
||||||
|
is active, and may be configured. For comprehensive tests, please consult
|
||||||
|
the test suite in oslo_middleware.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# Make sure the CORS options are registered
|
||||||
|
cfg.CONF.register_opts(cors_middleware.CORS_OPTS, 'cors')
|
||||||
|
|
||||||
|
# Load up our valid domain values before the application is created.
|
||||||
|
self.override_config(
|
||||||
|
"allowed_origin",
|
||||||
|
"http://valid.example.com",
|
||||||
|
group='cors'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create the application.
|
||||||
|
super(TestCORSMiddleware, self).setUp()
|
||||||
|
|
||||||
|
def test_valid_cors_options_request(self):
|
||||||
|
response = self.app.options(
|
||||||
|
'/',
|
||||||
|
headers={
|
||||||
|
'Origin': 'http://valid.example.com',
|
||||||
|
'Access-Control-Request-Method': 'GET'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(200, response.status_code)
|
||||||
|
self.assertIn('access-control-allow-origin', response.headers)
|
||||||
|
self.assertEqual(
|
||||||
|
'http://valid.example.com',
|
||||||
|
response.headers['access-control-allow-origin']
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_invalid_cors_options_request(self):
|
||||||
|
response = self.app.options(
|
||||||
|
'/',
|
||||||
|
headers={
|
||||||
|
'Origin': 'http://invalid.example.com',
|
||||||
|
'Access-Control-Request-Method': 'GET'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(200, response.status_code)
|
||||||
|
self.assertNotIn('access-control-allow-origin', response.headers)
|
||||||
|
|
||||||
|
def test_valid_cors_get_request(self):
|
||||||
|
response = self.app.get(
|
||||||
|
'/',
|
||||||
|
headers={
|
||||||
|
'Origin': 'http://valid.example.com'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(200, response.status_code)
|
||||||
|
self.assertIn('access-control-allow-origin', response.headers)
|
||||||
|
self.assertEqual(
|
||||||
|
'http://valid.example.com',
|
||||||
|
response.headers['access-control-allow-origin']
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_invalid_cors_get_request(self):
|
||||||
|
response = self.app.get(
|
||||||
|
'/',
|
||||||
|
headers={
|
||||||
|
'Origin': 'http://invalid.example.com'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(200, response.status_code)
|
||||||
|
self.assertNotIn('access-control-allow-origin', response.headers)
|
|
@ -0,0 +1,42 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Tests http_proxy_to_wsgi middleware."""
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_middleware import http_proxy_to_wsgi as http_proxy_to_wsgi_middleware
|
||||||
|
from qinling.tests.unit.api import base
|
||||||
|
|
||||||
|
|
||||||
|
class TestHTTPProxyToWSGIMiddleware(base.APITest):
|
||||||
|
"""Test oslo_middleware HTTPProxyToWSGI.
|
||||||
|
|
||||||
|
It checks that oslo_middleware middleware HTTPProxyToWSGI is executed
|
||||||
|
when enabled.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# Make sure the HTTPProxyToWSGI options are registered
|
||||||
|
cfg.CONF.register_opts(http_proxy_to_wsgi_middleware.OPTS,
|
||||||
|
'oslo_middleware')
|
||||||
|
|
||||||
|
# Enable proxy headers parsing in HTTPProxyToWSGI middleware.
|
||||||
|
self.override_config(
|
||||||
|
"enable_proxy_headers_parsing",
|
||||||
|
"True",
|
||||||
|
group='oslo_middleware'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create the application.
|
||||||
|
super(TestHTTPProxyToWSGIMiddleware, self).setUp()
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Add CORS and HTTPProxyToWSGI support based on oslo_middleware in front
|
||||||
|
of the Qinling API. The purpose of this middleware is to set up the
|
||||||
|
request URL correctly in the case there is a proxy (for instance, a
|
||||||
|
loadbalancer such as HAProxy) in front of the Qinling API.
|
||||||
|
The HTTPProxyToWSGI is off by default and needs to be enabled via a
|
||||||
|
configuration value.
|
|
@ -10,6 +10,7 @@ oslo.concurrency>=3.26.0 # Apache-2.0
|
||||||
oslo.config>=5.2.0 # Apache-2.0
|
oslo.config>=5.2.0 # Apache-2.0
|
||||||
oslo.db>=4.27.0 # Apache-2.0
|
oslo.db>=4.27.0 # Apache-2.0
|
||||||
oslo.messaging>=5.29.0 # Apache-2.0
|
oslo.messaging>=5.29.0 # Apache-2.0
|
||||||
|
oslo.middleware>=3.35.0 # Apache-2.0
|
||||||
oslo.policy>=1.30.0 # Apache-2.0
|
oslo.policy>=1.30.0 # Apache-2.0
|
||||||
oslo.upgradecheck>=0.1.0 # Apache-2.0
|
oslo.upgradecheck>=0.1.0 # Apache-2.0
|
||||||
oslo.utils>=3.33.0 # Apache-2.0
|
oslo.utils>=3.33.0 # Apache-2.0
|
||||||
|
|
|
@ -30,8 +30,11 @@ console_scripts =
|
||||||
qinling-db-manage = qinling.db.sqlalchemy.migration.cli:main
|
qinling-db-manage = qinling.db.sqlalchemy.migration.cli:main
|
||||||
qinling-status = qinling.cmd.status:main
|
qinling-status = qinling.cmd.status:main
|
||||||
|
|
||||||
|
wsgi_scripts =
|
||||||
|
qinling-wsgi-api = qinling.api.app:init_wsgi
|
||||||
|
|
||||||
qinling.storage.provider:
|
qinling.storage.provider:
|
||||||
local = qinling.storage.file_system:FileSystemStorage
|
local = qinling.storage.file_system:FileSystemStorage
|
||||||
|
|
||||||
qinling.orchestrator =
|
qinling.orchestrator =
|
||||||
kubernetes = qinling.orchestrator.kubernetes.manager:KubernetesManager
|
kubernetes = qinling.orchestrator.kubernetes.manager:KubernetesManager
|
||||||
|
@ -39,6 +42,9 @@ qinling.orchestrator =
|
||||||
oslo.config.opts =
|
oslo.config.opts =
|
||||||
qinling.config = qinling.config:list_opts
|
qinling.config = qinling.config:list_opts
|
||||||
|
|
||||||
|
oslo.config.opts.defaults =
|
||||||
|
qinling.config = qinling.config:set_cors_middleware_defaults
|
||||||
|
|
||||||
tempest.test_plugins =
|
tempest.test_plugins =
|
||||||
qinling_test = qinling_tempest_plugin.plugin:QinlingTempestPlugin
|
qinling_test = qinling_tempest_plugin.plugin:QinlingTempestPlugin
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
namespace = qinling.config
|
namespace = qinling.config
|
||||||
namespace = keystonemiddleware.auth_token
|
namespace = keystonemiddleware.auth_token
|
||||||
namespace = oslo.messaging
|
namespace = oslo.messaging
|
||||||
|
namespace = oslo.middleware.cors
|
||||||
|
namespace = oslo.middleware.http_proxy_to_wsgi
|
||||||
namespace = oslo.log
|
namespace = oslo.log
|
||||||
namespace = oslo.policy
|
namespace = oslo.policy
|
||||||
namespace = oslo.db
|
namespace = oslo.db
|
||||||
|
|
Loading…
Reference in New Issue