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:
parent
b338739037
commit
d5f08170a4
37
HTTP-API.rst
37
HTTP-API.rst
@ -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
|
||||
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
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -15,6 +15,8 @@ import eventlet
|
||||
eventlet.monkey_patch()
|
||||
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
import ssl
|
||||
import sys
|
||||
|
||||
@ -108,13 +110,55 @@ def add_version_headers(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('/v1', methods=['GET'])
|
||||
@convert_exceptions
|
||||
def api_root():
|
||||
# TODO(dtantsur): this endpoint only returns API version now, it's possible
|
||||
# we'll return something meaningful in addition later
|
||||
return flask.jsonify({})
|
||||
versions = [
|
||||
{
|
||||
"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'])
|
||||
@ -126,6 +170,7 @@ def api_continue():
|
||||
return flask.jsonify(process.process(data))
|
||||
|
||||
|
||||
# TODO(sambetts) Add API discovery for this endpoint
|
||||
@app.route('/v1/introspection/<uuid>', methods=['GET', 'POST'])
|
||||
@convert_exceptions
|
||||
def api_introspection(uuid):
|
||||
|
@ -305,11 +305,53 @@ class TestApiVersions(BaseAPITest):
|
||||
self.assertEqual('%d.%d' % main.CURRENT_API_VERSION,
|
||||
res.headers.get(main._MAX_VERSION_HEADER))
|
||||
|
||||
def test_root_endpoints(self):
|
||||
for endpoint in '/', '/v1':
|
||||
res = self.app.get(endpoint)
|
||||
self.assertEqual(200, res.status_code)
|
||||
self._check_version_present(res)
|
||||
def test_root_endpoint(self):
|
||||
res = self.app.get("/")
|
||||
self.assertEqual(200, res.status_code)
|
||||
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):
|
||||
# API version on unknown pages
|
||||
|
Loading…
Reference in New Issue
Block a user