mirror of
https://github.com/wassname/ray.git
synced 2026-06-28 21:29:53 +08:00
73fd78316d
* Change authentication schema. Authentication implementation. * Formatting. * Fix a minor style. * Fix tests. * Removed url validation.
262 lines
8.2 KiB
Python
262 lines
8.2 KiB
Python
import pytest
|
|
import requests
|
|
|
|
from unittest.mock import patch
|
|
|
|
from ray.dashboard.metrics_exporter.actions import ActionHandler
|
|
from ray.dashboard.metrics_exporter.client import MetricsExportClient
|
|
from ray.dashboard.metrics_exporter.client import Exporter
|
|
from ray.dashboard.metrics_exporter.schema import (AuthResponse, BaseModel,
|
|
ValidationError, Field)
|
|
|
|
MOCK_DASHBOARD_ID = "1234"
|
|
MOCK_DASHBOARD_ADDRESS = "http://127.0.0.1:9081"
|
|
MOCK_ACCESS_TOKEN = "1234"
|
|
|
|
|
|
def _setup_client_and_exporter(controller):
|
|
exporter = Exporter(MOCK_DASHBOARD_ID, MOCK_DASHBOARD_ADDRESS, controller)
|
|
client = MetricsExportClient(MOCK_DASHBOARD_ADDRESS, controller,
|
|
MOCK_DASHBOARD_ID, exporter)
|
|
return exporter, client
|
|
|
|
|
|
"""
|
|
Test Exporter
|
|
"""
|
|
|
|
|
|
@patch("ray.dashboard.dashboard.DashboardController")
|
|
def test_verify_exporter_cannot_run_without_access_token(mock_controller):
|
|
exporter, client = _setup_client_and_exporter(mock_controller)
|
|
# Should raise an assertion error because there's no access token set.
|
|
with pytest.raises(AssertionError):
|
|
exporter.run()
|
|
|
|
|
|
"""
|
|
Test Client
|
|
"""
|
|
|
|
|
|
@patch("ray.dashboard.dashboard.DashboardController")
|
|
@patch(
|
|
"ray.dashboard.metrics_exporter.api.authentication_request",
|
|
side_effect=requests.exceptions.HTTPError)
|
|
def test_client_invalid_request_status_returned(auth_request, mock_controller):
|
|
"""
|
|
If authentication request fails with an invalid status code,
|
|
`start_exporting_metrics` should fail.
|
|
"""
|
|
exporter, client = _setup_client_and_exporter(mock_controller)
|
|
|
|
# authenticate should throw an exception because API request fails.
|
|
with pytest.raises(requests.exceptions.HTTPError):
|
|
client._authenticate()
|
|
|
|
# This should fail because authentication throws an exception.
|
|
result, error = client.start_exporting_metrics()
|
|
assert result is False
|
|
|
|
|
|
@patch("ray.dashboard.dashboard.DashboardController")
|
|
@patch("ray.dashboard.metrics_exporter.api.authentication_request")
|
|
def test_authentication(auth_request, mock_controller):
|
|
auth_request.return_value = AuthResponse(
|
|
access_token_dashboard=MOCK_ACCESS_TOKEN,
|
|
access_token_ingest=MOCK_ACCESS_TOKEN)
|
|
exporter, client = _setup_client_and_exporter(mock_controller)
|
|
|
|
assert client.enabled is False
|
|
client._authenticate()
|
|
assert client.dashboard_url == "{address}/dashboard/{access_token}".format(
|
|
address=MOCK_DASHBOARD_ADDRESS, access_token=MOCK_ACCESS_TOKEN)
|
|
assert client.enabled is True
|
|
|
|
|
|
@patch.object(Exporter, "start")
|
|
@patch("ray.dashboard.dashboard.DashboardController")
|
|
@patch("ray.dashboard.metrics_exporter.api.authentication_request")
|
|
def test_start_exporting_metrics_without_authentication(
|
|
auth_request, mock_controller, start):
|
|
"""
|
|
`start_exporting_metrics` should trigger authentication if users
|
|
are not authenticated.
|
|
"""
|
|
auth_request.return_value = AuthResponse(
|
|
access_token_dashboard=MOCK_ACCESS_TOKEN,
|
|
access_token_ingest=MOCK_ACCESS_TOKEN)
|
|
exporter, client = _setup_client_and_exporter(mock_controller)
|
|
|
|
# start_exporting_metrics should succeed.
|
|
result, error = client.start_exporting_metrics()
|
|
assert result is True
|
|
assert error is None
|
|
assert client.enabled is True
|
|
|
|
|
|
@patch.object(Exporter, "start")
|
|
@patch("ray.dashboard.dashboard.DashboardController")
|
|
@patch("ray.dashboard.metrics_exporter.api.authentication_request")
|
|
def test_start_exporting_metrics_with_authentication(auth_request,
|
|
mock_controller, start):
|
|
"""
|
|
If users are already authenticated, `start_exporting_metrics`
|
|
should not authenticate users.
|
|
"""
|
|
auth_request.return_value = AuthResponse(
|
|
access_token_dashboard=MOCK_ACCESS_TOKEN,
|
|
access_token_ingest=MOCK_ACCESS_TOKEN)
|
|
exporter, client = _setup_client_and_exporter(mock_controller)
|
|
# Already authenticated.
|
|
client._authenticate()
|
|
assert client.enabled is True
|
|
|
|
result, error = client.start_exporting_metrics()
|
|
# Auth request should be called only once because
|
|
# it was already authenticated.
|
|
auth_request.call_count == 1
|
|
assert result is True
|
|
assert error is None
|
|
|
|
|
|
@patch.object(Exporter, "start")
|
|
@patch("ray.dashboard.dashboard.DashboardController")
|
|
@patch("ray.dashboard.metrics_exporter.api.authentication_request")
|
|
def test_start_exporting_metrics_succeed(auth_request, mock_controller, start):
|
|
auth_request.return_value = AuthResponse(
|
|
access_token_dashboard=MOCK_ACCESS_TOKEN,
|
|
access_token_ingest=MOCK_ACCESS_TOKEN)
|
|
exporter, client = _setup_client_and_exporter(mock_controller)
|
|
|
|
result, error = client.start_exporting_metrics()
|
|
assert result is True
|
|
assert error is None
|
|
assert client.is_exporting_started is True
|
|
start.call_count == 1
|
|
|
|
with pytest.raises(AssertionError):
|
|
client.start_exporting_metrics()
|
|
|
|
|
|
"""
|
|
BaseModel Test
|
|
"""
|
|
|
|
|
|
def test_base_model():
|
|
DEFAULT_VALUE = "default"
|
|
|
|
class A(BaseModel):
|
|
__schema__ = {
|
|
"a": Field(required=True, default=None, type=str),
|
|
"b": Field(required=False, default=DEFAULT_VALUE, type=str)
|
|
}
|
|
|
|
# Test the correct case.
|
|
obj = {"a": "1", "b": "1"}
|
|
a = A.parse_obj(obj)
|
|
assert a.a == "1"
|
|
assert a.b == "1"
|
|
assert a._dict == obj
|
|
string = "{name}\n{dict}".format(name=A.__name__, dict=str(obj))
|
|
assert str(a) == string
|
|
|
|
# Test wrong types. It is not checked in the current implementation.
|
|
obj = {"a": 1, "b": 2}
|
|
a = A.parse_obj(obj)
|
|
assert a.a == 1
|
|
assert a.b == 2
|
|
|
|
# Test wrong types. parse_obj can only parse dictionary.
|
|
obj = None
|
|
with pytest.raises(AssertionError):
|
|
a = A.parse_obj(obj)
|
|
|
|
# Test when required fields are not provided.
|
|
obj = {"b": "1"}
|
|
with pytest.raises(ValidationError):
|
|
a = A.parse_obj(obj)
|
|
|
|
# Test optional fields are set to default when fields are not given.
|
|
obj = {"a": "1"}
|
|
a = A.parse_obj(obj)
|
|
assert a.b == DEFAULT_VALUE
|
|
|
|
# Test when fields that are not defined in the schema is given.
|
|
# It should be ignoered
|
|
obj = {"a": "a", "b": "b", "c": "c"}
|
|
a = A.parse_obj(obj)
|
|
assert a.a == "a"
|
|
assert a.b == "b"
|
|
assert a.c == "c"
|
|
|
|
|
|
"""
|
|
Test Action Handler
|
|
"""
|
|
|
|
|
|
def _get_mock_kill_action():
|
|
return {
|
|
"type": "KILL_ACTOR",
|
|
"actor_id": "1234",
|
|
"ip_address": "1234",
|
|
"port": 30
|
|
}
|
|
|
|
|
|
@patch("ray.dashboard.dashboard.DashboardController")
|
|
def test_handle_kill_action(mock_controller):
|
|
action_handler = ActionHandler(mock_controller)
|
|
kill_action = _get_mock_kill_action()
|
|
action_handler.handle_kill_action(kill_action)
|
|
assert mock_controller.kill_actor.call_count == 1
|
|
|
|
|
|
@patch("ray.dashboard.dashboard.DashboardController")
|
|
def test_handle_kill_action_invalid_dict(mock_controller):
|
|
action_handler = ActionHandler(mock_controller)
|
|
kill_action = {"type": "KILL_ACTOR", "ip_address": "1234", "port": 30}
|
|
|
|
with pytest.raises(ValidationError):
|
|
action_handler.handle_kill_action(kill_action)
|
|
|
|
|
|
@patch("ray.dashboard.dashboard.DashboardController")
|
|
def test_handle_actions_many_kill_actor(mock_controller):
|
|
action_handler = ActionHandler(mock_controller)
|
|
# 10 actions required.
|
|
actions = [_get_mock_kill_action() for _ in range(10)]
|
|
|
|
action_handler.handle_actions(actions)
|
|
assert mock_controller.kill_actor.call_count == 10
|
|
|
|
|
|
@patch("ray.dashboard.dashboard.DashboardController")
|
|
def test_handle_actions_kill_actor_and_mixed_type(mock_controller):
|
|
action_handler = ActionHandler(mock_controller)
|
|
wrong_type_action = {"type": "NON_EXIST"}
|
|
actions = [
|
|
_get_mock_kill_action(), wrong_type_action,
|
|
_get_mock_kill_action()
|
|
]
|
|
|
|
action_handler.handle_actions(actions)
|
|
assert mock_controller.kill_actor.call_count == 2
|
|
|
|
|
|
@patch("ray.dashboard.dashboard.DashboardController")
|
|
def test_handle_actions_only_wrong_type(mock_controller):
|
|
action_handler = ActionHandler(mock_controller)
|
|
wrong_type_action = {"type": "NON_EXIST"}
|
|
actions = [wrong_type_action for _ in range(10)]
|
|
|
|
action_handler.handle_actions(actions)
|
|
assert mock_controller.kill_actor.call_count == 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
sys.exit(pytest.main(["-v", __file__]))
|