Add support for filtering for sharded nodes

This request parameter will allow an operator to ask the question
"Do I need to assign shards to any of my nodes?".

Change-Id: I26b745e5ef2b320a8d8a0667ac61c080fcdcd576
This commit is contained in:
Jay Faulkner 2023-02-01 14:54:03 -08:00
parent 28167f18f8
commit a0c1fd8888
5 changed files with 45 additions and 12 deletions

View File

@ -286,7 +286,7 @@ provision state, and maintenance setting for each Node.
Introduced the ``lessee`` field. Introduced the ``lessee`` field.
.. versionadded:: 1.82 .. versionadded:: 1.82
Introduced the ``shard`` field. Introduced the ``shard`` field. Introduced the ``sharded`` request parameter.
Normal response codes: 200 Normal response codes: 200
@ -309,6 +309,7 @@ Request
- owner: owner - owner: owner
- lessee: lessee - lessee: lessee
- shard: req_shard - shard: req_shard
- sharded: req_sharded
- description_contains: r_description_contains - description_contains: r_description_contains
- fields: fields - fields: fields
- limit: limit - limit: limit
@ -381,7 +382,7 @@ Nova instance, eg. with a request to ``v1/nodes/detail?instance_uuid={NOVA INSTA
Introduced the ``lessee`` field. Introduced the ``lessee`` field.
.. versionadded:: 1.82 .. versionadded:: 1.82
Introduced the ``shard`` field. Introduced the ``shard`` field. Introduced the ``sharded`` request parameter.
Normal response codes: 200 Normal response codes: 200
@ -404,6 +405,7 @@ Request
- owner: owner - owner: owner
- lessee: lessee - lessee: lessee
- shard: req_shard - shard: req_shard
- sharded: req_sharded
- description_contains: r_description_contains - description_contains: r_description_contains
- limit: limit - limit: limit
- marker: marker - marker: marker

View File

@ -1827,6 +1827,13 @@ req_shard:
in: body in: body
required: false required: false
type: array type: array
req_sharded:
description: |
When true, filter the list of returned Nodes, and only return the ones with
a non-null ``shard`` value. When false, the inverse filter is performed.
in: body
required: false
type: boolean
req_standalone_ports_supported: req_standalone_ports_supported:
description: | description: |
Indicates whether ports that are members of this portgroup can be Indicates whether ports that are members of this portgroup can be

View File

@ -2071,7 +2071,8 @@ class NodesController(rest.RestController):
fields=None, fault=None, conductor_group=None, fields=None, fault=None, conductor_group=None,
detail=None, conductor=None, owner=None, detail=None, conductor=None, owner=None,
lessee=None, project=None, lessee=None, project=None,
description_contains=None, shard=None): description_contains=None, shard=None,
sharded=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."))
@ -2112,7 +2113,8 @@ class NodesController(rest.RestController):
'project': project, 'project': project,
'description_contains': description_contains, 'description_contains': description_contains,
'retired': retired, 'retired': retired,
'instance_uuid': instance_uuid 'instance_uuid': instance_uuid,
'sharded': sharded
} }
filters = {} filters = {}
for key, value in possible_filters.items(): for key, value in possible_filters.items():
@ -2257,14 +2259,14 @@ class NodesController(rest.RestController):
detail=args.boolean, conductor=args.string, detail=args.boolean, conductor=args.string,
owner=args.string, description_contains=args.string, owner=args.string, description_contains=args.string,
lessee=args.string, project=args.string, lessee=args.string, project=args.string,
shard=args.string_list) shard=args.string_list, sharded=args.boolean)
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, retired=None, provision_state=None, maintenance=None, retired=None, provision_state=None,
marker=None, limit=None, sort_key='id', sort_dir='asc', marker=None, limit=None, sort_key='id', sort_dir='asc',
driver=None, fields=None, resource_class=None, fault=None, driver=None, fields=None, resource_class=None, fault=None,
conductor_group=None, detail=None, conductor=None, conductor_group=None, detail=None, conductor=None,
owner=None, description_contains=None, lessee=None, owner=None, description_contains=None, lessee=None,
project=None, shard=None): project=None, shard=None, sharded=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
@ -2310,6 +2312,9 @@ class NodesController(rest.RestController):
:param description_contains: Optional string value to get only nodes :param description_contains: Optional string value to get only nodes
with description field contains matching with description field contains matching
value. value.
:param sharded: Optional boolean whether to return a list of
nodes with or without a shard set. May be combined
with other parameters.
""" """
project = api_utils.check_list_policy('node', project) project = api_utils.check_list_policy('node', project)
@ -2325,6 +2330,8 @@ class NodesController(rest.RestController):
api_utils.check_allow_filter_by_owner(owner) api_utils.check_allow_filter_by_owner(owner)
api_utils.check_allow_filter_by_lessee(lessee) api_utils.check_allow_filter_by_lessee(lessee)
api_utils.check_allow_filter_by_shard(shard) api_utils.check_allow_filter_by_shard(shard)
# Sharded is guarded by the same API version as shard
api_utils.check_allow_filter_by_shard(sharded)
fields = api_utils.get_request_return_fields(fields, detail, fields = api_utils.get_request_return_fields(fields, detail,
_DEFAULT_RETURN_FIELDS) _DEFAULT_RETURN_FIELDS)
@ -2341,8 +2348,8 @@ class NodesController(rest.RestController):
detail=detail, detail=detail,
conductor=conductor, conductor=conductor,
owner=owner, lessee=lessee, owner=owner, lessee=lessee,
shard=shard, project=project, shard=shard, sharded=sharded,
**extra_args) project=project, **extra_args)
@METRICS.timer('NodesController.detail') @METRICS.timer('NodesController.detail')
@method.expose() @method.expose()
@ -2355,14 +2362,14 @@ class NodesController(rest.RestController):
conductor_group=args.string, conductor=args.string, conductor_group=args.string, conductor=args.string,
owner=args.string, description_contains=args.string, owner=args.string, description_contains=args.string,
lessee=args.string, project=args.string, lessee=args.string, project=args.string,
shard=args.string_list) shard=args.string_list, sharded=args.boolean)
def detail(self, chassis_uuid=None, instance_uuid=None, associated=None, def detail(self, chassis_uuid=None, instance_uuid=None, associated=None,
maintenance=None, retired=None, provision_state=None, maintenance=None, retired=None, provision_state=None,
marker=None, limit=None, sort_key='id', sort_dir='asc', marker=None, limit=None, sort_key='id', sort_dir='asc',
driver=None, resource_class=None, fault=None, driver=None, resource_class=None, fault=None,
conductor_group=None, conductor=None, owner=None, conductor_group=None, conductor=None, owner=None,
description_contains=None, lessee=None, project=None, description_contains=None, lessee=None, project=None,
shard=None): shard=None, sharded=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
@ -2403,6 +2410,9 @@ class NodesController(rest.RestController):
:param description_contains: Optional string value to get only nodes :param description_contains: Optional string value to get only nodes
with description field contains matching with description field contains matching
value. value.
:param sharded: Optional boolean whether to return a list of
nodes with or without a shard set. May be combined
with other parameters.
""" """
project = api_utils.check_list_policy('node', project) project = api_utils.check_list_policy('node', project)
@ -2421,6 +2431,8 @@ class NodesController(rest.RestController):
api_utils.check_allow_filter_by_conductor(conductor) api_utils.check_allow_filter_by_conductor(conductor)
api_utils.check_allow_filter_by_shard(shard) api_utils.check_allow_filter_by_shard(shard)
# Sharded is guarded by the same API version as shard
api_utils.check_allow_filter_by_shard(sharded)
extra_args = {'description_contains': description_contains} extra_args = {'description_contains': description_contains}
return self._get_nodes_collection(chassis_uuid, instance_uuid, return self._get_nodes_collection(chassis_uuid, instance_uuid,
@ -2435,7 +2447,7 @@ class NodesController(rest.RestController):
conductor=conductor, conductor=conductor,
owner=owner, lessee=lessee, owner=owner, lessee=lessee,
project=project, shard=shard, project=project, shard=shard,
**extra_args) sharded=sharded, **extra_args)
@METRICS.timer('NodesController.validate') @METRICS.timer('NodesController.validate')
@method.expose() @method.expose()

View File

@ -401,7 +401,8 @@ class Connection(api.Connection):
for field in ('uuid', 'provision_state', 'shard')} for field in ('uuid', 'provision_state', 'shard')}
_NODE_NON_NULL_FILTERS = {'associated': 'instance_uuid', _NODE_NON_NULL_FILTERS = {'associated': 'instance_uuid',
'reserved': 'reservation', 'reserved': 'reservation',
'with_power_state': 'power_state'} 'with_power_state': 'power_state',
'sharded': 'shard'}
_NODE_FILTERS = ({'chassis_uuid', 'reserved_by_any_of', _NODE_FILTERS = ({'chassis_uuid', 'reserved_by_any_of',
'provisioned_before', 'inspection_started_before', 'provisioned_before', 'inspection_started_before',
'description_contains', 'project'} 'description_contains', 'project'}

View File

@ -8028,6 +8028,17 @@ class TestNodeShardGets(test_api_base.BaseApiTest):
expect_errors=True, headers=headers) expect_errors=True, headers=headers)
self.assertEqual(http_client.NOT_ACCEPTABLE, result.status_code) self.assertEqual(http_client.NOT_ACCEPTABLE, result.status_code)
def test_filtering_by_sharded(self):
obj_utils.create_test_node(self.context, uuid=uuid.uuid4())
obj_utils.create_test_node(self.context, uuid=uuid.uuid4())
# We now have one node in shard foo (setUp) and two unsharded.
result_true = self.get_json(
'/nodes?sharded=true', headers=self.headers)
result_false = self.get_json(
'/nodes?sharded=false', headers=self.headers)
self.assertEqual(1, len(result_true['nodes']))
self.assertEqual(2, len(result_false['nodes']))
@mock.patch.object(rpcapi.ConductorAPI, 'create_node', @mock.patch.object(rpcapi.ConductorAPI, 'create_node',
lambda _api, _ctx, node, _topic: _create_node_locally(node)) lambda _api, _ctx, node, _topic: _create_node_locally(node))