Skip to content

Commit f5e154a

Browse files
committed
task(RHOAIENG-35928): Add HTTPRoute detection
1 parent d23d5fd commit f5e154a

File tree

2 files changed

+486
-40
lines changed

2 files changed

+486
-40
lines changed

src/codeflare_sdk/ray/cluster/cluster.py

Lines changed: 132 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -477,8 +477,19 @@ def cluster_uri(self) -> str:
477477
def cluster_dashboard_uri(self) -> str:
478478
"""
479479
Returns a string containing the cluster's dashboard URI.
480+
Tries HTTPRoute first (RHOAI v3.0+), then falls back to OpenShift Routes or Ingresses.
480481
"""
481482
config_check()
483+
484+
# Try HTTPRoute first (RHOAI v3.0+)
485+
# This will return None if HTTPRoute is not found (SDK v0.31.1 and below or Kind clusters)
486+
httproute_url = _get_dashboard_url_from_httproute(
487+
self.config.name, self.config.namespace
488+
)
489+
if httproute_url:
490+
return httproute_url
491+
492+
# Fall back to OpenShift Routes (pre-v3.0) or Ingresses (Kind)
482493
if _is_openshift_cluster():
483494
try:
484495
api_instance = client.CustomObjectsApi(get_api_client())
@@ -1005,45 +1016,51 @@ def _map_to_ray_cluster(rc) -> Optional[RayCluster]:
10051016
status = RayClusterStatus.UNKNOWN
10061017
config_check()
10071018
dashboard_url = None
1008-
if _is_openshift_cluster():
1009-
try:
1010-
api_instance = client.CustomObjectsApi(get_api_client())
1011-
routes = api_instance.list_namespaced_custom_object(
1012-
group="route.openshift.io",
1013-
version="v1",
1014-
namespace=rc["metadata"]["namespace"],
1015-
plural="routes",
1016-
)
1017-
except Exception as e: # pragma: no cover
1018-
return _kube_api_error_handling(e)
10191019

1020-
for route in routes["items"]:
1021-
rc_name = rc["metadata"]["name"]
1022-
if route["metadata"]["name"] == f"ray-dashboard-{rc_name}" or route[
1023-
"metadata"
1024-
]["name"].startswith(f"{rc_name}-ingress"):
1025-
protocol = "https" if route["spec"].get("tls") else "http"
1026-
dashboard_url = f"{protocol}://{route['spec']['host']}"
1027-
else:
1028-
try:
1029-
api_instance = client.NetworkingV1Api(get_api_client())
1030-
ingresses = api_instance.list_namespaced_ingress(
1031-
rc["metadata"]["namespace"]
1032-
)
1033-
except Exception as e: # pragma no cover
1034-
return _kube_api_error_handling(e)
1035-
for ingress in ingresses.items:
1036-
annotations = ingress.metadata.annotations
1037-
protocol = "http"
1038-
if (
1039-
ingress.metadata.name == f"ray-dashboard-{rc['metadata']['name']}"
1040-
or ingress.metadata.name.startswith(f"{rc['metadata']['name']}-ingress")
1041-
):
1042-
if annotations == None:
1043-
protocol = "http"
1044-
elif "route.openshift.io/termination" in annotations:
1045-
protocol = "https"
1046-
dashboard_url = f"{protocol}://{ingress.spec.rules[0].host}"
1020+
# Try HTTPRoute first (RHOAI v3.0+)
1021+
rc_name = rc["metadata"]["name"]
1022+
rc_namespace = rc["metadata"]["namespace"]
1023+
dashboard_url = _get_dashboard_url_from_httproute(rc_name, rc_namespace)
1024+
1025+
# Fall back to OpenShift Routes or Ingresses if HTTPRoute not found
1026+
if not dashboard_url:
1027+
if _is_openshift_cluster():
1028+
try:
1029+
api_instance = client.CustomObjectsApi(get_api_client())
1030+
routes = api_instance.list_namespaced_custom_object(
1031+
group="route.openshift.io",
1032+
version="v1",
1033+
namespace=rc_namespace,
1034+
plural="routes",
1035+
)
1036+
except Exception as e: # pragma: no cover
1037+
return _kube_api_error_handling(e)
1038+
1039+
for route in routes["items"]:
1040+
if route["metadata"]["name"] == f"ray-dashboard-{rc_name}" or route[
1041+
"metadata"
1042+
]["name"].startswith(f"{rc_name}-ingress"):
1043+
protocol = "https" if route["spec"].get("tls") else "http"
1044+
dashboard_url = f"{protocol}://{route['spec']['host']}"
1045+
break
1046+
else:
1047+
try:
1048+
api_instance = client.NetworkingV1Api(get_api_client())
1049+
ingresses = api_instance.list_namespaced_ingress(rc_namespace)
1050+
except Exception as e: # pragma no cover
1051+
return _kube_api_error_handling(e)
1052+
for ingress in ingresses.items:
1053+
annotations = ingress.metadata.annotations
1054+
protocol = "http"
1055+
if (
1056+
ingress.metadata.name == f"ray-dashboard-{rc_name}"
1057+
or ingress.metadata.name.startswith(f"{rc_name}-ingress")
1058+
):
1059+
if annotations == None:
1060+
protocol = "http"
1061+
elif "route.openshift.io/termination" in annotations:
1062+
protocol = "https"
1063+
dashboard_url = f"{protocol}://{ingress.spec.rules[0].host}"
10471064

10481065
(
10491066
head_extended_resources,
@@ -1133,3 +1150,80 @@ def _is_openshift_cluster():
11331150
return False
11341151
except Exception as e: # pragma: no cover
11351152
return _kube_api_error_handling(e)
1153+
1154+
1155+
# Get dashboard URL from HTTPRoute (RHOAI v3.0+)
1156+
def _get_dashboard_url_from_httproute(
1157+
cluster_name: str, namespace: str
1158+
) -> Optional[str]:
1159+
"""
1160+
Attempts to get the Ray dashboard URL from an HTTPRoute resource.
1161+
This is used for RHOAI v3.0+ clusters that use Gateway API.
1162+
1163+
Args:
1164+
cluster_name: Name of the Ray cluster
1165+
namespace: Namespace of the Ray cluster
1166+
1167+
Returns:
1168+
Dashboard URL if HTTPRoute is found, None otherwise
1169+
"""
1170+
try:
1171+
config_check()
1172+
api_instance = client.CustomObjectsApi(get_api_client())
1173+
1174+
# Try to get HTTPRoute for this Ray cluster
1175+
try:
1176+
httproute = api_instance.get_namespaced_custom_object(
1177+
group="gateway.networking.k8s.io",
1178+
version="v1",
1179+
namespace=namespace,
1180+
plural="httproutes",
1181+
name=cluster_name,
1182+
)
1183+
except client.exceptions.ApiException as e:
1184+
if e.status == 404:
1185+
# HTTPRoute not found - this is expected for SDK v0.31.1 and below or Kind clusters
1186+
return None
1187+
raise
1188+
1189+
# Get the Gateway reference from HTTPRoute
1190+
parent_refs = httproute.get("spec", {}).get("parentRefs", [])
1191+
if not parent_refs:
1192+
return None
1193+
1194+
gateway_ref = parent_refs[0]
1195+
gateway_name = gateway_ref.get("name")
1196+
gateway_namespace = gateway_ref.get("namespace")
1197+
1198+
if not gateway_name or not gateway_namespace:
1199+
return None
1200+
1201+
# Get the Gateway to retrieve the hostname
1202+
gateway = api_instance.get_namespaced_custom_object(
1203+
group="gateway.networking.k8s.io",
1204+
version="v1",
1205+
namespace=gateway_namespace,
1206+
plural="gateways",
1207+
name=gateway_name,
1208+
)
1209+
1210+
# Extract hostname from Gateway listeners
1211+
listeners = gateway.get("spec", {}).get("listeners", [])
1212+
if not listeners:
1213+
return None
1214+
1215+
hostname = listeners[0].get("hostname")
1216+
if not hostname:
1217+
return None
1218+
1219+
# Construct the dashboard URL using RHOAI v3.0+ Gateway API pattern
1220+
# The HTTPRoute existence confirms v3.0+, so we use the standard path pattern
1221+
# Format: https://{hostname}/ray/{namespace}/{cluster-name}
1222+
protocol = "https" # Gateway API uses HTTPS
1223+
dashboard_url = f"{protocol}://{hostname}/ray/{namespace}/{cluster_name}"
1224+
1225+
return dashboard_url
1226+
1227+
except Exception as e: # pragma: no cover
1228+
# If any error occurs, return None to fall back to OpenShift Route
1229+
return None

0 commit comments

Comments
 (0)