|
35 | 35 | from ..utils.kube_api_helpers import _kube_api_error_handling |
36 | 36 | from ..utils.generate_yaml import is_openshift_cluster |
37 | 37 |
|
38 | | -import ipywidgets as widgets |
39 | | -from IPython.display import display, HTML, Javascript |
40 | | -import pandas as pd |
41 | | - |
42 | 38 | from .config import ClusterConfiguration |
43 | 39 | from .model import ( |
44 | 40 | AppWrapper, |
@@ -597,200 +593,6 @@ def get_current_namespace(): # pragma: no cover |
597 | 593 | except KeyError: |
598 | 594 | return None |
599 | 595 |
|
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 | | - |
794 | 596 |
|
795 | 597 | def get_cluster( |
796 | 598 | cluster_name: str, |
|
0 commit comments