Merge "Make end-points discoverable via Ironic API"
This commit is contained in:
commit
fb1e3d1e40
@ -32,6 +32,12 @@ always requests the newest supported API version.
|
||||
API Versions History
|
||||
--------------------
|
||||
|
||||
**1.14**
|
||||
|
||||
Make the following endpoints discoverable via Ironic API:
|
||||
* '/v1/nodes/<UUID or logical name>/states'
|
||||
* '/v1/drivers/<driver name>/properties'
|
||||
|
||||
**1.13**
|
||||
|
||||
Add a new verb ``abort`` to the API used to abort nodes in
|
||||
|
@ -70,6 +70,9 @@ class Driver(base.APIBase):
|
||||
links = wsme.wsattr([link.Link], readonly=True)
|
||||
"""A list containing self and bookmark links"""
|
||||
|
||||
properties = wsme.wsattr([link.Link], readonly=True)
|
||||
"""A list containing links to driver properties"""
|
||||
|
||||
@staticmethod
|
||||
def convert_with_links(name, hosts):
|
||||
driver = Driver()
|
||||
@ -84,6 +87,16 @@ class Driver(base.APIBase):
|
||||
'drivers', name,
|
||||
bookmark=True)
|
||||
]
|
||||
if api_utils.allow_links_node_states_and_driver_properties():
|
||||
driver.properties = [
|
||||
link.Link.make_link('self',
|
||||
pecan.request.public_url,
|
||||
'drivers', name + "/properties"),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.public_url,
|
||||
'drivers', name + "/properties",
|
||||
bookmark=True)
|
||||
]
|
||||
return driver
|
||||
|
||||
@classmethod
|
||||
|
@ -625,6 +625,9 @@ class Node(base.APIBase):
|
||||
ports = wsme.wsattr([link.Link], readonly=True)
|
||||
"""Links to the collection of ports on this node"""
|
||||
|
||||
states = wsme.wsattr([link.Link], readonly=True)
|
||||
"""Links to endpoint for retrieving and setting node states"""
|
||||
|
||||
# NOTE(deva): "conductor_affinity" shouldn't be presented on the
|
||||
# API because it's an internal value. Don't add it here.
|
||||
|
||||
@ -648,7 +651,8 @@ class Node(base.APIBase):
|
||||
setattr(self, 'chassis_uuid', kwargs.get('chassis_id', wtypes.Unset))
|
||||
|
||||
@staticmethod
|
||||
def _convert_with_links(node, url, fields=None, show_password=True):
|
||||
def _convert_with_links(node, url, fields=None, show_password=True,
|
||||
show_states_links=True):
|
||||
# NOTE(lucasagomes): Since we are able to return a specified set of
|
||||
# fields the "uuid" can be unset, so we need to save it in another
|
||||
# variable to use when building the links
|
||||
@ -662,6 +666,12 @@ class Node(base.APIBase):
|
||||
node_uuid + "/ports",
|
||||
bookmark=True)
|
||||
]
|
||||
if show_states_links:
|
||||
node.states = [link.Link.make_link('self', url, 'nodes',
|
||||
node_uuid + "/states"),
|
||||
link.Link.make_link('bookmark', url, 'nodes',
|
||||
node_uuid + "/states",
|
||||
bookmark=True)]
|
||||
|
||||
if not show_password and node.driver_info != wtypes.Unset:
|
||||
node.driver_info = ast.literal_eval(strutils.mask_password(
|
||||
@ -689,9 +699,12 @@ class Node(base.APIBase):
|
||||
assert_juno_provision_state_name(node)
|
||||
hide_fields_in_newer_versions(node)
|
||||
show_password = pecan.request.context.show_password
|
||||
show_states_links = (
|
||||
api_utils.allow_links_node_states_and_driver_properties())
|
||||
return cls._convert_with_links(node, pecan.request.public_url,
|
||||
fields=fields,
|
||||
show_password=show_password)
|
||||
show_password=show_password,
|
||||
show_states_links=show_states_links)
|
||||
|
||||
@classmethod
|
||||
def sample(cls, expand=True):
|
||||
|
@ -231,3 +231,13 @@ def allow_raid_config():
|
||||
Version 1.12 of the API allows RAID configuration for the node.
|
||||
"""
|
||||
return pecan.request.version.minor >= versions.MINOR_12_RAID_CONFIG
|
||||
|
||||
|
||||
def allow_links_node_states_and_driver_properties():
|
||||
"""Check if links are displayable.
|
||||
|
||||
Version 1.14 of the API allows the display of links to node states
|
||||
and driver properties.
|
||||
"""
|
||||
return (pecan.request.version.minor >=
|
||||
versions.MINOR_14_LINKS_NODESTATES_DRIVERPROPERTIES)
|
||||
|
@ -41,6 +41,9 @@ BASE_VERSION = 1
|
||||
# v1.11: Nodes appear in ENROLL state by default
|
||||
# v1.12: Add support for RAID
|
||||
# v1.13: Add 'abort' verb to CLEANWAIT
|
||||
# v1.14: Make the following endpoints discoverable via API:
|
||||
# 1. '/v1/nodes/<uuid>/states'
|
||||
# 2. '/v1/drivers/<driver-name>/properties'
|
||||
|
||||
MINOR_0_JUNO = 0
|
||||
MINOR_1_INITIAL_VERSION = 1
|
||||
@ -56,11 +59,12 @@ MINOR_10_UNRESTRICTED_NODE_NAME = 10
|
||||
MINOR_11_ENROLL_STATE = 11
|
||||
MINOR_12_RAID_CONFIG = 12
|
||||
MINOR_13_ABORT_VERB = 13
|
||||
MINOR_14_LINKS_NODESTATES_DRIVERPROPERTIES = 14
|
||||
|
||||
# When adding another version, update MINOR_MAX_VERSION and also update
|
||||
# doc/source/webapi/v1.rst with a detailed explanation of what the version has
|
||||
# changed.
|
||||
MINOR_MAX_VERSION = MINOR_13_ABORT_VERB
|
||||
MINOR_MAX_VERSION = MINOR_14_LINKS_NODESTATES_DRIVERPROPERTIES
|
||||
|
||||
# String representations of the minor and maximum versions
|
||||
MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)
|
||||
|
@ -64,13 +64,25 @@ class TestListDrivers(base.FunctionalTest):
|
||||
self.assertThat(data['drivers'], HasLength(0))
|
||||
self.assertEqual([], data['drivers'])
|
||||
|
||||
def test_drivers_get_one_ok(self):
|
||||
@mock.patch.object(rpcapi.ConductorAPI, 'get_driver_properties')
|
||||
def test_drivers_get_one_ok(self, mock_driver_properties):
|
||||
# get_driver_properties mock is required by validate_link()
|
||||
self.register_fake_conductors()
|
||||
data = self.get_json('/drivers/%s' % self.d1)
|
||||
data = self.get_json('/drivers/%s' % self.d1,
|
||||
headers={api_base.Version.string: '1.14'})
|
||||
self.assertEqual(self.d1, data['name'])
|
||||
self.assertEqual([self.h1], data['hosts'])
|
||||
self.assertIn('properties', data.keys())
|
||||
self.validate_link(data['links'][0]['href'])
|
||||
self.validate_link(data['links'][1]['href'])
|
||||
self.validate_link(data['properties'][0]['href'])
|
||||
self.validate_link(data['properties'][1]['href'])
|
||||
|
||||
def test_driver_properties_hidden_in_lower_version(self):
|
||||
self.register_fake_conductors()
|
||||
data = self.get_json('/drivers/%s' % self.d1,
|
||||
headers={api_base.Version.string: '1.8'})
|
||||
self.assertNotIn('properties', data.keys())
|
||||
|
||||
def test_drivers_get_one_not_found(self):
|
||||
response = self.get_json('/drivers/%s' % self.d1, expect_errors=True)
|
||||
|
@ -134,9 +134,18 @@ class TestListNodes(test_api_base.FunctionalTest):
|
||||
self.assertIn('inspection_finished_at', data)
|
||||
self.assertIn('inspection_started_at', data)
|
||||
self.assertIn('clean_step', data)
|
||||
self.assertIn('states', data)
|
||||
# never expose the chassis_id
|
||||
self.assertNotIn('chassis_id', data)
|
||||
|
||||
def test_node_states_field_hidden_in_lower_version(self):
|
||||
node = obj_utils.create_test_node(self.context,
|
||||
chassis_id=self.chassis.id)
|
||||
data = self.get_json(
|
||||
'/nodes/%s' % node.uuid,
|
||||
headers={api_base.Version.string: '1.8'})
|
||||
self.assertNotIn('states', data)
|
||||
|
||||
def test_get_one_custom_fields(self):
|
||||
node = obj_utils.create_test_node(self.context,
|
||||
chassis_id=self.chassis.id)
|
||||
|
@ -79,6 +79,13 @@ class TestApiUtils(base.TestCase):
|
||||
self.assertRaises(exception.NotAcceptable,
|
||||
utils.check_allow_specify_fields, ['foo'])
|
||||
|
||||
@mock.patch.object(pecan, 'request', spec_set=['version'])
|
||||
def test_allow_links_node_states_and_driver_properties(self, mock_request):
|
||||
mock_request.version.minor = 14
|
||||
self.assertTrue(utils.allow_links_node_states_and_driver_properties())
|
||||
mock_request.version.minor = 10
|
||||
self.assertFalse(utils.allow_links_node_states_and_driver_properties())
|
||||
|
||||
|
||||
class TestNodeIdent(base.TestCase):
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user