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:
Kan 2016-01-06 03:13:50 +00:00
parent 6d8768e87c
commit d3859a79ba
7 changed files with 85 additions and 11 deletions

View File

@ -32,6 +32,9 @@ always requests the newest supported API version.
API Versions History
--------------------
**1.16**
Add ability to filter nodes by driver.
**1.15**
Add ability to do manual cleaning when a node is in the manageable

View File

@ -974,8 +974,8 @@ class NodesController(rest.RestController):
def _get_nodes_collection(self, chassis_uuid, instance_uuid, associated,
maintenance, provision_state, marker, limit,
sort_key, sort_dir, resource_url=None,
fields=None):
sort_key, sort_dir, driver=None,
resource_url=None, fields=None):
if self.from_chassis and not chassis_uuid:
raise exception.MissingParameterValue(
_("Chassis id not specified."))
@ -1005,6 +1005,8 @@ class NodesController(rest.RestController):
filters['maintenance'] = maintenance
if provision_state:
filters['provision_state'] = provision_state
if driver:
filters['driver'] = driver
nodes = objects.Node.list(pecan.request.context, limit, marker_obj,
sort_key=sort_key, sort_dir=sort_dir,
@ -1083,14 +1085,15 @@ class NodesController(rest.RestController):
@expose.expose(NodeCollection, types.uuid, types.uuid, types.boolean,
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,
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.
:param chassis_uuid: Optional UUID of a chassis, to get only nodes for
that chassis.
that chassis.
:param instance_uuid: Optional UUID of an instance, to find the node
associated with that instance.
:param associated: Optional boolean whether to return a list of
@ -1105,25 +1108,28 @@ class NodesController(rest.RestController):
:param limit: maximum number of resources to return in a single result.
:param sort_key: column to sort results by. Default: id.
: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
of the resource to be returned.
of the resource to be returned.
"""
api_utils.check_allow_specify_fields(fields)
api_utils.check_for_invalid_state_and_allow_filter(provision_state)
api_utils.check_allow_specify_driver(driver)
if fields is None:
fields = _DEFAULT_RETURN_FIELDS
return self._get_nodes_collection(chassis_uuid, instance_uuid,
associated, maintenance,
provision_state, marker,
limit, sort_key, sort_dir,
fields=fields)
driver, fields=fields)
@expose.expose(NodeCollection, types.uuid, types.uuid, types.boolean,
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,
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.
: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 sort_key: column to sort results by. Default: id.
: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_allow_specify_driver(driver)
# /detail should only work against collections
parent = pecan.request.path.split('/')[:-1][-1]
if parent != "nodes":
@ -1154,7 +1163,7 @@ class NodesController(rest.RestController):
associated, maintenance,
provision_state, marker,
limit, sort_key, sort_dir,
resource_url)
driver, resource_url)
@expose.expose(wtypes.text, types.uuid_or_name, types.uuid)
def validate(self, node=None, node_uuid=None):

View File

@ -214,6 +214,20 @@ def check_for_invalid_state_and_allow_filter(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():
"""Return node state to use by default when creating new nodes.

View File

@ -45,6 +45,7 @@ BASE_VERSION = 1
# 1. '/v1/nodes/<uuid>/states'
# 2. '/v1/drivers/<driver-name>/properties'
# v1.15: Add ability to do manual cleaning of nodes
# v1.16: Add ability to filter nodes by driver.
MINOR_0_JUNO = 0
MINOR_1_INITIAL_VERSION = 1
@ -62,11 +63,12 @@ MINOR_12_RAID_CONFIG = 12
MINOR_13_ABORT_VERB = 13
MINOR_14_LINKS_NODESTATES_DRIVERPROPERTIES = 14
MINOR_15_MANUAL_CLEAN = 15
MINOR_16_DRIVER_FILTER = 16
# 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_15_MANUAL_CLEAN
MINOR_MAX_VERSION = MINOR_16_DRIVER_FILTER
# String representations of the minor and maximum versions
MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)

View File

@ -695,6 +695,38 @@ class TestListNodes(test_api_base.BaseApiTest):
self.assertEqual(http_client.NOT_ACCEPTABLE, response.status_code)
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):
node = obj_utils.create_test_node(self.context)
expected_console_info = {'test': 'test-data'}

View File

@ -79,6 +79,17 @@ class TestApiUtils(base.TestCase):
self.assertRaises(exception.NotAcceptable,
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'])
def test_allow_links_node_states_and_driver_properties(self, mock_request):
mock_request.version.minor = 14

View File

@ -0,0 +1,3 @@
---
features:
- Add support for filtering nodes using the same driver via the API.