summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve Baker <sbaker@redhat.com>2020-07-15 11:09:40 +1200
committerSteve Baker <sbaker@redhat.com>2020-07-30 07:37:31 +1200
commitb6a25d467a18b271a51dc4e283693df82837e080 (patch)
tree65f681a52f6e6154c28f7f38f55648724c150401
parentbf15520119f482a96730337367cf7f7ed192d979 (diff)
downloadironic-b6a25d467a18b271a51dc4e283693df82837e080.tar.gz
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
-rw-r--r--ironic/api/controllers/v1/__init__.py364
-rw-r--r--ironic/tests/unit/api/controllers/v1/test_root.py125
-rw-r--r--ironic/tests/unit/api/test_root.py9
3 files changed, 294 insertions, 204 deletions
diff --git a/ironic/api/controllers/v1/__init__.py b/ironic/api/controllers/v1/__init__.py
index b0be184f9..a944dec69 100644
--- a/ironic/api/controllers/v1/__init__.py
+++ b/ironic/api/controllers/v1/__init__.py
@@ -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,205 +58,161 @@ 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,
- '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,
- '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('bookmark',
- api.request.public_url,
- 'nodes', '',
- bookmark=True)
- ]
- v1.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 = [
- 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)
- ]
- if utils.allow_volume():
- v1.volume = [
- link.make_link('self',
- api.request.public_url,
- 'volume', ''),
- link.make_link('bookmark',
- api.request.public_url,
- 'volume', '',
- bookmark=True)
- ]
- if utils.allow_ramdisk_endpoints():
- 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',
- api.request.public_url,
- 'heartbeat', ''),
- link.make_link('bookmark',
- api.request.public_url,
- 'heartbeat', '',
- bookmark=True)
- ]
- if utils.allow_expose_conductors():
- v1.conductors = [link.make_link('self',
- api.request.public_url,
- 'conductors', ''),
- link.make_link('bookmark',
- api.request.public_url,
- 'conductors', '',
- bookmark=True)
- ]
- if utils.allow_allocations():
- v1.allocations = [link.make_link('self',
- api.request.public_url,
- 'allocations', ''),
- link.make_link('bookmark',
- api.request.public_url,
- 'allocations', '',
- bookmark=True)
- ]
- if utils.allow_expose_events():
- v1.events = [link.make_link('self', api.request.public_url,
- 'events', ''),
- link.make_link('bookmark',
- api.request.public_url,
- 'events', '',
- bookmark=True)
- ]
- if utils.allow_deploy_templates():
- v1.deploy_templates = [
- link.make_link('self',
- api.request.public_url,
- 'deploy_templates', ''),
- link.make_link('bookmark',
- api.request.public_url,
- 'deploy_templates', '',
- bookmark=True)
- ]
- v1.version = version.default_version()
- return v1
-
-
-class Controller(rest.RestController):
+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')
+ ],
+ '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)
+ ],
+ 'nodes': [
+ link.make_link('self', api.request.public_url,
+ 'nodes', ''),
+ link.make_link('bookmark',
+ api.request.public_url,
+ 'nodes', '',
+ bookmark=True)
+ ],
+ 'ports': [
+ link.make_link('self', api.request.public_url,
+ 'ports', ''),
+ link.make_link('bookmark',
+ api.request.public_url,
+ 'ports', '',
+ bookmark=True)
+ ],
+ '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'] = [
+ link.make_link('self',
+ api.request.public_url,
+ 'volume', ''),
+ link.make_link('bookmark',
+ api.request.public_url,
+ 'volume', '',
+ bookmark=True)
+ ]
+ if utils.allow_ramdisk_endpoints():
+ 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',
+ api.request.public_url,
+ 'heartbeat', ''),
+ link.make_link('bookmark',
+ api.request.public_url,
+ 'heartbeat', '',
+ bookmark=True)
+ ]
+ if utils.allow_expose_conductors():
+ v1['conductors'] = [
+ link.make_link('self',
+ api.request.public_url,
+ 'conductors', ''),
+ link.make_link('bookmark',
+ api.request.public_url,
+ 'conductors', '',
+ bookmark=True)
+ ]
+ if utils.allow_allocations():
+ v1['allocations'] = [
+ link.make_link('self',
+ api.request.public_url,
+ 'allocations', ''),
+ link.make_link('bookmark',
+ api.request.public_url,
+ 'allocations', '',
+ bookmark=True)
+ ]
+ if utils.allow_expose_events():
+ v1['events'] = [
+ link.make_link('self', api.request.public_url,
+ 'events', ''),
+ link.make_link('bookmark',
+ api.request.public_url,
+ 'events', '',
+ bookmark=True)
+ ]
+ if utils.allow_deploy_templates():
+ v1['deploy_templates'] = [
+ link.make_link('self',
+ api.request.public_url,
+ 'deploy_templates', ''),
+ link.make_link('bookmark',
+ api.request.public_url,
+ 'deploy_templates', '',
+ bookmark=True)
+ ]
+ return v1
+
+
+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()
-
- @expose.expose(V1)
- def get(self):
- # NOTE: The reason why convert() it's being called for every
+ _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()
+ }
+
+ @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',)
diff --git a/ironic/tests/unit/api/controllers/v1/test_root.py b/ironic/tests/unit/api/controllers/v1/test_root.py
index b3e58b817..78d3053e4 100644
--- a/ironic/tests/unit/api/controllers/v1/test_root.py
+++ b/ironic/tests/unit/api/controllers/v1/test_root.py
@@ -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):
diff --git a/ironic/tests/unit/api/test_root.py b/ironic/tests/unit/api/test_root.py
index 9a512d7ad..b784762f3 100644
--- a/ironic/tests/unit/api/test_root.py
+++ b/ironic/tests/unit/api/test_root.py
@@ -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,8 +69,8 @@ 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',
- 'base': 'application/json'}, data['media_types'])
+ self.assertEqual({'type': 'application/vnd.openstack.ironic.v1+json',
+ 'base': 'application/json'}, data['media_types'])
version1 = data['version']
self.assertEqual('v1', version1['id'])