Fix api wsgi entrypoint

This patch fixes a bunch of issues with the wsgi
entrypoint and the api implementation in general.

* Fixed issue with versions and mod_wsgi.
* Fixed href not properly redirecting under mod_wsgi.
* Fixed Webhook middleware not handling /cluster/v1 uri.
* Register objects when starting api_wsgi.

Closes-Bug: #1849174
Closes-Bug: #1849175

Change-Id: Idebab562d1cd4b6b498d90a93577f34b6f56cd78
This commit is contained in:
Erik Olof Gunnar Andersson 2019-10-13 21:24:28 -07:00
parent 427acad1e2
commit ba74c64771
8 changed files with 79 additions and 28 deletions

View File

@ -46,11 +46,14 @@ class VersionNegotiationFilter(wsgi.Middleware):
If there is a version identifier in the URI, simply return the correct
API controller, otherwise, if we find an Accept: header, process it
"""
LOG.debug("Processing request: %(m)s %(p)s Accept: %(a)s",
{'m': req.method, 'p': req.path, 'a': req.accept})
LOG.debug(
"Processing request: %(method)s %(path)s Accept: %(accept)s",
{'method': req.method, 'path': req.path, 'accept': req.accept}
)
# If the request is for /versions, just return the versions container
if req.path_info_peek() in ("versions", ""):
path_info_peak = req.path_info_peek()
if path_info_peak in ('versions', ''):
return self.versions_app
accept = str(req.accept)
@ -69,11 +72,7 @@ class VersionNegotiationFilter(wsgi.Middleware):
if path is None or path == '/':
return controller(self.conf)
return None
else:
LOG.debug("Unknown version in URI")
# Try another path
if accept.startswith('application/vnd.openstack.clustering-'):
elif accept.startswith('application/vnd.openstack.clustering-'):
token_loc = len('application/vnd.openstack.clustering-')
accept_version = accept[token_loc:]
controller = self._get_controller(accept_version, req)
@ -88,10 +87,10 @@ class VersionNegotiationFilter(wsgi.Middleware):
if path is None or path == '/':
return controller(self.conf)
return None
else:
LOG.debug("Unknown version in request header")
else:
LOG.debug("Unknown version in request")
if accept not in ('*/*', ''):
if accept not in ('*/*', '') and path_info_peak is not None:
LOG.debug("Returning HTTP 404 due to unknown Accept header: %s ",
accept)
return webob.exc.HTTPNotFound()

View File

@ -69,18 +69,19 @@ class WebhookMiddleware(wsgi.Middleware):
def _parse_url(self, url):
"""Extract receiver ID from the request URL.
Parse a URL of format: http://host:port/webhooks/id/trigger?V=1&k=v
Parse a URL of format: http://host:port/v1/webhooks/id/trigger?V=1&k=v
:param url: The URL from which the request is received.
"""
parts = urlparse.urlparse(url)
p = parts.path.split('/')
# check if URL is a webhook trigger request
# expected: ['', 'v1', 'webhooks', 'webhook-id', 'trigger']
if len(p) != 5:
return None
try:
index = p.index('v1')
p = p[(index + 1):]
except ValueError:
pass
if any((p[0] != '', p[2] != 'webhooks', p[4] != 'trigger')):
if len(p) != 3 or p[0] != 'webhooks' or p[2] != 'trigger':
return None
# at this point it has been determined that the URL is a webhook
@ -94,7 +95,7 @@ class WebhookMiddleware(wsgi.Middleware):
'trigger URL'))
params = dict((k, v[0]) for k, v in qs.items())
return p[3], params
return p[1], params
def _get_token(self, **kwargs):
"""Get a valid token based on the credential provided.

View File

@ -9,7 +9,6 @@
# 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_utils import encodeutils
import webob.dec
@ -41,7 +40,7 @@ class VersionController(object):
return response
@classmethod
def version_info(cls):
def version_info(cls, req):
return {
"id": "1.0",
"status": "CURRENT",
@ -53,7 +52,7 @@ class VersionController(object):
}
],
"links": [{
"href": "/v1",
"href": req.application_url.rstrip('/') + '/v1',
"rel": "self"}, {
"rel": "help",
"href": "https://docs.openstack.org/api-ref/clustering"
@ -63,7 +62,7 @@ class VersionController(object):
}
def version(self, req):
return {"version": self.version_info()}
return {"version": self.version_info(req)}
@classmethod
def min_api_version(cls):

View File

@ -39,7 +39,7 @@ class Controller(object):
versions = []
for ver, vc in self.Controllers.items():
versions.append(vc.version_info())
versions.append(vc.version_info(req))
body = jsonutils.dumps(dict(versions=versions))

View File

@ -25,6 +25,7 @@ from senlin.api.common import wsgi
from senlin.common import config
from senlin.common import messaging
from senlin.common import profiler
from senlin import objects
from senlin import version
@ -34,6 +35,7 @@ def init_app():
version=version.version_info.version_string())
logging.setup(cfg.CONF, 'senlin-api')
config.set_config_defaults()
objects.register_all()
messaging.setup()
profiler.setup('senlin-api', cfg.CONF.host)

View File

@ -13,6 +13,7 @@
import mock
from oslo_config import cfg
from oslo_utils import uuidutils
import six
import webob
@ -153,7 +154,8 @@ class TestWebhookMiddleware(base.SenlinTestCase):
req = mock.Mock()
req.method = 'POST'
req.url = 'http://url1'
req.url = 'http://url1/v1'
req.script_name = '/v1'
req.params = {'key': 'FAKE_KEY'}
req.headers = {}
req.version_request = vr.APIVersionRequest('1.0')
@ -181,7 +183,7 @@ class TestWebhookMiddleware(base.SenlinTestCase):
self.assertIsNone(res)
self.assertEqual('FAKE_TOKEN', req.headers['X-Auth-Token'])
mock_extract.assert_called_once_with('http://url1')
mock_extract.assert_called_once_with('http://url1/v1')
mock_token.assert_called_once_with(
auth_url='AUTH_URL', password='PASSWORD', username='USERNAME',
user_domain_name='DOMAIN', foo='bar')
@ -202,7 +204,8 @@ class TestWebhookMiddleware(base.SenlinTestCase):
# no webhook_id extracted
req = mock.Mock()
req.method = 'POST'
req.url = 'http://url1'
req.url = 'http://url1/v1'
req.script_name = '/v1'
mock_extract = self.patchobject(self.middleware, '_parse_url',
return_value=None)
@ -210,3 +213,50 @@ class TestWebhookMiddleware(base.SenlinTestCase):
self.assertIsNone(res)
mock_extract.assert_called_once_with(req.url)
self.assertNotIn('X-Auth-Token', req.headers)
def test_parse_url_valid(self):
uid = uuidutils.generate_uuid()
result = self.middleware._parse_url(
'https://url1/cluster/v1/webhooks/%s/trigger?V=2&k=v' % uid
)
self.assertEqual(
(uid, {'k': 'v'}), result
)
def test_parse_url_valid_with_port(self):
uid = uuidutils.generate_uuid()
result = self.middleware._parse_url(
'http://url1:5000/v1/webhooks/%s/trigger?V=2&k=v' % uid
)
self.assertEqual(
(uid, {'k': 'v'}), result
)
def test_parse_url_invalid(self):
result = self.middleware._parse_url(
'http://url1'
)
self.assertIsNone(result)
def test_parse_url_missing_version(self):
uid = uuidutils.generate_uuid()
result = self.middleware._parse_url(
'https://url1/cluster/webhooks/%s/trigger?V=2&k=v' % uid
)
self.assertIsNone(result)
def test_parse_url_missing_webhooks(self):
uid = uuidutils.generate_uuid()
result = self.middleware._parse_url(
'https://url1/cluster/v1/%s/trigger?V=2&k=v' % uid
)
self.assertIsNone(result)

View File

@ -51,7 +51,7 @@ class VersionControllerTest(shared.ControllerTest, base.SenlinTestCase):
}]
self.assertEqual(expected, response['media-types'])
expected = [{
'href': '/v1',
'href': 'http://server.test:8004/v1',
'rel': 'self'}, {
'href': 'https://docs.openstack.org/api-ref/clustering',
'rel': 'help',

View File

@ -58,7 +58,7 @@ class ControllerTest(object):
return {
'SERVER_NAME': 'server.test',
'SERVER_PORT': 8004,
'SCRIPT_NAME': '/v1',
'SCRIPT_NAME': '',
'PATH_INFO': '/%s' % self.project + path,
'wsgi.url_scheme': 'http',
}