Skip to content

Commit

Permalink
[core][state] Case insensitive match for string value for state API f…
Browse files Browse the repository at this point in the history
…ilter. (ray-project#34577)

---------

Signed-off-by: Ricky Xu <xuchen727@hotmail.com>
Signed-off-by: e428265 <arvind.chandramouli@lmco.com>
  • Loading branch information
rickyyx authored and arvind-chandra committed Aug 31, 2023
1 parent 6e1c7bc commit 9a6c65d
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 3 deletions.
15 changes: 13 additions & 2 deletions dashboard/state_aggregator.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,20 @@ def _filter(
if filter_column not in datum:
match = False
elif filter_predicate == "=":
match = datum[filter_column] == filter_value
if isinstance(filter_value, str) and isinstance(
datum[filter_column], str
):
# Case insensitive match for string filter values.
match = datum[filter_column].lower() == filter_value.lower()
else:
match = datum[filter_column] == filter_value
elif filter_predicate == "!=":
match = datum[filter_column] != filter_value
if isinstance(filter_value, str) and isinstance(
datum[filter_column], str
):
match = datum[filter_column].lower() != filter_value.lower()
else:
match = datum[filter_column] != filter_value
else:
raise ValueError(
f"Unsupported filter predicate {filter_predicate} is given. "
Expand Down
41 changes: 41 additions & 0 deletions python/ray/tests/test_state_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3012,6 +3012,47 @@ def verify():
assert dead_actor_id not in result.output
assert alive_actor_id in result.output

"""
Test case insensitive match on string fields.
"""

@ray.remote
def task():
pass

ray.get(task.remote())

def verify():
result_1 = list_tasks(filters=[("name", "=", "task")])
result_2 = list_tasks(filters=[("name", "=", "TASK")])
assert result_1 == result_2

result_1 = list_tasks(filters=[("state", "=", "FINISHED")])
result_2 = list_tasks(filters=[("state", "=", "finished")])
assert result_1 == result_2

result_1 = list_objects(
filters=[("pid", "=", pid), ("reference_type", "=", "LOCAL_REFERENCE")]
)

result_2 = list_objects(
filters=[("pid", "=", pid), ("reference_type", "=", "local_reference")]
)
assert result_1 == result_2

result_1 = list_actors(filters=[("state", "=", "DEAD")])
result_2 = list_actors(filters=[("state", "=", "dead")])

assert result_1 == result_2

result_1 = list_actors(filters=[("state", "!=", "DEAD")])
result_2 = list_actors(filters=[("state", "!=", "dead")])

assert result_1 == result_2
return True

wait_for_condition(verify)


def test_data_truncate(shutdown_only, monkeypatch):
"""
Expand Down
8 changes: 8 additions & 0 deletions python/ray/util/state/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,7 @@ def list_actors(
If None, it will be resolved automatically from an initialized ray.
filters: List of tuples of filter key, predicate (=, or !=), and
the filter value. E.g., `("id", "=", "abcd")`
String filter values are case-insensitive.
limit: Max number of entries returned by the state backend.
timeout: Max timeout value for the state APIs requests made.
detail: When True, more details info (specified in `ActorState`)
Expand Down Expand Up @@ -843,6 +844,7 @@ def list_placement_groups(
If None, it will be resolved automatically from an initialized ray.
filters: List of tuples of filter key, predicate (=, or !=), and
the filter value. E.g., `("state", "=", "abcd")`
String filter values are case-insensitive.
limit: Max number of entries returned by the state backend.
timeout: Max timeout value for the state APIs requests made.
detail: When True, more details info (specified in `PlacementGroupState`)
Expand Down Expand Up @@ -887,6 +889,7 @@ def list_nodes(
If None, it will be resolved automatically from an initialized ray.
filters: List of tuples of filter key, predicate (=, or !=), and
the filter value. E.g., `("node_name", "=", "abcd")`
String filter values are case-insensitive.
limit: Max number of entries returned by the state backend.
timeout: Max timeout value for the state APIs requests made.
detail: When True, more details info (specified in `NodeState`)
Expand Down Expand Up @@ -932,6 +935,7 @@ def list_jobs(
If None, it will be resolved automatically from an initialized ray.
filters: List of tuples of filter key, predicate (=, or !=), and
the filter value. E.g., `("status", "=", "abcd")`
String filter values are case-insensitive.
limit: Max number of entries returned by the state backend.
timeout: Max timeout value for the state APIs requests made.
detail: When True, more details info (specified in `JobState`)
Expand Down Expand Up @@ -977,6 +981,7 @@ def list_workers(
If None, it will be resolved automatically from an initialized ray.
filters: List of tuples of filter key, predicate (=, or !=), and
the filter value. E.g., `("is_alive", "=", "True")`
String filter values are case-insensitive.
limit: Max number of entries returned by the state backend.
timeout: Max timeout value for the state APIs requests made.
detail: When True, more details info (specified in `WorkerState`)
Expand Down Expand Up @@ -1022,6 +1027,7 @@ def list_tasks(
If None, it will be resolved automatically from an initialized ray.
filters: List of tuples of filter key, predicate (=, or !=), and
the filter value. E.g., `("is_alive", "=", "True")`
String filter values are case-insensitive.
limit: Max number of entries returned by the state backend.
timeout: Max timeout value for the state APIs requests made.
detail: When True, more details info (specified in `WorkerState`)
Expand Down Expand Up @@ -1067,6 +1073,7 @@ def list_objects(
If None, it will be resolved automatically from an initialized ray.
filters: List of tuples of filter key, predicate (=, or !=), and
the filter value. E.g., `("ip", "=", "0.0.0.0")`
String filter values are case-insensitive.
limit: Max number of entries returned by the state backend.
timeout: Max timeout value for the state APIs requests made.
detail: When True, more details info (specified in `ObjectState`)
Expand Down Expand Up @@ -1112,6 +1119,7 @@ def list_runtime_envs(
If None, it will be resolved automatically from an initialized ray.
filters: List of tuples of filter key, predicate (=, or !=), and
the filter value. E.g., `("node_id", "=", "abcdef")`
String filter values are case-insensitive.
limit: Max number of entries returned by the state backend.
timeout: Max timeout value for the state APIs requests made.
detail: When True, more details info (specified in `RuntimeEnvState`)
Expand Down
7 changes: 6 additions & 1 deletion python/ray/util/state/state_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,8 @@ def ray_get(
"E.g., --filter 'key=value' or --filter 'key!=value'. "
"You can specify multiple --filter options. In this case all predicates "
"are concatenated as AND. For example, --filter key=value --filter key2=value "
"means (key==val) AND (key2==val2)"
"means (key==val) AND (key2==val2), "
"String filter values are case-insensitive."
),
multiple=True,
)
Expand Down Expand Up @@ -536,6 +537,10 @@ def ray_list(
Raises:
:class:`RayStateApiException <ray.util.state.exception.RayStateApiException>`
if the CLI is failed to query the data.
Changes:
- changed in version 2.7: --filter values are case-insensitive.
""" # noqa: E501
# All resource names use '_' rather than '-'. But users options have '-'
resource = StateResource(resource.replace("-", "_"))
Expand Down
2 changes: 2 additions & 0 deletions python/ray/util/state/state_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,8 @@ async def get_all_actor_info(
if key == "actor_id":
req_filters.actor_id = ActorID(hex_to_binary(value)).binary()
elif key == "state":
# Convert to uppercase.
value = value.upper()
if value not in ActorTableData.ActorState.keys():
raise ValueError(f"Invalid actor state for filtering: {value}")
req_filters.state = ActorTableData.ActorState.Value(value)
Expand Down

0 comments on commit 9a6c65d

Please sign in to comment.