Add rudimentary CORS support to placement API

If 'cors.allowed_origin' is set in the nova.conf, configure the
placement API to use oslo_middleware.CORS. Simple gabbi tests are
added which confirm the basic operation of the middleware, modeled
on the tests in nova/tests/functional/test_middleware.py, as well as
additional tests which confirm that when the middleware is not
configured it is not present in the system.

The cors config options are registered in deploy.py to ensure that
the group is allowed to exist in conf (even if it doesn't). Without
that, a deployment that tries to configure cors would not actually
cause the middleware to run.

Change-Id: I571bc675facaecb523dcf906f4bb44a51102b514
This commit is contained in:
Chris Dent 2016-11-02 17:01:36 +00:00
parent cee14139bd
commit a598af9de6
6 changed files with 130 additions and 4 deletions

View File

@ -12,7 +12,8 @@
"""Deployment handling for Placmenent API.""" """Deployment handling for Placmenent API."""
from keystonemiddleware import auth_token from keystonemiddleware import auth_token
from oslo_middleware import request_id import oslo_middleware
from oslo_middleware import cors
from nova.api import openstack as common_api from nova.api import openstack as common_api
from nova.api.openstack.placement import auth from nova.api.openstack.placement import auth
@ -41,8 +42,18 @@ def deploy(conf, project_name):
auth_middleware = auth_token.filter_factory( auth_middleware = auth_token.filter_factory(
{}, oslo_config_project=project_name) {}, oslo_config_project=project_name)
# Pass in our CORS config, if any, manually as that's a)
# explicit, b) makes testing more straightfoward, c) let's
# us control the use of cors by the presence of its config.
conf.register_opts(cors.CORS_OPTS, 'cors')
if conf.cors.allowed_origin:
cors_middleware = oslo_middleware.CORS.factory(
{}, **conf.cors)
else:
cors_middleware = None
context_middleware = auth.PlacementKeystoneContext context_middleware = auth.PlacementKeystoneContext
req_id_middleware = request_id.RequestId req_id_middleware = oslo_middleware.RequestId
microversion_middleware = microversion.MicroversionMiddleware microversion_middleware = microversion.MicroversionMiddleware
fault_wrap = common_api.FaultWrapper fault_wrap = common_api.FaultWrapper
request_log = requestlog.RequestLog request_log = requestlog.RequestLog
@ -62,9 +73,11 @@ def deploy(conf, project_name):
request_log, request_log,
context_middleware, context_middleware,
auth_middleware, auth_middleware,
cors_middleware,
req_id_middleware, req_id_middleware,
): ):
application = middleware(application) if middleware:
application = middleware(application)
return application return application

View File

@ -13,6 +13,7 @@
import os import os
from gabbi import fixture from gabbi import fixture
from oslo_middleware import cors
from oslo_utils import uuidutils from oslo_utils import uuidutils
from nova.api.openstack.placement import deploy from nova.api.openstack.placement import deploy
@ -55,7 +56,16 @@ class APIFixture(fixture.GabbiFixture):
group='api_database') group='api_database')
self.conf.set_override('connection', "sqlite://", self.conf.set_override('connection', "sqlite://",
group='placement_database') group='placement_database')
config.parse_args([], default_config_files=None, configure_db=False,
# Register CORS opts, but do not set config. This has the
# effect of exercising the "don't use cors" path in
# deploy.py. Without setting some config the group will not
# be present.
self.conf.register_opts(cors.CORS_OPTS, 'cors')
# Make sure default_config_files is an empty list, not None.
# If None /etc/nova/nova.conf is read and confuses results.
config.parse_args([], default_config_files=[], configure_db=False,
init_rpc=False) init_rpc=False)
# NOTE(cdent): api and main database are not used but we still need # NOTE(cdent): api and main database are not used but we still need
@ -136,3 +146,15 @@ class AllocationFixture(APIFixture):
# not been created in the Allocation fixture # not been created in the Allocation fixture
os.environ['ALT_RP_UUID'] = uuidutils.generate_uuid() os.environ['ALT_RP_UUID'] = uuidutils.generate_uuid()
os.environ['ALT_RP_NAME'] = uuidutils.generate_uuid() os.environ['ALT_RP_NAME'] = uuidutils.generate_uuid()
class CORSFixture(APIFixture):
"""An APIFixture that turns on CORS."""
def start_fixture(self):
super(CORSFixture, self).start_fixture()
# NOTE(cdent): If we remove this override, then the cors
# group ends up not existing in the conf, so when deploy.py
# wants to load the CORS middleware, it will not.
self.conf.set_override('allowed_origin', 'http://valid.example.com',
group='cors')

View File

@ -52,6 +52,16 @@ tests:
response_strings: response_strings:
- The method DELETE is not allowed for this resource. - The method DELETE is not allowed for this resource.
- name: 405 on bad options method on app
OPTIONS: /resource_providers
status: 405
response_headers:
allow: /(GET|POST), (POST|GET)/
response_json_paths:
$.errors[0].title: Method Not Allowed
response_strings:
- The method OPTIONS is not allowed for this resource.
- name: bad accept resource providers - name: bad accept resource providers
GET: /resource_providers GET: /resource_providers
request_headers: request_headers:

View File

@ -0,0 +1,47 @@
# Confirm that CORS is present. No complex configuration is done so
# this just tests the basics. Borrowed, in spirit, from
# nova.tests.functional.test_middleware.
fixtures:
- CORSFixture
defaults:
request_headers:
x-auth-token: user
tests:
- name: valid options request
OPTIONS: /
request_headers:
origin: http://valid.example.com
access-control-request-method: GET
status: 200
response_headers:
access-control-allow-origin: http://valid.example.com
- name: invalid options request
OPTIONS: /
request_headers:
origin: http://invalid.example.com
access-control-request-method: GET
status: 200
response_forbidden_headers:
- access-control-allow-origin
- name: valid get request
GET: /
request_headers:
origin: http://valid.example.com
access-control-request-method: GET
status: 200
response_headers:
access-control-allow-origin: http://valid.example.com
- name: invalid get request
GET: /
request_headers:
origin: http://invalid.example.com
access-control-request-method: GET
status: 200
response_forbidden_headers:
- access-control-allow-origin

View File

@ -0,0 +1,25 @@
# Confirm that things work as intended when CORS is not configured.
fixtures:
- APIFixture
defaults:
request_headers:
x-auth-token: user
tests:
- name: options request not allowed
OPTIONS: /
request_headers:
origin: http://valid.example.com
access-control-request-method: GET
status: 405
- name: get request no cors headers
GET: /
request_headers:
origin: http://valid.example.com
access-control-request-method: GET
status: 200
response_forbidden_headers:
- access-control-allow-origin

View File

@ -0,0 +1,9 @@
---
features:
- |
The placement API service can now be configured to support
`CORS <http://docs.openstack.org/developer/oslo.middleware/cors.html>`_.
If a `cors` configuration group is present in the service's configuration
file (currently `nova.conf`), with `allowed_origin` configured, the values
within will be used to configure the middleware. If `cors.allowed_origin`
is not set, the middleware will not be used.