Skip to content

Commit 191d4ee

Browse files
Refactor and move UI table to widgets.py file
1 parent fb01b95 commit 191d4ee

File tree

4 files changed

+216
-202
lines changed

4 files changed

+216
-202
lines changed

src/codeflare_sdk/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
get_cluster,
1515
list_all_queued,
1616
list_all_clusters,
17-
list_cluster_details,
17+
view_clusters,
1818
)
1919

2020
from .job import RayJobClient

src/codeflare_sdk/cluster/__init__.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
get_cluster,
2020
list_all_queued,
2121
list_all_clusters,
22-
list_cluster_details,
22+
)
23+
24+
from .widgets import (
25+
view_clusters,
2326
)
2427

2528
from .awload import AWManager

src/codeflare_sdk/cluster/cluster.py

-198
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@
3535
from ..utils.kube_api_helpers import _kube_api_error_handling
3636
from ..utils.generate_yaml import is_openshift_cluster
3737

38-
import ipywidgets as widgets
39-
from IPython.display import display, HTML, Javascript
40-
import pandas as pd
41-
4238
from .config import ClusterConfiguration
4339
from .model import (
4440
AppWrapper,
@@ -597,200 +593,6 @@ def get_current_namespace(): # pragma: no cover
597593
except KeyError:
598594
return None
599595

600-
# format_status takes a RayCluster status and applies colors and icons based on the status.
601-
def format_status(status):
602-
if status == RayClusterStatus.READY:
603-
return '<span style="color: green;">Ready ✓</span>'
604-
elif status == RayClusterStatus.SUSPENDED:
605-
return '<span style="color: #007BFF;">Suspended ❄️</span>'
606-
elif status == RayClusterStatus.FAILED:
607-
return '<span style="color: red;">Failed ✗</span>'
608-
elif status == RayClusterStatus.UNHEALTHY:
609-
return '<span style="color: purple;">Unhealthy</span>'
610-
elif status == RayClusterStatus.UNKNOWN:
611-
return '<span style="color: purple;">Unknown</span>'
612-
else:
613-
return status
614-
615-
def _fetch_cluster_data(namespace):
616-
rayclusters = list_all_clusters(namespace, False)
617-
names = [item.name for item in rayclusters]
618-
namespaces = [item.namespace for item in rayclusters]
619-
head_extended_resources = [
620-
f"{list(item.head_extended_resources.keys())[0]}: {list(item.head_extended_resources.values())[0]}"
621-
if item.head_extended_resources else "0"
622-
for item in rayclusters
623-
]
624-
worker_extended_resources = [
625-
f"{list(item.worker_extended_resources.keys())[0]}: {list(item.worker_extended_resources.values())[0]}"
626-
if item.worker_extended_resources else "0"
627-
for item in rayclusters
628-
]
629-
head_cpu_requests = [item.head_cpu_requests if item.head_cpu_requests else 0 for item in rayclusters]
630-
head_cpu_limits = [item.head_cpu_limits if item.head_cpu_limits else 0 for item in rayclusters]
631-
head_cpu_rl = [f"{requests}~{limits}" for requests, limits in zip(head_cpu_requests, head_cpu_limits)]
632-
head_mem_requests = [item.head_mem_requests if item.head_mem_requests else 0 for item in rayclusters]
633-
head_mem_limits = [item.head_mem_limits if item.head_mem_limits else 0 for item in rayclusters]
634-
head_mem_rl = [f"{requests}~{limits}" for requests, limits in zip(head_mem_requests, head_mem_limits)]
635-
worker_cpu_requests = [item.worker_cpu_requests if item.worker_cpu_requests else 0 for item in rayclusters]
636-
worker_cpu_limits = [item.worker_cpu_limits if item.worker_cpu_limits else 0 for item in rayclusters]
637-
worker_cpu_rl = [f"{requests}~{limits}" for requests, limits in zip(worker_cpu_requests, worker_cpu_limits)]
638-
worker_mem_requests = [item.worker_mem_requests if item.worker_mem_requests else 0 for item in rayclusters]
639-
worker_mem_limits = [item.worker_mem_limits if item.worker_mem_limits else 0 for item in rayclusters]
640-
worker_mem_rl = [f"{requests}~{limits}" for requests, limits in zip(worker_mem_requests, worker_mem_limits)]
641-
status = [item.status.name for item in rayclusters]
642-
643-
status = [format_status(item.status) for item in rayclusters]
644-
645-
data = {
646-
"Name": names,
647-
"Namespace": namespaces,
648-
"Head GPUs": head_extended_resources,
649-
"Worker GPUs": worker_extended_resources,
650-
"Head CPU Req~Lim": head_cpu_rl,
651-
"Head Memory Req~Lim": head_mem_rl,
652-
"Worker CPU Req~Lim": worker_cpu_rl,
653-
"Worker Memory Req~Lim": worker_mem_rl,
654-
"status": status
655-
}
656-
return pd.DataFrame(data)
657-
658-
659-
def list_cluster_details(namespace: str): # TODO: or current namespace as default
660-
global df
661-
df = _fetch_cluster_data(namespace)
662-
663-
outputs = widgets.Output()
664-
data_output = widgets.Output()
665-
if df["Name"].empty:
666-
print(f"No clusters found in the {namespace} namespace.")
667-
else:
668-
classification_widget = widgets.ToggleButtons(
669-
options=df["Name"].tolist(), value=None,
670-
description='Select an existing cluster:',
671-
)
672-
673-
def on_cluster_click(change):
674-
new_value = change["new"]
675-
data_output.clear_output()
676-
df = _fetch_cluster_data(namespace)
677-
classification_widget.options = df["Name"].tolist()
678-
with data_output:
679-
display(HTML(df[df["Name"]==new_value][["Name", "Namespace", "Head GPUs", "Head CPU Req~Lim", "Head Memory Req~Lim", "Worker GPUs", "Worker CPU Req~Lim", "Worker Memory Req~Lim", "status"]].to_html(escape=False, index=False, border=2)))
680-
681-
classification_widget.observe(on_cluster_click, names="value")
682-
display(widgets.VBox([classification_widget, data_output]))
683-
684-
def on_delete_button_clicked(b):
685-
global df
686-
cluster_name = classification_widget.value
687-
namespace = df[df["Name"]==classification_widget.value]["Namespace"].values[0]
688-
delete_cluster(cluster_name, namespace)
689-
690-
sleep(3) # wait for the cluster to be deleted
691-
outputs.clear_output()
692-
with outputs:
693-
print(f"Cluster {cluster_name} in the {namespace} namespace was deleted successfully.")
694-
# Refresh the dataframe
695-
df = _fetch_cluster_data(namespace)
696-
if df["Name"].empty:
697-
classification_widget.close()
698-
delete_button.close()
699-
list_jobs_button.close()
700-
ray_dashboard_button.close()
701-
data_output.clear_output()
702-
with data_output:
703-
print(f"No clusters found in the {namespace} namespace.")
704-
else:
705-
classification_widget.options = df["Name"].tolist()
706-
707-
708-
709-
# out Output widget is used to execute JavaScript code to open the Ray dashboard URL in a new browser tab
710-
out = widgets.Output()
711-
def on_ray_dashboard_button_clicked(b):
712-
cluster_name = classification_widget.value
713-
namespace = df[df["Name"]==classification_widget.value]["Namespace"].values[0]
714-
715-
cluster = Cluster(ClusterConfiguration(cluster_name, namespace))
716-
dashboard_url = cluster.cluster_dashboard_uri()
717-
718-
outputs.clear_output()
719-
with outputs:
720-
print(f"Opening Ray Dashboard for {cluster_name} cluster:\n{dashboard_url}")
721-
with out:
722-
display(Javascript(f'window.open("{dashboard_url}", "_blank");'))
723-
724-
def on_list_jobs_button_clicked(b):
725-
cluster_name = classification_widget.value
726-
namespace = df[df["Name"]==classification_widget.value]["Namespace"].values[0]
727-
728-
cluster = Cluster(ClusterConfiguration(cluster_name, namespace))
729-
dashboard_url = cluster.cluster_dashboard_uri()
730-
731-
outputs.clear_output()
732-
with outputs:
733-
print(f"Opening Ray Jobs Dashboard for {cluster_name} cluster:\n{dashboard_url}/#/jobs")
734-
with out:
735-
display(Javascript(f'window.open("{dashboard_url}/#/jobs", "_blank");'))
736-
737-
list_jobs_button = widgets.Button(
738-
description='View Jobs',
739-
icon='suitcase',
740-
tooltip="Open the Ray Job Dashboard"
741-
)
742-
list_jobs_button.on_click(on_list_jobs_button_clicked)
743-
744-
delete_button = widgets.Button(
745-
description='Delete Cluster',
746-
icon='trash',
747-
tooltip="Delete the selected cluster"
748-
)
749-
delete_button.on_click(on_delete_button_clicked)
750-
751-
ray_dashboard_button = widgets.Button(
752-
description='Open Ray Dashboard',
753-
icon='dashboard',
754-
tooltip="Open the Ray Dashboard in a new tab",
755-
layout=widgets.Layout(width='auto'),
756-
)
757-
ray_dashboard_button.on_click(on_ray_dashboard_button_clicked)
758-
759-
display(widgets.HBox([delete_button, list_jobs_button, ray_dashboard_button]), out, outputs)
760-
761-
762-
763-
def delete_cluster(
764-
cluster_name: str,
765-
namespace: str, # TODO: get current namespace if not provided
766-
):
767-
if _check_aw_exists(cluster_name, namespace):
768-
try:
769-
config_check()
770-
api_instance = client.CustomObjectsApi(api_config_handler())
771-
api_instance.delete_namespaced_custom_object(
772-
group="workload.codeflare.dev",
773-
version="v1beta2",
774-
namespace=namespace,
775-
plural="appwrappers",
776-
name=cluster_name,
777-
)
778-
except Exception as e:
779-
return _kube_api_error_handling(e)
780-
else:
781-
try:
782-
config_check()
783-
api_instance = client.CustomObjectsApi(api_config_handler())
784-
api_instance.delete_namespaced_custom_object(
785-
group="ray.io",
786-
version="v1",
787-
namespace=namespace,
788-
plural="rayclusters",
789-
name=cluster_name,
790-
)
791-
except Exception as e:
792-
return _kube_api_error_handling(e)
793-
794596

795597
def get_cluster(
796598
cluster_name: str,

0 commit comments

Comments
 (0)