Skip to content

Commit c1c3d1c

Browse files
authored
Merge branch 'master' into elliot-barn/upgrading-data-ci-tests-py310
2 parents d5b7297 + 01ad74f commit c1c3d1c

29 files changed

+709
-106
lines changed

.buildkite/build.rayci.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ steps:
9797
- raycudabase
9898
- raycpubase
9999
matrix:
100-
- "3.9"
101100
- "3.10"
102101
- "3.11"
103102
- "3.12"
@@ -123,7 +122,6 @@ steps:
123122
- raycpubaseextra
124123
- raycudabaseextra
125124
matrix:
126-
- "3.9"
127125
- "3.10"
128126
- "3.11"
129127
- "3.12"

.buildkite/cicd.rayci.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ steps:
77
//ci/ray_ci/... //release/... //ci/pipeline/... ci
88
--only-tags=release_unit,ci_unit
99
--cache-test-results --parallelism-per-worker 2
10-
--build-name oss-ci-base_test
10+
--build-name oss-ci-base_test-py3.10
1111
--build-type skip
1212
instance_type: small
1313
depends_on:
14-
- oss-ci-base_test
14+
- oss-ci-base_test-multipy
1515
- forge
1616
tags: tools
1717
- label: ":coral: reef: raydepsets tests"
@@ -20,30 +20,30 @@ steps:
2020
- bazel run //ci/ray_ci:test_in_docker --
2121
//ci/raydepsets/... ci
2222
--cache-test-results
23-
--build-name oss-ci-base_test
23+
--build-name oss-ci-base_test-py3.10
2424
--build-type skip
2525
instance_type: small
2626
depends_on:
27-
- oss-ci-base_test
27+
- oss-ci-base_test-multipy
2828
- forge
2929
tags: tools
3030
- label: ":coral: reef: privileged container tests"
3131
commands:
3232
- bazel run //ci/ray_ci:test_in_docker --
3333
//ci/ray_ci:test_privileged ci
3434
--cache-test-results
35-
--build-name oss-ci-base_test
35+
--build-name oss-ci-base_test-py3.10
3636
--build-type cgroup
3737
--privileged
3838
instance_type: small
3939
depends_on:
40-
- oss-ci-base_test
40+
- oss-ci-base_test-multipy
4141
- forge
4242
tags: tools
4343
- label: ":coral: reef: iwyu tests"
4444
commands:
4545
- bazel test --config iwyu //bazel/tests/cpp:example_test
4646
instance_type: small
47-
depends_on: oss-ci-base_build
48-
job_env: oss-ci-base_build
47+
depends_on: oss-ci-base_build-multipy
48+
job_env: oss-ci-base_build-py3.10
4949
tags: tools

doc/source/cluster/kubernetes/k8s-ecosystem/yunikorn.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ helm install kuberay-operator kuberay/kuberay-operator --version 1.4.2 --set bat
3434

3535
## Step 4: Use Apache YuniKorn for gang scheduling
3636

37-
This example uses gang scheduling with Apache YuniKorn and KubeRay.
37+
This example demonstrates gang scheduling of RayCluster custom resources with Apache YuniKorn and KubeRay. Starting with KubeRay 1.5.0, KubeRay also supports gang scheduling for RayJob custom resources.
3838

3939
First, create a queue with a capacity of 4 CPUs and 6Gi of RAM by editing the ConfigMap:
4040

python/ray/_private/authentication/authentication_constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@
2121
"The authentication token you provided is invalid or incorrect.\n\n"
2222
+ TOKEN_SETUP_INSTRUCTIONS
2323
)
24+
25+
AUTHORIZATION_HEADER_NAME = "authorization"
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import logging
2+
from types import ModuleType
3+
from typing import Dict, Optional
4+
5+
from ray._private.authentication import authentication_constants
6+
from ray.dashboard import authentication_utils as auth_utils
7+
8+
logger = logging.getLogger(__name__)
9+
10+
11+
def get_token_auth_middleware(aiohttp_module: ModuleType):
12+
"""Internal helper to create token auth middleware with provided modules.
13+
14+
Args:
15+
aiohttp_module: The aiohttp module to use
16+
Returns:
17+
An aiohttp middleware function
18+
"""
19+
20+
@aiohttp_module.web.middleware
21+
async def token_auth_middleware(request, handler):
22+
"""Middleware to validate bearer tokens when token authentication is enabled.
23+
24+
In minimal Ray installations (without ray._raylet), this middleware is a no-op
25+
and passes all requests through without authentication.
26+
"""
27+
# No-op if token auth is not enabled or raylet is not available
28+
if not auth_utils.is_token_auth_enabled():
29+
return await handler(request)
30+
31+
auth_header = request.headers.get(
32+
authentication_constants.AUTHORIZATION_HEADER_NAME, ""
33+
)
34+
if not auth_header:
35+
return aiohttp_module.web.Response(
36+
status=401, text="Unauthorized: Missing authentication token"
37+
)
38+
39+
if not auth_utils.validate_request_token(auth_header):
40+
return aiohttp_module.web.Response(
41+
status=403, text="Forbidden: Invalid authentication token"
42+
)
43+
44+
return await handler(request)
45+
46+
return token_auth_middleware
47+
48+
49+
def get_auth_headers_if_auth_enabled(user_headers: Dict[str, str]) -> Dict[str, str]:
50+
51+
if not auth_utils.is_token_auth_enabled():
52+
return {}
53+
54+
from ray._raylet import AuthenticationTokenLoader
55+
56+
# Check if user provided their own Authorization header (case-insensitive)
57+
has_user_auth = any(
58+
key.lower() == authentication_constants.AUTHORIZATION_HEADER_NAME
59+
for key in user_headers.keys()
60+
)
61+
if has_user_auth:
62+
# User has provided their own auth header, don't override
63+
return {}
64+
65+
token_loader = AuthenticationTokenLoader.instance()
66+
auth_headers = token_loader.get_token_for_http_header()
67+
68+
if not auth_headers:
69+
# Token auth enabled but no token found
70+
logger.warning(
71+
"Token authentication is enabled but no token was found. "
72+
"Requests to authenticated clusters will fail."
73+
)
74+
75+
return auth_headers
76+
77+
78+
def format_authentication_http_error(status: int, body: str) -> Optional[str]:
79+
"""Return a user-friendly authentication error message, if applicable."""
80+
81+
if status == 401:
82+
return "Authentication required: {body}\n\n{details}".format(
83+
body=body,
84+
details=authentication_constants.HTTP_REQUEST_MISSING_TOKEN_ERROR_MESSAGE,
85+
)
86+
87+
if status == 403:
88+
return "Authentication failed: {body}\n\n{details}".format(
89+
body=body,
90+
details=authentication_constants.HTTP_REQUEST_INVALID_TOKEN_ERROR_MESSAGE,
91+
)
92+
93+
return None

python/ray/_private/runtime_env/agent/main.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
get_or_create_event_loop,
99
)
1010
from ray._private import logging_utils
11+
from ray._private.authentication.http_token_authentication import (
12+
get_token_auth_middleware,
13+
)
1114
from ray._private.process_watcher import create_check_raylet_task
1215
from ray._raylet import GcsClient
1316
from ray.core.generated import (
@@ -23,6 +26,7 @@ def import_libs():
2326

2427
import_libs()
2528

29+
import aiohttp # noqa: E402
2630
import runtime_env_consts # noqa: E402
2731
from aiohttp import web # noqa: E402
2832
from runtime_env_agent import RuntimeEnvAgent # noqa: E402
@@ -194,7 +198,7 @@ async def get_runtime_envs_info(request: web.Request) -> web.Response:
194198
body=reply.SerializeToString(), content_type="application/octet-stream"
195199
)
196200

197-
app = web.Application()
201+
app = web.Application(middlewares=[get_token_auth_middleware(aiohttp)])
198202

199203
app.router.add_post("/get_or_create_runtime_env", get_or_create_runtime_env)
200204
app.router.add_post(

python/ray/dashboard/authentication_utils.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
from ray._raylet import (
2-
AuthenticationMode,
3-
get_authentication_mode,
4-
validate_authentication_token,
5-
)
1+
try:
2+
from ray._raylet import (
3+
AuthenticationMode,
4+
get_authentication_mode,
5+
validate_authentication_token,
6+
)
7+
8+
_RAYLET_AVAILABLE = True
9+
except ImportError:
10+
# ray._raylet not available during doc builds
11+
_RAYLET_AVAILABLE = False
612

713

814
def is_token_auth_enabled() -> bool:
@@ -11,6 +17,8 @@ def is_token_auth_enabled() -> bool:
1117
Returns:
1218
bool: True if auth_mode is set to "token", False otherwise
1319
"""
20+
if not _RAYLET_AVAILABLE:
21+
return False
1422
return get_authentication_mode() == AuthenticationMode.TOKEN
1523

1624

@@ -23,7 +31,7 @@ def validate_request_token(auth_header: str) -> bool:
2331
Returns:
2432
bool: True if token is valid, False otherwise
2533
"""
26-
if not auth_header:
34+
if not _RAYLET_AVAILABLE or not auth_header:
2735
return False
2836

2937
# validate_authentication_token expects full "Bearer <token>" format

python/ray/dashboard/http_server_head.py

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
from ray._common.network_utils import build_address, parse_address
2121
from ray._common.usage.usage_lib import TagKey, record_extra_usage_tag
2222
from ray._common.utils import get_or_create_event_loop
23-
from ray.dashboard import authentication_utils as auth_utils
23+
from ray._private.authentication.http_token_authentication import (
24+
get_token_auth_middleware,
25+
)
2426
from ray.dashboard.dashboard_metrics import DashboardPrometheusMetrics
2527
from ray.dashboard.head import DashboardHeadModule
2628

@@ -164,30 +166,6 @@ def get_address(self):
164166
assert self.http_host and self.http_port
165167
return self.http_host, self.http_port
166168

167-
@aiohttp.web.middleware
168-
async def auth_middleware(self, request, handler):
169-
"""Authenticate requests when token auth is enabled."""
170-
171-
# Skip if auth not enabled
172-
if not auth_utils.is_token_auth_enabled():
173-
return await handler(request)
174-
175-
# Extract and validate token
176-
auth_header = request.headers.get("Authorization", "")
177-
178-
if not auth_header:
179-
return aiohttp.web.Response(
180-
status=401, text="Unauthorized: Missing authentication token"
181-
)
182-
183-
# Validate token
184-
if not auth_utils.validate_request_token(auth_header):
185-
return aiohttp.web.Response(
186-
status=403, text="Forbidden: Invalid authentication token"
187-
)
188-
189-
return await handler(request)
190-
191169
@aiohttp.web.middleware
192170
async def path_clean_middleware(self, request, handler):
193171
if request.path.startswith("/static") or request.path.startswith("/logs"):
@@ -273,11 +251,12 @@ async def run(
273251

274252
# Http server should be initialized after all modules loaded.
275253
# working_dir uploads for job submission can be up to 100MiB.
254+
276255
app = aiohttp.web.Application(
277256
client_max_size=ray_constants.DASHBOARD_CLIENT_MAX_SIZE,
278257
middlewares=[
279258
self.metrics_middleware,
280-
self.auth_middleware,
259+
get_token_auth_middleware(aiohttp),
281260
self.path_clean_middleware,
282261
self.browsers_no_post_put_middleware,
283262
self.cache_control_static_middleware,

python/ray/dashboard/modules/dashboard_sdk.py

Lines changed: 10 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
import yaml
1313

1414
import ray
15-
from ray._private.authentication import authentication_constants
15+
from ray._private.authentication.http_token_authentication import (
16+
format_authentication_http_error,
17+
get_auth_headers_if_auth_enabled,
18+
)
1619
from ray._private.runtime_env.packaging import (
1720
create_package,
1821
get_uri_for_directory,
@@ -21,9 +24,7 @@
2124
from ray._private.runtime_env.py_modules import upload_py_modules_if_needed
2225
from ray._private.runtime_env.working_dir import upload_working_dir_if_needed
2326
from ray._private.utils import split_address
24-
from ray._raylet import AuthenticationTokenLoader
2527
from ray.autoscaler._private.cli_logger import cli_logger
26-
from ray.dashboard.authentication_utils import is_token_auth_enabled
2728
from ray.dashboard.modules.job.common import uri_to_http_components
2829
from ray.util.annotations import DeveloperAPI, PublicAPI
2930

@@ -226,7 +227,7 @@ def __init__(
226227
# Headers used for all requests sent to job server, optional and only
227228
# needed for cases like authentication to remote cluster.
228229
self._headers = cluster_info.headers or {}
229-
self._headers.update(**self._get_auth_headers())
230+
self._headers.update(**get_auth_headers_if_auth_enabled(self._headers))
230231

231232
# Set SSL verify parameter for the requests library and create an ssl_context
232233
# object when needed for the aiohttp library.
@@ -247,36 +248,6 @@ def __init__(
247248
else:
248249
self._ssl_context = None
249250

250-
def _get_auth_headers(self) -> Dict[str, str]:
251-
"""Get authentication headers if token auth is enabled.
252-
253-
Returns:
254-
dict: Authentication headers to merge with request headers.
255-
Empty dict if no auth needed or token unavailable.
256-
"""
257-
if not is_token_auth_enabled():
258-
return {}
259-
260-
# Check if user provided their own Authorization header (case-insensitive)
261-
has_user_auth = any(
262-
key.lower() == "authorization" for key in self._headers.keys()
263-
)
264-
if has_user_auth:
265-
# User has provided their own auth header, don't override
266-
return {}
267-
268-
token_loader = AuthenticationTokenLoader.instance()
269-
auth_headers = token_loader.get_token_for_http_header()
270-
271-
if not auth_headers:
272-
# Token auth enabled but no token found
273-
logger.warning(
274-
"Token authentication is enabled but no token was found. "
275-
"Requests to authenticated clusters will fail."
276-
)
277-
278-
return auth_headers
279-
280251
def _check_connection_and_version(
281252
self, min_version: str = "1.9", version_error_message: str = None
282253
):
@@ -348,18 +319,11 @@ def _do_request(
348319
)
349320

350321
# Check for authentication errors and provide helpful messages
351-
if response.status_code == 401:
352-
# Unauthorized - missing or no token provided
353-
raise RuntimeError(
354-
f"Authentication required: {response.text}\n\n"
355-
+ authentication_constants.HTTP_REQUEST_MISSING_TOKEN_ERROR_MESSAGE
356-
)
357-
elif response.status_code == 403:
358-
# Forbidden - invalid token
359-
raise RuntimeError(
360-
f"Authentication failed: {response.text}\n\n"
361-
+ authentication_constants.HTTP_REQUEST_INVALID_TOKEN_ERROR_MESSAGE
362-
)
322+
formatted_error = format_authentication_http_error(
323+
response.status_code, response.text
324+
)
325+
if formatted_error:
326+
raise RuntimeError(formatted_error)
363327

364328
return response
365329

0 commit comments

Comments
 (0)