Add config option to override url for links
The versions url returns the wrong data when Ironic API is behind a proxy. This adds a new config option called "public_endpoint" so it can be set properly. Closes-Bug: #1384379 Change-Id: I6d1b59db3ce09aba7bca5a71edcf97eb79f0b17b
This commit is contained in:
parent
f38cfb1ee2
commit
eec96136be
|
@ -447,6 +447,14 @@
|
|||
# from a collection resource. (integer value)
|
||||
#max_limit=1000
|
||||
|
||||
# Public URL to use when building the links to the API
|
||||
# resources (for example, "https://ironic.rocks:6384"). If
|
||||
# None the links will be built using the request's host URL.
|
||||
# If the API is operating behind a proxy, you will want to
|
||||
# change this to represent the proxy's URL. Defaults to None.
|
||||
# (string value)
|
||||
#public_endpoint=<None>
|
||||
|
||||
|
||||
[cisco_ucs]
|
||||
|
||||
|
|
|
@ -29,6 +29,14 @@ API_SERVICE_OPTS = [
|
|||
default=1000,
|
||||
help=_('The maximum number of items returned in a single '
|
||||
'response from a collection resource.')),
|
||||
cfg.StrOpt('public_endpoint',
|
||||
default=None,
|
||||
help=_("Public URL to use when building the links to the API "
|
||||
"resources (for example, \"https://ironic.rocks:6384\")."
|
||||
" If None the links will be built using the request's "
|
||||
"host URL. If the API is operating behind a proxy, you "
|
||||
"will want to change this to represent the proxy's URL. "
|
||||
"Defaults to None.")),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
|
|
@ -54,7 +54,8 @@ def setup_app(pecan_config=None, extra_hooks=None):
|
|||
hooks.DBHook(),
|
||||
hooks.ContextHook(pecan_config.app.acl_public_routes),
|
||||
hooks.RPCHook(),
|
||||
hooks.NoExceptionTracebackHook()]
|
||||
hooks.NoExceptionTracebackHook(),
|
||||
hooks.PublicUrlHook()]
|
||||
if extra_hooks:
|
||||
app_hooks.extend(extra_hooks)
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ from ironic.api.controllers import base
|
|||
|
||||
def build_url(resource, resource_args, bookmark=False, base_url=None):
|
||||
if base_url is None:
|
||||
base_url = pecan.request.host_url
|
||||
base_url = pecan.request.public_url
|
||||
|
||||
template = '%(url)s/%(res)s' if bookmark else '%(url)s/v1/%(res)s'
|
||||
# FIXME(lucasagomes): I'm getting a 404 when doing a GET on
|
||||
|
|
|
@ -37,7 +37,7 @@ class Version(base.APIBase):
|
|||
def convert(id):
|
||||
version = Version()
|
||||
version.id = id
|
||||
version.links = [link.Link.make_link('self', pecan.request.host_url,
|
||||
version.links = [link.Link.make_link('self', pecan.request.public_url,
|
||||
id, '', bookmark=True)]
|
||||
return version
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ class V1(base.APIBase):
|
|||
def convert():
|
||||
v1 = V1()
|
||||
v1.id = "v1"
|
||||
v1.links = [link.Link.make_link('self', pecan.request.host_url,
|
||||
v1.links = [link.Link.make_link('self', pecan.request.public_url,
|
||||
'v1', '', bookmark=True),
|
||||
link.Link.make_link('describedby',
|
||||
'http://docs.openstack.org',
|
||||
|
@ -96,31 +96,31 @@ class V1(base.APIBase):
|
|||
]
|
||||
v1.media_types = [MediaType('application/json',
|
||||
'application/vnd.openstack.ironic.v1+json')]
|
||||
v1.chassis = [link.Link.make_link('self', pecan.request.host_url,
|
||||
v1.chassis = [link.Link.make_link('self', pecan.request.public_url,
|
||||
'chassis', ''),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.host_url,
|
||||
pecan.request.public_url,
|
||||
'chassis', '',
|
||||
bookmark=True)
|
||||
]
|
||||
v1.nodes = [link.Link.make_link('self', pecan.request.host_url,
|
||||
v1.nodes = [link.Link.make_link('self', pecan.request.public_url,
|
||||
'nodes', ''),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.host_url,
|
||||
pecan.request.public_url,
|
||||
'nodes', '',
|
||||
bookmark=True)
|
||||
]
|
||||
v1.ports = [link.Link.make_link('self', pecan.request.host_url,
|
||||
v1.ports = [link.Link.make_link('self', pecan.request.public_url,
|
||||
'ports', ''),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.host_url,
|
||||
pecan.request.public_url,
|
||||
'ports', '',
|
||||
bookmark=True)
|
||||
]
|
||||
v1.drivers = [link.Link.make_link('self', pecan.request.host_url,
|
||||
v1.drivers = [link.Link.make_link('self', pecan.request.public_url,
|
||||
'drivers', ''),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.host_url,
|
||||
pecan.request.public_url,
|
||||
'drivers', '',
|
||||
bookmark=True)
|
||||
]
|
||||
|
|
|
@ -108,7 +108,7 @@ class Chassis(base.APIBase):
|
|||
if fields is not None:
|
||||
api_utils.check_for_invalid_fields(fields, chassis.as_dict())
|
||||
|
||||
return cls._convert_with_links(chassis, pecan.request.host_url,
|
||||
return cls._convert_with_links(chassis, pecan.request.public_url,
|
||||
fields)
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -44,5 +44,5 @@ class Collection(base.APIBase):
|
|||
'args': q_args, 'limit': limit,
|
||||
'marker': self.collection[-1].uuid}
|
||||
|
||||
return link.Link.make_link('next', pecan.request.host_url,
|
||||
return link.Link.make_link('next', pecan.request.public_url,
|
||||
resource_url, next_args).href
|
||||
|
|
|
@ -77,10 +77,10 @@ class Driver(base.APIBase):
|
|||
driver.hosts = hosts
|
||||
driver.links = [
|
||||
link.Link.make_link('self',
|
||||
pecan.request.host_url,
|
||||
pecan.request.public_url,
|
||||
'drivers', name),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.host_url,
|
||||
pecan.request.public_url,
|
||||
'drivers', name,
|
||||
bookmark=True)
|
||||
]
|
||||
|
|
|
@ -684,7 +684,7 @@ class Node(base.APIBase):
|
|||
assert_juno_provision_state_name(node)
|
||||
hide_fields_in_newer_versions(node)
|
||||
show_password = pecan.request.context.show_password
|
||||
return cls._convert_with_links(node, pecan.request.host_url,
|
||||
return cls._convert_with_links(node, pecan.request.public_url,
|
||||
fields=fields,
|
||||
show_password=show_password)
|
||||
|
||||
|
|
|
@ -137,7 +137,7 @@ class Port(base.APIBase):
|
|||
if fields is not None:
|
||||
api_utils.check_for_invalid_fields(fields, port.as_dict())
|
||||
|
||||
return cls._convert_with_links(port, pecan.request.host_url,
|
||||
return cls._convert_with_links(port, pecan.request.public_url,
|
||||
fields=fields)
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -153,3 +153,16 @@ class NoExceptionTracebackHook(hooks.PecanHook):
|
|||
# Replace the whole json. Cannot change original one beacause it's
|
||||
# generated on the fly.
|
||||
state.response.json = json_body
|
||||
|
||||
|
||||
class PublicUrlHook(hooks.PecanHook):
|
||||
"""Attach the right public_url to the request.
|
||||
|
||||
Attach the right public_url to the request so resources can create
|
||||
links even when the API service is behind a proxy or SSL terminator.
|
||||
|
||||
"""
|
||||
|
||||
def before(self, state):
|
||||
state.request.public_url = (cfg.CONF.api.public_endpoint or
|
||||
state.request.host_url)
|
||||
|
|
|
@ -36,6 +36,7 @@ class FakeRequest(object):
|
|||
self.context = context
|
||||
self.environ = environ or {}
|
||||
self.version = (1, 0)
|
||||
self.host_url = 'http://127.0.0.1:6385'
|
||||
|
||||
|
||||
class FakeRequestState(object):
|
||||
|
@ -281,3 +282,22 @@ class TestTrustedCallHookCompatJuno(TestTrustedCallHook):
|
|||
|
||||
def test_trusted_call_hook_public_api(self):
|
||||
self.skipTest('no public_api trusted call policy in juno')
|
||||
|
||||
|
||||
class TestPublicUrlHook(base.FunctionalTest):
|
||||
|
||||
def test_before_host_url(self):
|
||||
headers = fake_headers()
|
||||
reqstate = FakeRequestState(headers=headers)
|
||||
trusted_call_hook = hooks.PublicUrlHook()
|
||||
trusted_call_hook.before(reqstate)
|
||||
self.assertEqual(reqstate.request.host_url,
|
||||
reqstate.request.public_url)
|
||||
|
||||
def test_before_public_endpoint(self):
|
||||
cfg.CONF.set_override('public_endpoint', 'http://foo', 'api')
|
||||
headers = fake_headers()
|
||||
reqstate = FakeRequestState(headers=headers)
|
||||
trusted_call_hook = hooks.PublicUrlHook()
|
||||
trusted_call_hook.before(reqstate)
|
||||
self.assertEqual('http://foo', reqstate.request.public_url)
|
||||
|
|
|
@ -132,7 +132,8 @@ class TestListChassis(test_api_base.FunctionalTest):
|
|||
uuids = [n['uuid'] for n in data['chassis']]
|
||||
six.assertCountEqual(self, ch_list, uuids)
|
||||
|
||||
def test_links(self):
|
||||
def _test_links(self, public_url=None):
|
||||
cfg.CONF.set_override('public_endpoint', public_url, 'api')
|
||||
uuid = uuidutils.generate_uuid()
|
||||
obj_utils.create_test_chassis(self.context, uuid=uuid)
|
||||
data = self.get_json('/chassis/%s' % uuid)
|
||||
|
@ -143,6 +144,20 @@ class TestListChassis(test_api_base.FunctionalTest):
|
|||
bookmark = l['rel'] == 'bookmark'
|
||||
self.assertTrue(self.validate_link(l['href'], bookmark=bookmark))
|
||||
|
||||
if public_url is not None:
|
||||
expected = [{'href': '%s/v1/chassis/%s' % (public_url, uuid),
|
||||
'rel': 'self'},
|
||||
{'href': '%s/chassis/%s' % (public_url, uuid),
|
||||
'rel': 'bookmark'}]
|
||||
for i in expected:
|
||||
self.assertIn(i, data['links'])
|
||||
|
||||
def test_links(self):
|
||||
self._test_links()
|
||||
|
||||
def test_links_public_url(self):
|
||||
self._test_links(public_url='http://foo')
|
||||
|
||||
def test_collection_links(self):
|
||||
for id in range(5):
|
||||
obj_utils.create_test_chassis(self.context,
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
import json
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from six.moves import http_client
|
||||
from testtools.matchers import HasLength
|
||||
|
||||
|
@ -75,6 +76,31 @@ class TestListDrivers(base.FunctionalTest):
|
|||
response = self.get_json('/drivers/%s' % self.d1, expect_errors=True)
|
||||
self.assertEqual(http_client.NOT_FOUND, response.status_int)
|
||||
|
||||
def _test_links(self, public_url=None):
|
||||
cfg.CONF.set_override('public_endpoint', public_url, 'api')
|
||||
self.register_fake_conductors()
|
||||
data = self.get_json('/drivers/%s' % self.d1)
|
||||
self.assertIn('links', data.keys())
|
||||
self.assertEqual(2, len(data['links']))
|
||||
self.assertIn(self.d1, data['links'][0]['href'])
|
||||
for l in data['links']:
|
||||
bookmark = l['rel'] == 'bookmark'
|
||||
self.assertTrue(self.validate_link(l['href'], bookmark=bookmark))
|
||||
|
||||
if public_url is not None:
|
||||
expected = [{'href': '%s/v1/drivers/%s' % (public_url, self.d1),
|
||||
'rel': 'self'},
|
||||
{'href': '%s/drivers/%s' % (public_url, self.d1),
|
||||
'rel': 'bookmark'}]
|
||||
for i in expected:
|
||||
self.assertIn(i, data['links'])
|
||||
|
||||
def test_links(self):
|
||||
self._test_links()
|
||||
|
||||
def test_links_public_url(self):
|
||||
self._test_links(public_url='http://foo')
|
||||
|
||||
@mock.patch.object(rpcapi.ConductorAPI, 'driver_vendor_passthru')
|
||||
def test_driver_vendor_passthru_sync(self, mocked_driver_vendor_passthru):
|
||||
self.register_fake_conductors()
|
||||
|
|
|
@ -322,7 +322,8 @@ class TestListNodes(test_api_base.FunctionalTest):
|
|||
self.assertEqual(len(nodes), len(data['nodes']))
|
||||
self.assertEqual(sorted(node_names), sorted(names))
|
||||
|
||||
def test_links(self):
|
||||
def _test_links(self, public_url=None):
|
||||
cfg.CONF.set_override('public_endpoint', public_url, 'api')
|
||||
uuid = uuidutils.generate_uuid()
|
||||
obj_utils.create_test_node(self.context, uuid=uuid)
|
||||
data = self.get_json('/nodes/%s' % uuid)
|
||||
|
@ -333,6 +334,20 @@ class TestListNodes(test_api_base.FunctionalTest):
|
|||
bookmark = l['rel'] == 'bookmark'
|
||||
self.assertTrue(self.validate_link(l['href'], bookmark=bookmark))
|
||||
|
||||
if public_url is not None:
|
||||
expected = [{'href': '%s/v1/nodes/%s' % (public_url, uuid),
|
||||
'rel': 'self'},
|
||||
{'href': '%s/nodes/%s' % (public_url, uuid),
|
||||
'rel': 'bookmark'}]
|
||||
for i in expected:
|
||||
self.assertIn(i, data['links'])
|
||||
|
||||
def test_links(self):
|
||||
self._test_links()
|
||||
|
||||
def test_links_public_url(self):
|
||||
self._test_links(public_url='http://foo')
|
||||
|
||||
def test_collection_links(self):
|
||||
nodes = []
|
||||
for id in range(5):
|
||||
|
|
|
@ -161,7 +161,8 @@ class TestListPorts(test_api_base.FunctionalTest):
|
|||
uuids = [n['uuid'] for n in data['ports']]
|
||||
six.assertCountEqual(self, ports, uuids)
|
||||
|
||||
def test_links(self):
|
||||
def _test_links(self, public_url=None):
|
||||
cfg.CONF.set_override('public_endpoint', public_url, 'api')
|
||||
uuid = uuidutils.generate_uuid()
|
||||
obj_utils.create_test_port(self.context,
|
||||
uuid=uuid,
|
||||
|
@ -174,6 +175,20 @@ class TestListPorts(test_api_base.FunctionalTest):
|
|||
bookmark = l['rel'] == 'bookmark'
|
||||
self.assertTrue(self.validate_link(l['href'], bookmark=bookmark))
|
||||
|
||||
if public_url is not None:
|
||||
expected = [{'href': '%s/v1/ports/%s' % (public_url, uuid),
|
||||
'rel': 'self'},
|
||||
{'href': '%s/ports/%s' % (public_url, uuid),
|
||||
'rel': 'bookmark'}]
|
||||
for i in expected:
|
||||
self.assertIn(i, data['links'])
|
||||
|
||||
def test_links(self):
|
||||
self._test_links()
|
||||
|
||||
def test_links_public_url(self):
|
||||
self._test_links(public_url='http://foo')
|
||||
|
||||
def test_collection_links(self):
|
||||
ports = []
|
||||
for id_ in range(5):
|
||||
|
|
Loading…
Reference in New Issue