API to list nodes using the same driver
Add API to list nodes using the same driver. Change-Id: I998d6a0a6da44487d5ba90dafd417d81d1c7e504 Partial-Bug: #1530626
This commit is contained in:
parent
6d8768e87c
commit
d3859a79ba
@ -32,6 +32,9 @@ always requests the newest supported API version.
|
|||||||
API Versions History
|
API Versions History
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
**1.16**
|
||||||
|
Add ability to filter nodes by driver.
|
||||||
|
|
||||||
**1.15**
|
**1.15**
|
||||||
|
|
||||||
Add ability to do manual cleaning when a node is in the manageable
|
Add ability to do manual cleaning when a node is in the manageable
|
||||||
|
@ -974,8 +974,8 @@ class NodesController(rest.RestController):
|
|||||||
|
|
||||||
def _get_nodes_collection(self, chassis_uuid, instance_uuid, associated,
|
def _get_nodes_collection(self, chassis_uuid, instance_uuid, associated,
|
||||||
maintenance, provision_state, marker, limit,
|
maintenance, provision_state, marker, limit,
|
||||||
sort_key, sort_dir, resource_url=None,
|
sort_key, sort_dir, driver=None,
|
||||||
fields=None):
|
resource_url=None, fields=None):
|
||||||
if self.from_chassis and not chassis_uuid:
|
if self.from_chassis and not chassis_uuid:
|
||||||
raise exception.MissingParameterValue(
|
raise exception.MissingParameterValue(
|
||||||
_("Chassis id not specified."))
|
_("Chassis id not specified."))
|
||||||
@ -1005,6 +1005,8 @@ class NodesController(rest.RestController):
|
|||||||
filters['maintenance'] = maintenance
|
filters['maintenance'] = maintenance
|
||||||
if provision_state:
|
if provision_state:
|
||||||
filters['provision_state'] = provision_state
|
filters['provision_state'] = provision_state
|
||||||
|
if driver:
|
||||||
|
filters['driver'] = driver
|
||||||
|
|
||||||
nodes = objects.Node.list(pecan.request.context, limit, marker_obj,
|
nodes = objects.Node.list(pecan.request.context, limit, marker_obj,
|
||||||
sort_key=sort_key, sort_dir=sort_dir,
|
sort_key=sort_key, sort_dir=sort_dir,
|
||||||
@ -1083,10 +1085,11 @@ class NodesController(rest.RestController):
|
|||||||
|
|
||||||
@expose.expose(NodeCollection, types.uuid, types.uuid, types.boolean,
|
@expose.expose(NodeCollection, types.uuid, types.uuid, types.boolean,
|
||||||
types.boolean, wtypes.text, types.uuid, int, wtypes.text,
|
types.boolean, wtypes.text, types.uuid, int, wtypes.text,
|
||||||
wtypes.text, types.listtype)
|
wtypes.text, wtypes.text, types.listtype)
|
||||||
def get_all(self, chassis_uuid=None, instance_uuid=None, associated=None,
|
def get_all(self, chassis_uuid=None, instance_uuid=None, associated=None,
|
||||||
maintenance=None, provision_state=None, marker=None,
|
maintenance=None, provision_state=None, marker=None,
|
||||||
limit=None, sort_key='id', sort_dir='asc', fields=None):
|
limit=None, sort_key='id', sort_dir='asc', driver=None,
|
||||||
|
fields=None):
|
||||||
"""Retrieve a list of nodes.
|
"""Retrieve a list of nodes.
|
||||||
|
|
||||||
:param chassis_uuid: Optional UUID of a chassis, to get only nodes for
|
:param chassis_uuid: Optional UUID of a chassis, to get only nodes for
|
||||||
@ -1105,25 +1108,28 @@ class NodesController(rest.RestController):
|
|||||||
:param limit: maximum number of resources to return in a single result.
|
:param limit: maximum number of resources to return in a single result.
|
||||||
:param sort_key: column to sort results by. Default: id.
|
:param sort_key: column to sort results by. Default: id.
|
||||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
|
:param driver: Optional string value to get only nodes using that
|
||||||
|
driver.
|
||||||
:param fields: Optional, a list with a specified set of fields
|
:param fields: Optional, a list with a specified set of fields
|
||||||
of the resource to be returned.
|
of the resource to be returned.
|
||||||
"""
|
"""
|
||||||
api_utils.check_allow_specify_fields(fields)
|
api_utils.check_allow_specify_fields(fields)
|
||||||
api_utils.check_for_invalid_state_and_allow_filter(provision_state)
|
api_utils.check_for_invalid_state_and_allow_filter(provision_state)
|
||||||
|
api_utils.check_allow_specify_driver(driver)
|
||||||
if fields is None:
|
if fields is None:
|
||||||
fields = _DEFAULT_RETURN_FIELDS
|
fields = _DEFAULT_RETURN_FIELDS
|
||||||
return self._get_nodes_collection(chassis_uuid, instance_uuid,
|
return self._get_nodes_collection(chassis_uuid, instance_uuid,
|
||||||
associated, maintenance,
|
associated, maintenance,
|
||||||
provision_state, marker,
|
provision_state, marker,
|
||||||
limit, sort_key, sort_dir,
|
limit, sort_key, sort_dir,
|
||||||
fields=fields)
|
driver, fields=fields)
|
||||||
|
|
||||||
@expose.expose(NodeCollection, types.uuid, types.uuid, types.boolean,
|
@expose.expose(NodeCollection, types.uuid, types.uuid, types.boolean,
|
||||||
types.boolean, wtypes.text, types.uuid, int, wtypes.text,
|
types.boolean, wtypes.text, types.uuid, int, wtypes.text,
|
||||||
wtypes.text)
|
wtypes.text, wtypes.text)
|
||||||
def detail(self, chassis_uuid=None, instance_uuid=None, associated=None,
|
def detail(self, chassis_uuid=None, instance_uuid=None, associated=None,
|
||||||
maintenance=None, provision_state=None, marker=None,
|
maintenance=None, provision_state=None, marker=None,
|
||||||
limit=None, sort_key='id', sort_dir='asc'):
|
limit=None, sort_key='id', sort_dir='asc', driver=None):
|
||||||
"""Retrieve a list of nodes with detail.
|
"""Retrieve a list of nodes with detail.
|
||||||
|
|
||||||
:param chassis_uuid: Optional UUID of a chassis, to get only nodes for
|
:param chassis_uuid: Optional UUID of a chassis, to get only nodes for
|
||||||
@ -1142,8 +1148,11 @@ class NodesController(rest.RestController):
|
|||||||
:param limit: maximum number of resources to return in a single result.
|
:param limit: maximum number of resources to return in a single result.
|
||||||
:param sort_key: column to sort results by. Default: id.
|
:param sort_key: column to sort results by. Default: id.
|
||||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
|
:param driver: Optional string value to get only nodes using that
|
||||||
|
driver.
|
||||||
"""
|
"""
|
||||||
api_utils.check_for_invalid_state_and_allow_filter(provision_state)
|
api_utils.check_for_invalid_state_and_allow_filter(provision_state)
|
||||||
|
api_utils.check_allow_specify_driver(driver)
|
||||||
# /detail should only work against collections
|
# /detail should only work against collections
|
||||||
parent = pecan.request.path.split('/')[:-1][-1]
|
parent = pecan.request.path.split('/')[:-1][-1]
|
||||||
if parent != "nodes":
|
if parent != "nodes":
|
||||||
@ -1154,7 +1163,7 @@ class NodesController(rest.RestController):
|
|||||||
associated, maintenance,
|
associated, maintenance,
|
||||||
provision_state, marker,
|
provision_state, marker,
|
||||||
limit, sort_key, sort_dir,
|
limit, sort_key, sort_dir,
|
||||||
resource_url)
|
driver, resource_url)
|
||||||
|
|
||||||
@expose.expose(wtypes.text, types.uuid_or_name, types.uuid)
|
@expose.expose(wtypes.text, types.uuid_or_name, types.uuid)
|
||||||
def validate(self, node=None, node_uuid=None):
|
def validate(self, node=None, node_uuid=None):
|
||||||
|
@ -214,6 +214,20 @@ def check_for_invalid_state_and_allow_filter(provision_state):
|
|||||||
_('Provision state "%s" is not valid') % provision_state)
|
_('Provision state "%s" is not valid') % provision_state)
|
||||||
|
|
||||||
|
|
||||||
|
def check_allow_specify_driver(driver):
|
||||||
|
"""Check if filtering nodes by driver is allowed.
|
||||||
|
|
||||||
|
Version 1.16 of the API allows filter nodes by driver.
|
||||||
|
"""
|
||||||
|
if (driver is not None and pecan.request.version.minor <
|
||||||
|
versions.MINOR_16_DRIVER_FILTER):
|
||||||
|
raise exception.NotAcceptable(_(
|
||||||
|
"Request not acceptable. The minimal required API version "
|
||||||
|
"should be %(base)s.%(opr)s") %
|
||||||
|
{'base': versions.BASE_VERSION,
|
||||||
|
'opr': versions.MINOR_16_DRIVER_FILTER})
|
||||||
|
|
||||||
|
|
||||||
def initial_node_provision_state():
|
def initial_node_provision_state():
|
||||||
"""Return node state to use by default when creating new nodes.
|
"""Return node state to use by default when creating new nodes.
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@ BASE_VERSION = 1
|
|||||||
# 1. '/v1/nodes/<uuid>/states'
|
# 1. '/v1/nodes/<uuid>/states'
|
||||||
# 2. '/v1/drivers/<driver-name>/properties'
|
# 2. '/v1/drivers/<driver-name>/properties'
|
||||||
# v1.15: Add ability to do manual cleaning of nodes
|
# v1.15: Add ability to do manual cleaning of nodes
|
||||||
|
# v1.16: Add ability to filter nodes by driver.
|
||||||
|
|
||||||
MINOR_0_JUNO = 0
|
MINOR_0_JUNO = 0
|
||||||
MINOR_1_INITIAL_VERSION = 1
|
MINOR_1_INITIAL_VERSION = 1
|
||||||
@ -62,11 +63,12 @@ MINOR_12_RAID_CONFIG = 12
|
|||||||
MINOR_13_ABORT_VERB = 13
|
MINOR_13_ABORT_VERB = 13
|
||||||
MINOR_14_LINKS_NODESTATES_DRIVERPROPERTIES = 14
|
MINOR_14_LINKS_NODESTATES_DRIVERPROPERTIES = 14
|
||||||
MINOR_15_MANUAL_CLEAN = 15
|
MINOR_15_MANUAL_CLEAN = 15
|
||||||
|
MINOR_16_DRIVER_FILTER = 16
|
||||||
|
|
||||||
# When adding another version, update MINOR_MAX_VERSION and also update
|
# 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
|
# doc/source/webapi/v1.rst with a detailed explanation of what the version has
|
||||||
# changed.
|
# changed.
|
||||||
MINOR_MAX_VERSION = MINOR_15_MANUAL_CLEAN
|
MINOR_MAX_VERSION = MINOR_16_DRIVER_FILTER
|
||||||
|
|
||||||
# String representations of the minor and maximum versions
|
# String representations of the minor and maximum versions
|
||||||
MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)
|
MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)
|
||||||
|
@ -695,6 +695,38 @@ class TestListNodes(test_api_base.BaseApiTest):
|
|||||||
self.assertEqual(http_client.NOT_ACCEPTABLE, response.status_code)
|
self.assertEqual(http_client.NOT_ACCEPTABLE, response.status_code)
|
||||||
self.assertTrue(response.json['error_message'])
|
self.assertTrue(response.json['error_message'])
|
||||||
|
|
||||||
|
def test_get_nodes_by_driver(self):
|
||||||
|
node = obj_utils.create_test_node(self.context,
|
||||||
|
uuid=uuidutils.generate_uuid(),
|
||||||
|
driver='pxe_ssh')
|
||||||
|
node1 = obj_utils.create_test_node(self.context,
|
||||||
|
uuid=uuidutils.generate_uuid(),
|
||||||
|
driver='fake')
|
||||||
|
|
||||||
|
data = self.get_json('/nodes?driver=pxe_ssh',
|
||||||
|
headers={api_base.Version.string: "1.16"})
|
||||||
|
uuids = [n['uuid'] for n in data['nodes']]
|
||||||
|
self.assertIn(node.uuid, uuids)
|
||||||
|
self.assertNotIn(node1.uuid, uuids)
|
||||||
|
data = self.get_json('/nodes?driver=fake',
|
||||||
|
headers={api_base.Version.string: "1.16"})
|
||||||
|
uuids = [n['uuid'] for n in data['nodes']]
|
||||||
|
self.assertIn(node1.uuid, uuids)
|
||||||
|
self.assertNotIn(node.uuid, uuids)
|
||||||
|
|
||||||
|
def test_get_nodes_by_invalid_driver(self):
|
||||||
|
data = self.get_json('/nodes?driver=test',
|
||||||
|
headers={api_base.Version.string: "1.16"})
|
||||||
|
self.assertEqual(0, len(data['nodes']))
|
||||||
|
|
||||||
|
def test_get_nodes_by_driver_invalid_api_version(self):
|
||||||
|
response = self.get_json(
|
||||||
|
'/nodes?driver=fake',
|
||||||
|
headers={api_base.Version.string: str(api_v1.MIN_VER)},
|
||||||
|
expect_errors=True)
|
||||||
|
self.assertEqual(http_client.NOT_ACCEPTABLE, response.status_code)
|
||||||
|
self.assertTrue(response.json['error_message'])
|
||||||
|
|
||||||
def test_get_console_information(self):
|
def test_get_console_information(self):
|
||||||
node = obj_utils.create_test_node(self.context)
|
node = obj_utils.create_test_node(self.context)
|
||||||
expected_console_info = {'test': 'test-data'}
|
expected_console_info = {'test': 'test-data'}
|
||||||
|
@ -79,6 +79,17 @@ class TestApiUtils(base.TestCase):
|
|||||||
self.assertRaises(exception.NotAcceptable,
|
self.assertRaises(exception.NotAcceptable,
|
||||||
utils.check_allow_specify_fields, ['foo'])
|
utils.check_allow_specify_fields, ['foo'])
|
||||||
|
|
||||||
|
@mock.patch.object(pecan, 'request', spec_set=['version'])
|
||||||
|
def test_check_allow_specify_driver(self, mock_request):
|
||||||
|
mock_request.version.minor = 16
|
||||||
|
self.assertIsNone(utils.check_allow_specify_driver(['fake']))
|
||||||
|
|
||||||
|
@mock.patch.object(pecan, 'request', spec_set=['version'])
|
||||||
|
def test_check_allow_specify_driver_fail(self, mock_request):
|
||||||
|
mock_request.version.minor = 15
|
||||||
|
self.assertRaises(exception.NotAcceptable,
|
||||||
|
utils.check_allow_specify_driver, ['fake'])
|
||||||
|
|
||||||
@mock.patch.object(pecan, 'request', spec_set=['version'])
|
@mock.patch.object(pecan, 'request', spec_set=['version'])
|
||||||
def test_allow_links_node_states_and_driver_properties(self, mock_request):
|
def test_allow_links_node_states_and_driver_properties(self, mock_request):
|
||||||
mock_request.version.minor = 14
|
mock_request.version.minor = 14
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Add support for filtering nodes using the same driver via the API.
|
Loading…
Reference in New Issue
Block a user