From d4e831f670c06998a2fbe3d5fede0f9bc64cd87b Mon Sep 17 00:00:00 2001 From: Ricky Xu Date: Tue, 29 Aug 2023 13:11:34 -0700 Subject: [PATCH] [core][state] Case insensitive match for string value for state API filter. (#34577) --------- Signed-off-by: Ricky Xu Signed-off-by: Jim Thompson --- dashboard/state_aggregator.py | 15 ++++++++-- python/ray/tests/test_state_api.py | 41 ++++++++++++++++++++++++++ python/ray/util/state/api.py | 8 +++++ python/ray/util/state/state_cli.py | 7 ++++- python/ray/util/state/state_manager.py | 2 ++ 5 files changed, 70 insertions(+), 3 deletions(-) diff --git a/dashboard/state_aggregator.py b/dashboard/state_aggregator.py index ac87f513b151e..9b26fa4ac77cb 100644 --- a/dashboard/state_aggregator.py +++ b/dashboard/state_aggregator.py @@ -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. " diff --git a/python/ray/tests/test_state_api.py b/python/ray/tests/test_state_api.py index 47e7e2b129cf7..4e8c193f75801 100644 --- a/python/ray/tests/test_state_api.py +++ b/python/ray/tests/test_state_api.py @@ -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): """ diff --git a/python/ray/util/state/api.py b/python/ray/util/state/api.py index c9ce14f5bb722..67dd080b6b5d0 100644 --- a/python/ray/util/state/api.py +++ b/python/ray/util/state/api.py @@ -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`) @@ -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`) @@ -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`) @@ -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`) @@ -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`) @@ -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`) @@ -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`) @@ -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`) diff --git a/python/ray/util/state/state_cli.py b/python/ray/util/state/state_cli.py index 3671afe6bbf11..3b8e528443bb3 100644 --- a/python/ray/util/state/state_cli.py +++ b/python/ray/util/state/state_cli.py @@ -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, ) @@ -536,6 +537,10 @@ def ray_list( Raises: :class:`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("-", "_")) diff --git a/python/ray/util/state/state_manager.py b/python/ray/util/state/state_manager.py index 4c735650f2fb3..8314c6c306e2a 100644 --- a/python/ray/util/state/state_manager.py +++ b/python/ray/util/state/state_manager.py @@ -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)