Add API Discovery to Ironic Inspector

This patch allows for API discovery via the root API endpoint and
version endpoints. These endpoints will return a json blob containing
all the information required to talk to the different API versions and
resources.

Change-Id: Id427f1ca542523afb3513b047e32b5aad39bf743
Closes-Bug: #1477959
This commit is contained in:
Sam Betts 2015-09-15 10:57:19 +01:00
parent b338739037
commit d5f08170a4
3 changed files with 133 additions and 9 deletions

View File

@ -228,6 +228,43 @@ major version and is always ``1`` for now, ``Y`` is a minor version.
``X-OpenStack-Ironic-Inspector-API-Maximum-Version`` headers with minimum ``X-OpenStack-Ironic-Inspector-API-Maximum-Version`` headers with minimum
and maximum API versions supported by the server. and maximum API versions supported by the server.
API Discovery
~~~~~~~~~~~~~
The API supports API discovery. You can query different parts of the API to
discover what other endpoints are avaliable.
* ``GET /`` List API Versions
Response:
* 200 - OK
Response body: JSON dictionary containing a list of ``versions``, each
version contains:
* ``status`` Either CURRENT or SUPPORTED
* ``id`` The version identifier
* ``links`` A list of links to this version endpoint containing:
* ``href`` The URL
* ``rel`` The relationship between the version and the href
* ``GET /v1`` List API v1 resources
Response:
* 200 - OK
Response body: JSON dictionary containing a list of ``resources``, each
resource contains:
* ``name`` The name of this resources
* ``links`` A list of link to this resource containing:
* ``href`` The URL
* ``rel`` The relationship between the resource and the href
Version History Version History
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^

View File

@ -15,6 +15,8 @@ import eventlet
eventlet.monkey_patch() eventlet.monkey_patch()
import functools import functools
import os
import re
import ssl import ssl
import sys import sys
@ -108,13 +110,55 @@ def add_version_headers(res):
return res return res
def create_link_object(urls):
links = []
for url in urls:
links.append({"rel": "self",
"href": os.path.join(flask.request.url_root, url)})
return links
def generate_resource_data(resources):
data = []
for resource in resources:
item = {}
item['name'] = str(resource).split('/')[-1]
item['links'] = create_link_object([str(resource)[1:]])
data.append(item)
return data
@app.route('/', methods=['GET']) @app.route('/', methods=['GET'])
@app.route('/v1', methods=['GET'])
@convert_exceptions @convert_exceptions
def api_root(): def api_root():
# TODO(dtantsur): this endpoint only returns API version now, it's possible versions = [
# we'll return something meaningful in addition later {
return flask.jsonify({}) "status": "CURRENT",
"id": '%s.%s' % CURRENT_API_VERSION,
},
]
for version in versions:
version['links'] = create_link_object(
["v%s" % version['id'].split('.')[0]])
return flask.jsonify(versions=versions)
@app.route('/<version>', methods=['GET'])
@convert_exceptions
def version_root(version):
pat = re.compile("^\/%s\/[^\/]*?$" % version)
resources = []
for url in app.url_map.iter_rules():
if pat.match(str(url)):
resources.append(url)
if not resources:
raise utils.Error(_('Version not found.'), code=404)
return flask.jsonify(resources=generate_resource_data(resources))
@app.route('/v1/continue', methods=['POST']) @app.route('/v1/continue', methods=['POST'])
@ -126,6 +170,7 @@ def api_continue():
return flask.jsonify(process.process(data)) return flask.jsonify(process.process(data))
# TODO(sambetts) Add API discovery for this endpoint
@app.route('/v1/introspection/<uuid>', methods=['GET', 'POST']) @app.route('/v1/introspection/<uuid>', methods=['GET', 'POST'])
@convert_exceptions @convert_exceptions
def api_introspection(uuid): def api_introspection(uuid):

View File

@ -305,11 +305,53 @@ class TestApiVersions(BaseAPITest):
self.assertEqual('%d.%d' % main.CURRENT_API_VERSION, self.assertEqual('%d.%d' % main.CURRENT_API_VERSION,
res.headers.get(main._MAX_VERSION_HEADER)) res.headers.get(main._MAX_VERSION_HEADER))
def test_root_endpoints(self): def test_root_endpoint(self):
for endpoint in '/', '/v1': res = self.app.get("/")
res = self.app.get(endpoint) self.assertEqual(200, res.status_code)
self.assertEqual(200, res.status_code) self._check_version_present(res)
self._check_version_present(res) data = res.data.decode('utf-8')
json_data = json.loads(data)
expected = {"versions": [{
"status": "CURRENT", "id": '%s.%s' % main.CURRENT_API_VERSION,
"links": [{
"rel": "self",
"href": ("http://localhost/v%s" %
main.CURRENT_API_VERSION[0])
}]
}]}
self.assertEqual(expected, json_data)
@mock.patch.object(main.app.url_map, "iter_rules", autospec=True)
def test_version_endpoint(self, mock_rules):
mock_rules.return_value = ["/v1/endpoint1", "/v1/endpoint2/<uuid>",
"/v1/endpoint1/<name>",
"/v2/endpoint1", "/v1/endpoint3",
"/v1/endpoint2/<uuid>/subpoint"]
endpoint = "/v1"
res = self.app.get(endpoint)
self.assertEqual(200, res.status_code)
self._check_version_present(res)
json_data = json.loads(res.data.decode('utf-8'))
expected = {u'resources': [
{
u'name': u'endpoint1',
u'links': [{
u'rel': u'self',
u'href': u'http://localhost/v1/endpoint1'}]
},
{
u'name': u'endpoint3',
u'links': [{
u'rel': u'self',
u'href': u'http://localhost/v1/endpoint3'}]
},
]}
self.assertDictEqual(expected, json_data)
def test_version_endpoint_invalid(self):
endpoint = "/v-1"
res = self.app.get(endpoint)
self.assertEqual(404, res.status_code)
def test_404_unexpected(self): def test_404_unexpected(self):
# API version on unknown pages # API version on unknown pages