|
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