Convert v1 controller to plain, return JSON
This change converts the v1 controller from a RestController to a plain controller, and converts the /v1 response to remove the WSME types and return plain JSON. Change-Id: I483c6bb2e6b0da07b9e0c58190dbbc97e04bb6c1 Story: 1651346 Task: 10551
This commit is contained in:
parent
bf15520119
commit
b6a25d467a
@ -18,8 +18,9 @@ Version 1 of the Ironic API
|
||||
Specification can be found at doc/source/webapi/v1.rst
|
||||
"""
|
||||
|
||||
from http import client as http_client
|
||||
|
||||
import pecan
|
||||
from pecan import rest
|
||||
from webob import exc
|
||||
|
||||
from ironic import api
|
||||
@ -39,7 +40,7 @@ from ironic.api.controllers.v1 import utils
|
||||
from ironic.api.controllers.v1 import versions
|
||||
from ironic.api.controllers.v1 import volume
|
||||
from ironic.api.controllers import version
|
||||
from ironic.api import expose
|
||||
from ironic.api import method
|
||||
from ironic.common.i18n import _
|
||||
|
||||
BASE_VERSION = versions.BASE_VERSION
|
||||
@ -57,119 +58,65 @@ def max_version():
|
||||
versions.min_version_string(), versions.max_version_string())
|
||||
|
||||
|
||||
class MediaType(base.Base):
|
||||
"""A media type representation."""
|
||||
|
||||
base = str
|
||||
type = str
|
||||
|
||||
def __init__(self, base, type):
|
||||
self.base = base
|
||||
self.type = type
|
||||
|
||||
|
||||
class V1(base.Base):
|
||||
"""The representation of the version 1 of the API."""
|
||||
|
||||
id = str
|
||||
"""The ID of the version, also acts as the release number"""
|
||||
|
||||
media_types = [MediaType]
|
||||
"""An array of supported media types for this version"""
|
||||
|
||||
links = None
|
||||
"""Links that point to a specific URL for this version and documentation"""
|
||||
|
||||
chassis = None
|
||||
"""Links to the chassis resource"""
|
||||
|
||||
nodes = None
|
||||
"""Links to the nodes resource"""
|
||||
|
||||
ports = None
|
||||
"""Links to the ports resource"""
|
||||
|
||||
portgroups = None
|
||||
"""Links to the portgroups resource"""
|
||||
|
||||
drivers = None
|
||||
"""Links to the drivers resource"""
|
||||
|
||||
volume = None
|
||||
"""Links to the volume resource"""
|
||||
|
||||
lookup = None
|
||||
"""Links to the lookup resource"""
|
||||
|
||||
heartbeat = None
|
||||
"""Links to the heartbeat resource"""
|
||||
|
||||
conductors = None
|
||||
"""Links to the conductors resource"""
|
||||
|
||||
allocations = None
|
||||
"""Links to the allocations resource"""
|
||||
|
||||
deploy_templates = None
|
||||
"""Links to the deploy_templates resource"""
|
||||
|
||||
version = None
|
||||
"""Version discovery information."""
|
||||
|
||||
events = None
|
||||
"""Links to the events resource"""
|
||||
|
||||
@staticmethod
|
||||
def convert():
|
||||
v1 = V1()
|
||||
v1.id = "v1"
|
||||
v1.links = [link.make_link('self', api.request.public_url,
|
||||
def v1():
|
||||
v1 = {
|
||||
'id': "v1",
|
||||
'links': [
|
||||
link.make_link('self', api.request.public_url,
|
||||
'v1', '', bookmark=True),
|
||||
link.make_link('describedby',
|
||||
'https://docs.openstack.org',
|
||||
'/ironic/latest/contributor/',
|
||||
'webapi.html',
|
||||
bookmark=True, type='text/html')
|
||||
]
|
||||
v1.media_types = [MediaType('application/json',
|
||||
'application/vnd.openstack.ironic.v1+json')]
|
||||
v1.chassis = [link.make_link('self', api.request.public_url,
|
||||
],
|
||||
'media_types': {
|
||||
'base': 'application/json',
|
||||
'type': 'application/vnd.openstack.ironic.v1+json'
|
||||
},
|
||||
'chassis': [
|
||||
link.make_link('self', api.request.public_url,
|
||||
'chassis', ''),
|
||||
link.make_link('bookmark',
|
||||
api.request.public_url,
|
||||
'chassis', '',
|
||||
bookmark=True)
|
||||
]
|
||||
v1.nodes = [link.make_link('self', api.request.public_url,
|
||||
],
|
||||
'nodes': [
|
||||
link.make_link('self', api.request.public_url,
|
||||
'nodes', ''),
|
||||
link.make_link('bookmark',
|
||||
api.request.public_url,
|
||||
'nodes', '',
|
||||
bookmark=True)
|
||||
]
|
||||
v1.ports = [link.make_link('self', api.request.public_url,
|
||||
],
|
||||
'ports': [
|
||||
link.make_link('self', api.request.public_url,
|
||||
'ports', ''),
|
||||
link.make_link('bookmark',
|
||||
api.request.public_url,
|
||||
'ports', '',
|
||||
bookmark=True)
|
||||
]
|
||||
if utils.allow_portgroups():
|
||||
v1.portgroups = [
|
||||
],
|
||||
'drivers': [
|
||||
link.make_link('self', api.request.public_url,
|
||||
'portgroups', ''),
|
||||
link.make_link('bookmark', api.request.public_url,
|
||||
'portgroups', '', bookmark=True)
|
||||
]
|
||||
v1.drivers = [link.make_link('self', api.request.public_url,
|
||||
'drivers', ''),
|
||||
link.make_link('bookmark',
|
||||
api.request.public_url,
|
||||
'drivers', '',
|
||||
bookmark=True)
|
||||
],
|
||||
'version': version.default_version()
|
||||
}
|
||||
if utils.allow_portgroups():
|
||||
v1['portgroups'] = [
|
||||
link.make_link('self', api.request.public_url,
|
||||
'portgroups', ''),
|
||||
link.make_link('bookmark', api.request.public_url,
|
||||
'portgroups', '', bookmark=True)
|
||||
]
|
||||
if utils.allow_volume():
|
||||
v1.volume = [
|
||||
v1['volume'] = [
|
||||
link.make_link('self',
|
||||
api.request.public_url,
|
||||
'volume', ''),
|
||||
@ -179,14 +126,16 @@ class V1(base.Base):
|
||||
bookmark=True)
|
||||
]
|
||||
if utils.allow_ramdisk_endpoints():
|
||||
v1.lookup = [link.make_link('self', api.request.public_url,
|
||||
v1['lookup'] = [
|
||||
link.make_link('self', api.request.public_url,
|
||||
'lookup', ''),
|
||||
link.make_link('bookmark',
|
||||
api.request.public_url,
|
||||
'lookup', '',
|
||||
bookmark=True)
|
||||
]
|
||||
v1.heartbeat = [link.make_link('self',
|
||||
v1['heartbeat'] = [
|
||||
link.make_link('self',
|
||||
api.request.public_url,
|
||||
'heartbeat', ''),
|
||||
link.make_link('bookmark',
|
||||
@ -195,7 +144,8 @@ class V1(base.Base):
|
||||
bookmark=True)
|
||||
]
|
||||
if utils.allow_expose_conductors():
|
||||
v1.conductors = [link.make_link('self',
|
||||
v1['conductors'] = [
|
||||
link.make_link('self',
|
||||
api.request.public_url,
|
||||
'conductors', ''),
|
||||
link.make_link('bookmark',
|
||||
@ -204,7 +154,8 @@ class V1(base.Base):
|
||||
bookmark=True)
|
||||
]
|
||||
if utils.allow_allocations():
|
||||
v1.allocations = [link.make_link('self',
|
||||
v1['allocations'] = [
|
||||
link.make_link('self',
|
||||
api.request.public_url,
|
||||
'allocations', ''),
|
||||
link.make_link('bookmark',
|
||||
@ -213,7 +164,8 @@ class V1(base.Base):
|
||||
bookmark=True)
|
||||
]
|
||||
if utils.allow_expose_events():
|
||||
v1.events = [link.make_link('self', api.request.public_url,
|
||||
v1['events'] = [
|
||||
link.make_link('self', api.request.public_url,
|
||||
'events', ''),
|
||||
link.make_link('bookmark',
|
||||
api.request.public_url,
|
||||
@ -221,7 +173,7 @@ class V1(base.Base):
|
||||
bookmark=True)
|
||||
]
|
||||
if utils.allow_deploy_templates():
|
||||
v1.deploy_templates = [
|
||||
v1['deploy_templates'] = [
|
||||
link.make_link('self',
|
||||
api.request.public_url,
|
||||
'deploy_templates', ''),
|
||||
@ -230,32 +182,37 @@ class V1(base.Base):
|
||||
'deploy_templates', '',
|
||||
bookmark=True)
|
||||
]
|
||||
v1.version = version.default_version()
|
||||
return v1
|
||||
|
||||
|
||||
class Controller(rest.RestController):
|
||||
class Controller(object):
|
||||
"""Version 1 API controller root."""
|
||||
|
||||
nodes = node.NodesController()
|
||||
ports = port.PortsController()
|
||||
portgroups = portgroup.PortgroupsController()
|
||||
chassis = chassis.ChassisController()
|
||||
drivers = driver.DriversController()
|
||||
volume = volume.VolumeController()
|
||||
lookup = ramdisk.LookupController()
|
||||
heartbeat = ramdisk.HeartbeatController()
|
||||
conductors = conductor.ConductorsController()
|
||||
allocations = allocation.AllocationsController()
|
||||
events = event.EventsController()
|
||||
deploy_templates = deploy_template.DeployTemplatesController()
|
||||
_subcontroller_map = {
|
||||
'nodes': node.NodesController(),
|
||||
'ports': port.PortsController(),
|
||||
'portgroups': portgroup.PortgroupsController(),
|
||||
'chassis': chassis.ChassisController(),
|
||||
'drivers': driver.DriversController(),
|
||||
'volume': volume.VolumeController(),
|
||||
'lookup': ramdisk.LookupController(),
|
||||
'heartbeat': ramdisk.HeartbeatController(),
|
||||
'conductors': conductor.ConductorsController(),
|
||||
'allocations': allocation.AllocationsController(),
|
||||
'events': event.EventsController(),
|
||||
'deploy_templates': deploy_template.DeployTemplatesController()
|
||||
}
|
||||
|
||||
@expose.expose(V1)
|
||||
def get(self):
|
||||
# NOTE: The reason why convert() it's being called for every
|
||||
@method.expose()
|
||||
def index(self):
|
||||
# NOTE: The reason why v1() it's being called for every
|
||||
# request is because we need to get the host url from
|
||||
# the request object to make the links.
|
||||
return V1.convert()
|
||||
self._add_version_attributes()
|
||||
if api.request.method != "GET":
|
||||
pecan.abort(http_client.METHOD_NOT_ALLOWED)
|
||||
|
||||
return v1()
|
||||
|
||||
def _check_version(self, version, headers=None):
|
||||
if headers is None:
|
||||
@ -279,8 +236,7 @@ class Controller(rest.RestController):
|
||||
'max': versions.max_version_string()},
|
||||
headers=headers)
|
||||
|
||||
@pecan.expose()
|
||||
def _route(self, args, request=None):
|
||||
def _add_version_attributes(self):
|
||||
v = base.Version(api.request.headers, versions.min_version_string(),
|
||||
versions.max_version_string())
|
||||
|
||||
@ -295,7 +251,15 @@ class Controller(rest.RestController):
|
||||
api.response.headers[base.Version.string] = str(v)
|
||||
api.request.version = v
|
||||
|
||||
return super(Controller, self)._route(args, request)
|
||||
@pecan.expose()
|
||||
def _lookup(self, primary_key, *remainder):
|
||||
self._add_version_attributes()
|
||||
|
||||
controller = self._subcontroller_map.get(primary_key)
|
||||
if not controller:
|
||||
pecan.abort(http_client.NOT_FOUND)
|
||||
|
||||
return controller, remainder
|
||||
|
||||
|
||||
__all__ = ('Controller',)
|
||||
|
@ -17,6 +17,7 @@ from unittest import mock
|
||||
from webob import exc as webob_exc
|
||||
|
||||
from ironic.api.controllers import v1 as v1_api
|
||||
from ironic.api.controllers.v1 import versions
|
||||
from ironic.tests import base as test_base
|
||||
from ironic.tests.unit.api import base as api_base
|
||||
|
||||
@ -28,6 +29,130 @@ class TestV1Routing(api_base.BaseApiTest):
|
||||
mock.ANY,
|
||||
mock.ANY)
|
||||
|
||||
def test_min_version(self):
|
||||
response = self.get_json(
|
||||
'/',
|
||||
headers={
|
||||
'Accept': 'application/json',
|
||||
'X-OpenStack-Ironic-API-Version':
|
||||
versions.min_version_string()
|
||||
})
|
||||
self.assertEqual({
|
||||
'id': 'v1',
|
||||
'links': [
|
||||
{'href': 'http://localhost/v1/', 'rel': 'self'},
|
||||
{'href': 'https://docs.openstack.org//ironic/latest'
|
||||
'/contributor//webapi.html',
|
||||
'rel': 'describedby', 'type': 'text/html'}
|
||||
],
|
||||
'media_types': {
|
||||
'base': 'application/json',
|
||||
'type': 'application/vnd.openstack.ironic.v1+json'
|
||||
},
|
||||
'version': {
|
||||
'id': 'v1',
|
||||
'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}],
|
||||
'status': 'CURRENT',
|
||||
'min_version': versions.min_version_string(),
|
||||
'version': versions.max_version_string()
|
||||
},
|
||||
'chassis': [
|
||||
{'href': 'http://localhost/v1/chassis/', 'rel': 'self'},
|
||||
{'href': 'http://localhost/chassis/', 'rel': 'bookmark'}
|
||||
],
|
||||
'nodes': [
|
||||
{'href': 'http://localhost/v1/nodes/', 'rel': 'self'},
|
||||
{'href': 'http://localhost/nodes/', 'rel': 'bookmark'}
|
||||
],
|
||||
'ports': [
|
||||
{'href': 'http://localhost/v1/ports/', 'rel': 'self'},
|
||||
{'href': 'http://localhost/ports/', 'rel': 'bookmark'}
|
||||
],
|
||||
'drivers': [
|
||||
{'href': 'http://localhost/v1/drivers/', 'rel': 'self'},
|
||||
{'href': 'http://localhost/drivers/', 'rel': 'bookmark'}
|
||||
],
|
||||
}, response)
|
||||
|
||||
def test_max_version(self):
|
||||
response = self.get_json(
|
||||
'/',
|
||||
headers={
|
||||
'Accept': 'application/json',
|
||||
'X-OpenStack-Ironic-API-Version':
|
||||
versions.max_version_string()
|
||||
})
|
||||
self.assertEqual({
|
||||
'id': 'v1',
|
||||
'links': [
|
||||
{'href': 'http://localhost/v1/', 'rel': 'self'},
|
||||
{'href': 'https://docs.openstack.org//ironic/latest'
|
||||
'/contributor//webapi.html',
|
||||
'rel': 'describedby', 'type': 'text/html'}
|
||||
],
|
||||
'media_types': {
|
||||
'base': 'application/json',
|
||||
'type': 'application/vnd.openstack.ironic.v1+json'
|
||||
},
|
||||
'version': {
|
||||
'id': 'v1',
|
||||
'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}],
|
||||
'status': 'CURRENT',
|
||||
'min_version': versions.min_version_string(),
|
||||
'version': versions.max_version_string()
|
||||
},
|
||||
'allocations': [
|
||||
{'href': 'http://localhost/v1/allocations/', 'rel': 'self'},
|
||||
{'href': 'http://localhost/allocations/', 'rel': 'bookmark'}
|
||||
],
|
||||
'chassis': [
|
||||
{'href': 'http://localhost/v1/chassis/', 'rel': 'self'},
|
||||
{'href': 'http://localhost/chassis/', 'rel': 'bookmark'}
|
||||
],
|
||||
'conductors': [
|
||||
{'href': 'http://localhost/v1/conductors/', 'rel': 'self'},
|
||||
{'href': 'http://localhost/conductors/', 'rel': 'bookmark'}
|
||||
],
|
||||
'deploy_templates': [
|
||||
{'href': 'http://localhost/v1/deploy_templates/',
|
||||
'rel': 'self'},
|
||||
{'href': 'http://localhost/deploy_templates/',
|
||||
'rel': 'bookmark'}
|
||||
],
|
||||
'drivers': [
|
||||
{'href': 'http://localhost/v1/drivers/', 'rel': 'self'},
|
||||
{'href': 'http://localhost/drivers/', 'rel': 'bookmark'}
|
||||
],
|
||||
'events': [
|
||||
{'href': 'http://localhost/v1/events/', 'rel': 'self'},
|
||||
{'href': 'http://localhost/events/', 'rel': 'bookmark'}
|
||||
],
|
||||
'heartbeat': [
|
||||
{'href': 'http://localhost/v1/heartbeat/', 'rel': 'self'},
|
||||
{'href': 'http://localhost/heartbeat/', 'rel': 'bookmark'}
|
||||
],
|
||||
'lookup': [
|
||||
{'href': 'http://localhost/v1/lookup/', 'rel': 'self'},
|
||||
{'href': 'http://localhost/lookup/', 'rel': 'bookmark'}
|
||||
],
|
||||
'nodes': [
|
||||
{'href': 'http://localhost/v1/nodes/', 'rel': 'self'},
|
||||
{'href': 'http://localhost/nodes/', 'rel': 'bookmark'}
|
||||
],
|
||||
'portgroups': [
|
||||
{'href': 'http://localhost/v1/portgroups/', 'rel': 'self'},
|
||||
{'href': 'http://localhost/portgroups/', 'rel': 'bookmark'}
|
||||
],
|
||||
'ports': [
|
||||
{'href': 'http://localhost/v1/ports/', 'rel': 'self'},
|
||||
{'href': 'http://localhost/ports/', 'rel': 'bookmark'}
|
||||
],
|
||||
'volume': [
|
||||
{'href': 'http://localhost/v1/volume/', 'rel': 'self'},
|
||||
{'href': 'http://localhost/volume/', 'rel': 'bookmark'}
|
||||
]
|
||||
}, response)
|
||||
|
||||
|
||||
class TestCheckVersions(test_base.TestCase):
|
||||
|
||||
|
@ -44,9 +44,10 @@ class TestRoot(base.BaseApiTest):
|
||||
self.assertNotIn('<html', response.json['error_message'])
|
||||
|
||||
def test_no_html_errors2(self):
|
||||
response = self.delete('/v1', expect_errors=True)
|
||||
response = self.delete('/', expect_errors=True)
|
||||
self.assertEqual(http_client.METHOD_NOT_ALLOWED, response.status_int)
|
||||
self.assertIn('Not Allowed', response.json['error_message'])
|
||||
self.assertIn('malformed or otherwise incorrect',
|
||||
response.json['error_message'])
|
||||
self.assertNotIn('<html', response.json['error_message'])
|
||||
|
||||
|
||||
@ -68,7 +69,7 @@ class TestV1Root(base.BaseApiTest):
|
||||
expected_resources = (['chassis', 'drivers', 'nodes', 'ports']
|
||||
+ additional_expected_resources)
|
||||
self.assertEqual(sorted(expected_resources), sorted(actual_resources))
|
||||
self.assertIn({'type': 'application/vnd.openstack.ironic.v1+json',
|
||||
self.assertEqual({'type': 'application/vnd.openstack.ironic.v1+json',
|
||||
'base': 'application/json'}, data['media_types'])
|
||||
|
||||
version1 = data['version']
|
||||
|
Loading…
Reference in New Issue
Block a user