Make end-points discoverable via Ironic API

This commit makes the following API end-points
discoverable via Ironic API
1. '/v1/nodes/<uuid>/states'
2. '/v1/drivers/<driver-name>/properties'

API Micro version is bumped to v1.14 with this change.

Closes-Bug:#1311288
Closes-Bug: #1475744
Co-Authored-By: Lucas Alvares Gomes <lucasagomes@gmail.com>
Change-Id: Ibc0013b8c09c80d90042bf12b31a54820b00b43c
This commit is contained in:
Anusha Ramineni 2015-07-24 13:43:56 +05:30 committed by Lucas Alvares Gomes
parent 38a4ebcdf8
commit 3274cdeed4
8 changed files with 79 additions and 5 deletions

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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):