Browse Source

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
changes/51/661851/8
Gaetan Trellu 2 years ago
parent
commit
e18f80345a
13 changed files with 242 additions and 8 deletions
  1. +7
    -4
      doc/requirements.txt
  2. +5
    -0
      lower-constraints.txt
  3. +21
    -1
      qinling/api/app.py
  4. +1
    -1
      qinling/api/controllers/root.py
  5. +2
    -1
      qinling/api/controllers/v1/root.py
  6. +17
    -0
      qinling/api/wsgi.py
  7. +32
    -0
      qinling/config.py
  8. +96
    -0
      qinling/tests/unit/api/test_cors_middleware.py
  9. +42
    -0
      qinling/tests/unit/api/test_oslo_middleware.py
  10. +9
    -0
      releasenotes/notes/add-cors-support-ce060ee8513a1acf.yaml
  11. +1
    -0
      requirements.txt
  12. +7
    -1
      setup.cfg
  13. +2
    -0
      tools/config/config-generator.qinling.conf

+ 7
- 4
doc/requirements.txt View File

@ -1,7 +1,10 @@
# 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
# process, which may cause wedges in the gate later.
sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
openstackdocstheme>=1.24.0 # Apache-2.0
reno>=2.5.0 # Apache-2.0
# this is required for the docs build jobs
sphinx>=1.6.2,<2.0.0;python_version=='2.7' # BSD
sphinx>=1.6.2;python_version>='3.4' # BSD
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

+ 5
- 0
lower-constraints.txt View File

@ -67,6 +67,7 @@ oslo.serialization==2.18.0
oslo.service==1.24.0
oslo.upgradecheck==0.1.0
oslo.utils==3.33.0
oslosphinx==4.7.0
oslotest==3.2.0
paramiko==2.4.1
Paste==2.0.3
@ -106,6 +107,10 @@ setproctitle==1.1.10
setuptools==21.0.0
simplegeneric==0.8.1
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-migrate==0.11.0
sqlparse==0.2.4


+ 21
- 1
qinling/api/app.py View File

@ -14,9 +14,12 @@
from oslo_config import cfg
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
from qinling.api import access_control
from qinling import config as q_config
from qinling import context as ctx
from qinling.db import api as db_api
from qinling.services import periodics
@ -43,6 +46,9 @@ def get_pecan_config():
def setup_app(config=None):
if not config:
config = get_pecan_config()
q_config.set_config_defaults()
app_conf = dict(config.app)
db_api.setup_db()
@ -61,4 +67,18 @@ def setup_app(config=None):
# Set up access control.
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()

+ 1
- 1
qinling/api/controllers/root.py View File

@ -67,7 +67,7 @@ class RootController(object):
def index(self):
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(
id='v1.0',
status='CURRENT',


+ 2
- 1
qinling/api/controllers/v1/root.py View File

@ -43,4 +43,5 @@ class Controller(object):
@wsme_pecan.wsexpose(RootResource)
def index(self):
return RootResource(uri='%s/%s' % (pecan.request.host_url, 'v1'))
return RootResource(uri='%s/%s' % (pecan.request.application_url,
'v1'))

+ 17
- 0
qinling/api/wsgi.py View File

@ -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()

+ 32
- 0
qinling/config.py View File

@ -16,6 +16,7 @@ from keystonemiddleware import auth_token
from oslo_concurrency import processutils
from oslo_config import cfg
from oslo_log import log
from oslo_middleware import cors
from qinling import version
@ -335,3 +336,34 @@ def parse_args(args=None, usage=None, default_config_files=None):
usage=usage,
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']
)

+ 96
- 0
qinling/tests/unit/api/test_cors_middleware.py View File

@ -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)

+ 42
- 0
qinling/tests/unit/api/test_oslo_middleware.py View File

@ -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()

+ 9
- 0
releasenotes/notes/add-cors-support-ce060ee8513a1acf.yaml View File

@ -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.

+ 1
- 0
requirements.txt View File

@ -10,6 +10,7 @@ oslo.concurrency>=3.26.0 # Apache-2.0
oslo.config>=5.2.0 # Apache-2.0
oslo.db>=4.27.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.upgradecheck>=0.1.0 # Apache-2.0
oslo.utils>=3.33.0 # Apache-2.0


+ 7
- 1
setup.cfg View File

@ -30,8 +30,11 @@ console_scripts =
qinling-db-manage = qinling.db.sqlalchemy.migration.cli:main
qinling-status = qinling.cmd.status:main
wsgi_scripts =
qinling-wsgi-api = qinling.api.app:init_wsgi
qinling.storage.provider:
local = qinling.storage.file_system:FileSystemStorage
local = qinling.storage.file_system:FileSystemStorage
qinling.orchestrator =
kubernetes = qinling.orchestrator.kubernetes.manager:KubernetesManager
@ -39,6 +42,9 @@ qinling.orchestrator =
oslo.config.opts =
qinling.config = qinling.config:list_opts
oslo.config.opts.defaults =
qinling.config = qinling.config:set_cors_middleware_defaults
tempest.test_plugins =
qinling_test = qinling_tempest_plugin.plugin:QinlingTempestPlugin


+ 2
- 0
tools/config/config-generator.qinling.conf View File

@ -2,6 +2,8 @@
namespace = qinling.config
namespace = keystonemiddleware.auth_token
namespace = oslo.messaging
namespace = oslo.middleware.cors
namespace = oslo.middleware.http_proxy_to_wsgi
namespace = oslo.log
namespace = oslo.policy
namespace = oslo.db

Loading…
Cancel
Save