From c09d1c3c045e5bbf87bf6bab9898d54c0bbac380 Mon Sep 17 00:00:00 2001 From: codeflare-machine-account Date: Tue, 31 Oct 2023 15:50:45 +0000 Subject: [PATCH] Changes in docs for release: v0.11.0 --- docs/cluster/cluster.html | 192 ++++++++------- docs/cluster/config.html | 20 +- docs/utils/generate_yaml.html | 414 ++++++++++++++++++++++++++------ docs/utils/openshift_oauth.html | 6 +- 4 files changed, 468 insertions(+), 164 deletions(-) diff --git a/docs/cluster/cluster.html b/docs/cluster/cluster.html index e33b9cc2f..8a313dc75 100644 --- a/docs/cluster/cluster.html +++ b/docs/cluster/cluster.html @@ -60,7 +60,9 @@

Module codeflare_sdk.cluster.cluster

from .auth import config_check, api_config_handler from ..utils import pretty_print -from ..utils.generate_yaml import generate_appwrapper +from ..utils.generate_yaml import ( + generate_appwrapper, +) from ..utils.kube_api_helpers import _kube_api_error_handling from ..utils.openshift_oauth import ( create_openshift_oauth_objects, @@ -207,6 +209,8 @@

Module codeflare_sdk.cluster.cluster

local_interactive = self.config.local_interactive image_pull_secrets = self.config.image_pull_secrets dispatch_priority = self.config.dispatch_priority + ingress_domain = self.config.ingress_domain + ingress_options = self.config.ingress_options return generate_appwrapper( name=name, namespace=namespace, @@ -230,6 +234,8 @@

Module codeflare_sdk.cluster.cluster

dispatch_priority=dispatch_priority, priority_val=priority_val, openshift_oauth=self.config.openshift_oauth, + ingress_domain=ingress_domain, + ingress_options=ingress_options, ) # creates a new cluster with the provided or default spec @@ -368,7 +374,7 @@

Module codeflare_sdk.cluster.cluster

timeout=5, verify=self._client_verify_tls, ) - except requests.exceptions.SSLError: + except requests.exceptions.SSLError: # pragma no cover # SSL exception occurs when oauth ingress has been created but cluster is not up return False if response.status_code == 200: @@ -431,27 +437,24 @@

Module codeflare_sdk.cluster.cluster

""" try: config_check() - api_instance = client.CustomObjectsApi(api_config_handler()) - routes = api_instance.list_namespaced_custom_object( - group="route.openshift.io", - version="v1", - namespace=self.config.namespace, - plural="routes", - ) - except Exception as e: # pragma: no cover + api_instance = client.NetworkingV1Api(api_config_handler()) + ingresses = api_instance.list_namespaced_ingress(self.config.namespace) + except Exception as e: # pragma no cover return _kube_api_error_handling(e) - for route in routes["items"]: - if route["metadata"][ - "name" - ] == f"ray-dashboard-{self.config.name}" or route["metadata"][ - "name" - ].startswith( - f"{self.config.name}-ingress" + for ingress in ingresses.items: + annotations = ingress.metadata.annotations + protocol = "http" + if ( + ingress.metadata.name == f"ray-dashboard-{self.config.name}" + or ingress.metadata.name.startswith(f"{self.config.name}-ingress") ): - protocol = "https" if route["spec"].get("tls") else "http" - return f"{protocol}://{route['spec']['host']}" - return "Dashboard route not available yet, have you run cluster.up()?" + if annotations == None: + protocol = "http" + elif "route.openshift.io/termination" in annotations: + protocol = "https" + return f"{protocol}://{ingress.spec.rules[0].host}" + return "Dashboard ingress not available yet, have you run cluster.up()?" def list_jobs(self) -> List: """ @@ -530,8 +533,8 @@

Module codeflare_sdk.cluster.cluster

def local_client_url(self): if self.config.local_interactive == True: - ingress_domain = _get_ingress_domain() - return f"ray://rayclient-{self.config.name}-{self.config.namespace}.{ingress_domain}" + ingress_domain = _get_ingress_domain(self) + return f"ray://{ingress_domain}" else: return "None" @@ -687,16 +690,23 @@

Module codeflare_sdk.cluster.cluster

return False -def _get_ingress_domain(): +# Cant test this until get_current_namespace is fixed +def _get_ingress_domain(self): # pragma: no cover try: config_check() - api_client = client.CustomObjectsApi(api_config_handler()) - ingress = api_client.get_cluster_custom_object( - "config.openshift.io", "v1", "ingresses", "cluster" - ) + api_client = client.NetworkingV1Api(api_config_handler()) + if self.config.namespace != None: + namespace = self.config.namespace + else: + namespace = get_current_namespace() + ingresses = api_client.list_namespaced_ingress(namespace) except Exception as e: # pragma: no cover return _kube_api_error_handling(e) - return ingress["spec"]["domain"] + domain = None + for ingress in ingresses.items: + if ingress.spec.rules[0].http.paths[0].backend.service.port.number == 10001: + domain = ingress.spec.rules[0].host + return domain def _app_wrapper_status(name, namespace="default") -> Optional[AppWrapper]: @@ -788,27 +798,25 @@

Module codeflare_sdk.cluster.cluster

status = RayClusterStatus(rc["status"]["state"].lower()) else: status = RayClusterStatus.UNKNOWN - - config_check() - api_instance = client.CustomObjectsApi(api_config_handler()) - # UPDATE THIS - routes = api_instance.list_namespaced_custom_object( - group="route.openshift.io", - version="v1", - namespace=rc["metadata"]["namespace"], - plural="routes", - ) - ray_route = None - for route in routes["items"]: - if route["metadata"][ - "name" - ] == f"ray-dashboard-{rc['metadata']['name']}" or route["metadata"][ - "name" - ].startswith( - f"{rc['metadata']['name']}-ingress" + try: + config_check() + api_instance = client.NetworkingV1Api(api_config_handler()) + ingresses = api_instance.list_namespaced_ingress(rc["metadata"]["namespace"]) + except Exception as e: # pragma no cover + return _kube_api_error_handling(e) + ray_ingress = None + for ingress in ingresses.items: + annotations = ingress.metadata.annotations + protocol = "http" + if ( + ingress.metadata.name == f"ray-dashboard-{rc['metadata']['name']}" + or ingress.metadata.name.startswith(f"{rc['metadata']['name']}-ingress") ): - protocol = "https" if route["spec"].get("tls") else "http" - ray_route = f"{protocol}://{route['spec']['host']}" + if annotations == None: + protocol = "http" + elif "route.openshift.io/termination" in annotations: + protocol = "https" + ray_ingress = f"{protocol}://{ingress.spec.rules[0].host}" return RayCluster( name=rc["metadata"]["name"], @@ -826,7 +834,6 @@

Module codeflare_sdk.cluster.cluster

]["resources"]["limits"]["cpu"], worker_gpu=0, # hard to detect currently how many gpus, can override it with what the user asked for namespace=rc["metadata"]["namespace"], - dashboard=ray_route, head_cpus=rc["spec"]["headGroupSpec"]["template"]["spec"]["containers"][0][ "resources" ]["limits"]["cpu"], @@ -836,6 +843,7 @@

Module codeflare_sdk.cluster.cluster

head_gpu=rc["spec"]["headGroupSpec"]["template"]["spec"]["containers"][0][ "resources" ]["limits"]["nvidia.com/gpu"], + dashboard=ray_ingress, ) @@ -1136,6 +1144,8 @@

Classes

local_interactive = self.config.local_interactive image_pull_secrets = self.config.image_pull_secrets dispatch_priority = self.config.dispatch_priority + ingress_domain = self.config.ingress_domain + ingress_options = self.config.ingress_options return generate_appwrapper( name=name, namespace=namespace, @@ -1159,6 +1169,8 @@

Classes

dispatch_priority=dispatch_priority, priority_val=priority_val, openshift_oauth=self.config.openshift_oauth, + ingress_domain=ingress_domain, + ingress_options=ingress_options, ) # creates a new cluster with the provided or default spec @@ -1297,7 +1309,7 @@

Classes

timeout=5, verify=self._client_verify_tls, ) - except requests.exceptions.SSLError: + except requests.exceptions.SSLError: # pragma no cover # SSL exception occurs when oauth ingress has been created but cluster is not up return False if response.status_code == 200: @@ -1360,27 +1372,24 @@

Classes

""" try: config_check() - api_instance = client.CustomObjectsApi(api_config_handler()) - routes = api_instance.list_namespaced_custom_object( - group="route.openshift.io", - version="v1", - namespace=self.config.namespace, - plural="routes", - ) - except Exception as e: # pragma: no cover + api_instance = client.NetworkingV1Api(api_config_handler()) + ingresses = api_instance.list_namespaced_ingress(self.config.namespace) + except Exception as e: # pragma no cover return _kube_api_error_handling(e) - for route in routes["items"]: - if route["metadata"][ - "name" - ] == f"ray-dashboard-{self.config.name}" or route["metadata"][ - "name" - ].startswith( - f"{self.config.name}-ingress" + for ingress in ingresses.items: + annotations = ingress.metadata.annotations + protocol = "http" + if ( + ingress.metadata.name == f"ray-dashboard-{self.config.name}" + or ingress.metadata.name.startswith(f"{self.config.name}-ingress") ): - protocol = "https" if route["spec"].get("tls") else "http" - return f"{protocol}://{route['spec']['host']}" - return "Dashboard route not available yet, have you run cluster.up()?" + if annotations == None: + protocol = "http" + elif "route.openshift.io/termination" in annotations: + protocol = "https" + return f"{protocol}://{ingress.spec.rules[0].host}" + return "Dashboard ingress not available yet, have you run cluster.up()?" def list_jobs(self) -> List: """ @@ -1459,8 +1468,8 @@

Classes

def local_client_url(self): if self.config.local_interactive == True: - ingress_domain = _get_ingress_domain() - return f"ray://rayclient-{self.config.name}-{self.config.namespace}.{ingress_domain}" + ingress_domain = _get_ingress_domain(self) + return f"ray://{ingress_domain}" else: return "None" @@ -1580,27 +1589,24 @@

Methods

""" try: config_check() - api_instance = client.CustomObjectsApi(api_config_handler()) - routes = api_instance.list_namespaced_custom_object( - group="route.openshift.io", - version="v1", - namespace=self.config.namespace, - plural="routes", - ) - except Exception as e: # pragma: no cover + api_instance = client.NetworkingV1Api(api_config_handler()) + ingresses = api_instance.list_namespaced_ingress(self.config.namespace) + except Exception as e: # pragma no cover return _kube_api_error_handling(e) - for route in routes["items"]: - if route["metadata"][ - "name" - ] == f"ray-dashboard-{self.config.name}" or route["metadata"][ - "name" - ].startswith( - f"{self.config.name}-ingress" + for ingress in ingresses.items: + annotations = ingress.metadata.annotations + protocol = "http" + if ( + ingress.metadata.name == f"ray-dashboard-{self.config.name}" + or ingress.metadata.name.startswith(f"{self.config.name}-ingress") ): - protocol = "https" if route["spec"].get("tls") else "http" - return f"{protocol}://{route['spec']['host']}" - return "Dashboard route not available yet, have you run cluster.up()?" + if annotations == None: + protocol = "http" + elif "route.openshift.io/termination" in annotations: + protocol = "https" + return f"{protocol}://{ingress.spec.rules[0].host}" + return "Dashboard ingress not available yet, have you run cluster.up()?"
@@ -1678,6 +1684,8 @@

Methods

local_interactive = self.config.local_interactive image_pull_secrets = self.config.image_pull_secrets dispatch_priority = self.config.dispatch_priority + ingress_domain = self.config.ingress_domain + ingress_options = self.config.ingress_options return generate_appwrapper( name=name, namespace=namespace, @@ -1701,6 +1709,8 @@

Methods

dispatch_priority=dispatch_priority, priority_val=priority_val, openshift_oauth=self.config.openshift_oauth, + ingress_domain=ingress_domain, + ingress_options=ingress_options, )
@@ -1858,7 +1868,7 @@

Methods

timeout=5, verify=self._client_verify_tls, ) - except requests.exceptions.SSLError: + except requests.exceptions.SSLError: # pragma no cover # SSL exception occurs when oauth ingress has been created but cluster is not up return False if response.status_code == 200: @@ -1926,8 +1936,8 @@

Methods

def local_client_url(self):
     if self.config.local_interactive == True:
-        ingress_domain = _get_ingress_domain()
-        return f"ray://rayclient-{self.config.name}-{self.config.namespace}.{ingress_domain}"
+        ingress_domain = _get_ingress_domain(self)
+        return f"ray://{ingress_domain}"
     else:
         return "None"
diff --git a/docs/cluster/config.html b/docs/cluster/config.html index 37242b177..f9b762baf 100644 --- a/docs/cluster/config.html +++ b/docs/cluster/config.html @@ -84,7 +84,9 @@

Module codeflare_sdk.cluster.config

local_interactive: bool = False image_pull_secrets: list = field(default_factory=list) dispatch_priority: str = None - openshift_oauth: bool = False # NOTE: to use the user must have permission to create a RoleBinding for system:auth-delegator + openshift_oauth: bool = False # NOTE: to use the user must have permission to create a RoleBinding for system:auth-delegator + ingress_options: dict = field(default_factory=dict) + ingress_domain: str = None
@@ -98,7 +100,7 @@

Classes

class ClusterConfiguration -(name: str, namespace: str = None, head_info: list = <factory>, head_cpus: int = 2, head_memory: int = 8, head_gpus: int = 0, machine_types: list = <factory>, min_cpus: int = 1, max_cpus: int = 1, num_workers: int = 1, min_memory: int = 2, max_memory: int = 2, num_gpus: int = 0, template: str = '/home/runner/work/codeflare-sdk/codeflare-sdk/src/codeflare_sdk/templates/base-template.yaml', instascale: bool = False, mcad: bool = True, envs: dict = <factory>, image: str = 'quay.io/project-codeflare/ray:latest-py39-cu118', local_interactive: bool = False, image_pull_secrets: list = <factory>, dispatch_priority: str = None, openshift_oauth: bool = False) +(name: str, namespace: str = None, head_info: list = <factory>, head_cpus: int = 2, head_memory: int = 8, head_gpus: int = 0, machine_types: list = <factory>, min_cpus: int = 1, max_cpus: int = 1, num_workers: int = 1, min_memory: int = 2, max_memory: int = 2, num_gpus: int = 0, template: str = '/home/runner/work/codeflare-sdk/codeflare-sdk/src/codeflare_sdk/templates/base-template.yaml', instascale: bool = False, mcad: bool = True, envs: dict = <factory>, image: str = 'quay.io/project-codeflare/ray:latest-py39-cu118', local_interactive: bool = False, image_pull_secrets: list = <factory>, dispatch_priority: str = None, openshift_oauth: bool = False, ingress_options: dict = <factory>, ingress_domain: str = None)

This dataclass is used to specify resource requirements and other details, and @@ -134,7 +136,9 @@

Classes

local_interactive: bool = False image_pull_secrets: list = field(default_factory=list) dispatch_priority: str = None - openshift_oauth: bool = False # NOTE: to use the user must have permission to create a RoleBinding for system:auth-delegator + openshift_oauth: bool = False # NOTE: to use the user must have permission to create a RoleBinding for system:auth-delegator + ingress_options: dict = field(default_factory=dict) + ingress_domain: str = None

Class variables

@@ -170,6 +174,14 @@

Class variables

+
var ingress_domain : str
+
+
+
+
var ingress_options : dict
+
+
+
var instascale : bool
@@ -255,6 +267,8 @@

head_memory
  • image
  • image_pull_secrets
  • +
  • ingress_domain
  • +
  • ingress_options
  • instascale
  • local_interactive
  • machine_types
  • diff --git a/docs/utils/generate_yaml.html b/docs/utils/generate_yaml.html index fa449d8d7..ba2446644 100644 --- a/docs/utils/generate_yaml.html +++ b/docs/utils/generate_yaml.html @@ -82,27 +82,148 @@

    Module codeflare_sdk.utils.generate_yaml

    return name, name -def update_dashboard_route(route_item, cluster_name, namespace): - metadata = route_item.get("generictemplate", {}).get("metadata") - metadata["name"] = gen_dashboard_route_name(cluster_name) - metadata["namespace"] = namespace - metadata["labels"]["odh-ray-cluster-service"] = f"{cluster_name}-head-svc" - spec = route_item.get("generictemplate", {}).get("spec") - spec["to"]["name"] = f"{cluster_name}-head-svc" +def gen_dashboard_ingress_name(cluster_name): + return f"ray-dashboard-{cluster_name}" -def gen_dashboard_route_name(cluster_name): - return f"ray-dashboard-{cluster_name}" +# Check if the ingress api cluster resource exists +def is_openshift_cluster(): + try: + config_check() + api_instance = client.CustomObjectsApi(api_config_handler()) + api_instance.get_cluster_custom_object( + "config.openshift.io", "v1", "ingresses", "cluster" + ) + return True + except client.ApiException as e: # pragma: no cover + if e.status == 404 or e.status == 403: + return False + else: + print(f"Error detecting cluster type defaulting to Kubernetes: {e}") + return False + + +def update_dashboard_ingress( + ingress_item, cluster_name, namespace, ingress_options, ingress_domain +): # pragma: no cover + metadata = ingress_item.get("generictemplate", {}).get("metadata") + spec = ingress_item.get("generictemplate", {}).get("spec") + if ingress_options != {}: + for index, ingress_option in enumerate(ingress_options["ingresses"]): + if "ingressName" not in ingress_option.keys(): + raise ValueError( + f"Error: 'ingressName' is missing or empty for ingress item at index {index}" + ) + if "port" not in ingress_option.keys(): + raise ValueError( + f"Error: 'port' is missing or empty for ingress item at index {index}" + ) + elif not isinstance(ingress_option["port"], int): + raise ValueError( + f"Error: 'port' is not of type int for ingress item at index {index}" + ) + if ingress_option["port"] == 8265: + metadata["name"] = ingress_option["ingressName"] + metadata["namespace"] = namespace + if "annotations" not in ingress_option.keys(): + del metadata["annotations"] + else: + metadata["annotations"] = ingress_option["annotations"] + if "path" not in ingress_option.keys(): + del spec["rules"][0]["http"]["paths"][0]["path"] + else: + spec["rules"][0]["http"]["paths"][0]["path"] = ingress_option[ + "path" + ] + if "pathType" not in ingress_option.keys(): + spec["rules"][0]["http"]["paths"][0][ + "pathType" + ] = "ImplementationSpecific" + if "host" not in ingress_option.keys(): + del spec["rules"][0]["host"] + else: + spec["rules"][0]["host"] = ingress_option["host"] + if "ingressClassName" not in ingress_option.keys(): + del spec["ingressClassName"] + else: + spec["ingressClassName"] = ingress_option["ingressClassName"] + + spec["rules"][0]["http"]["paths"][0]["backend"]["service"][ + "name" + ] = f"{cluster_name}-head-svc" + else: + metadata["name"] = f"ray-dashboard-{cluster_name}" + metadata["namespace"] = namespace + spec["rules"][0]["http"]["paths"][0]["backend"]["service"][ + "name" + ] = f"{cluster_name}-head-svc" + if is_openshift_cluster(): + try: + config_check() + api_client = client.CustomObjectsApi(api_config_handler()) + ingress = api_client.get_cluster_custom_object( + "config.openshift.io", "v1", "ingresses", "cluster" + ) + del spec["ingressClassName"] + except Exception as e: # pragma: no cover + return _kube_api_error_handling(e) + domain = ingress["spec"]["domain"] + elif ingress_domain is None: + raise ValueError( + "ingress_domain is invalid. For Kubernetes Clusters please specify an ingress domain" + ) + else: + domain = ingress_domain + del metadata["annotations"] + spec["rules"][0]["host"] = f"ray-dashboard-{cluster_name}-{namespace}.{domain}" -# ToDo: refactor the update_x_route() functions -def update_rayclient_route(route_item, cluster_name, namespace): - metadata = route_item.get("generictemplate", {}).get("metadata") + +def update_rayclient_ingress( + ingress_item, cluster_name, namespace, ingress_domain +): # pragma: no cover + metadata = ingress_item.get("generictemplate", {}).get("metadata") + spec = ingress_item.get("generictemplate", {}).get("spec") metadata["name"] = f"rayclient-{cluster_name}" metadata["namespace"] = namespace metadata["labels"]["odh-ray-cluster-service"] = f"{cluster_name}-head-svc" - spec = route_item.get("generictemplate", {}).get("spec") - spec["to"]["name"] = f"{cluster_name}-head-svc" + + spec["rules"][0]["http"]["paths"][0]["backend"]["service"][ + "name" + ] = f"{cluster_name}-head-svc" + + if is_openshift_cluster(): + try: + config_check() + api_client = client.CustomObjectsApi(api_config_handler()) + ingress = api_client.get_cluster_custom_object( + "config.openshift.io", "v1", "ingresses", "cluster" + ) + ingressClassName = "openshift-default" + annotations = { + "nginx.ingress.kubernetes.io/rewrite-target": "/", + "nginx.ingress.kubernetes.io/ssl-redirect": "true", + "route.openshift.io/termination": "passthrough", + } + except Exception as e: # pragma: no cover + return _kube_api_error_handling(e) + domain = ingress["spec"]["domain"] + elif ingress_domain is None: + raise ValueError( + "ingress_domain is invalid. For Kubernetes Clusters please specify an ingress domain" + ) + else: + domain = ingress_domain + ingressClassName = "nginx" + annotations = { + "nginx.ingress.kubernetes.io/rewrite-target": "/", + "nginx.ingress.kubernetes.io/ssl-redirect": "true", + "nginx.ingress.kubernetes.io/ssl-passthrough": "true", + } + + metadata["annotations"] = annotations + spec["ingressClassName"] = ingressClassName + spec["rules"][0]["host"] = f"rayclient-{cluster_name}-{namespace}.{domain}" def update_names(yaml, item, appwrapper_name, cluster_name, namespace): @@ -305,11 +426,10 @@

    Module codeflare_sdk.utils.generate_yaml

    data["ca.key"], data["ca.crt"] = generate_cert.generate_ca_cert(365) -def enable_local_interactive(resources, cluster_name, namespace): - rayclient_route_item = resources["resources"].get("GenericItems")[2] +def enable_local_interactive(resources, cluster_name, namespace, ingress_domain): + rayclient_ingress_item = resources["resources"].get("GenericItems")[2] ca_secret_item = resources["resources"].get("GenericItems")[3] item = resources["resources"].get("GenericItems")[0] - update_rayclient_route(rayclient_route_item, cluster_name, namespace) update_ca_secret(ca_secret_item, cluster_name, namespace) # update_ca_secret_volumes item["generictemplate"]["spec"]["headGroupSpec"]["template"]["spec"]["volumes"][0][ @@ -331,16 +451,27 @@

    Module codeflare_sdk.utils.generate_yaml

    ][0].get("command")[2] command = command.replace("deployment-name", cluster_name) - try: - config_check() - api_client = client.CustomObjectsApi(api_config_handler()) - ingress = api_client.get_cluster_custom_object( - "config.openshift.io", "v1", "ingresses", "cluster" + + if is_openshift_cluster(): + # We can try get the domain through checking ingresses.config.openshift.io + try: + config_check() + api_client = client.CustomObjectsApi(api_config_handler()) + ingress = api_client.get_cluster_custom_object( + "config.openshift.io", "v1", "ingresses", "cluster" + ) + except Exception as e: # pragma: no cover + return _kube_api_error_handling(e) + domain = ingress["spec"]["domain"] + elif ingress_domain is None: + raise ValueError( + "ingress_domain is invalid. For Kubernetes Clusters please specify an ingress domain" ) - except Exception as e: # pragma: no cover - return _kube_api_error_handling(e) - domain = ingress["spec"]["domain"] + else: + domain = ingress_domain + command = command.replace("server-name", domain) + update_rayclient_ingress(rayclient_ingress_item, cluster_name, namespace, domain) item["generictemplate"]["spec"]["headGroupSpec"]["template"]["spec"][ "initContainers" @@ -422,7 +553,7 @@

    Module codeflare_sdk.utils.generate_yaml

    port_name = "oauth-proxy" host = _get_api_host(k8_client) host = host.replace( - "api.", f"{gen_dashboard_route_name(cluster_name)}-{namespace}.apps." + "api.", f"{gen_dashboard_ingress_name(cluster_name)}-{namespace}.apps." ) oauth_sidecar = _create_oauth_sidecar_object( namespace, @@ -524,12 +655,14 @@

    Module codeflare_sdk.utils.generate_yaml

    dispatch_priority: str, priority_val: int, openshift_oauth: bool, + ingress_domain: str, + ingress_options: dict, ): user_yaml = read_template(template) appwrapper_name, cluster_name = gen_names(name) resources = user_yaml.get("spec", "resources") item = resources["resources"].get("GenericItems")[0] - route_item = resources["resources"].get("GenericItems")[1] + ingress_item = resources["resources"].get("GenericItems")[1] update_names(user_yaml, item, appwrapper_name, cluster_name, namespace) update_labels(user_yaml, instascale, instance_types) update_priority(user_yaml, item, dispatch_priority, priority_val) @@ -562,9 +695,11 @@

    Module codeflare_sdk.utils.generate_yaml

    head_memory, head_gpus, ) - update_dashboard_route(route_item, cluster_name, namespace) + update_dashboard_ingress( + ingress_item, cluster_name, namespace, ingress_options, ingress_domain + ) if local_interactive: - enable_local_interactive(resources, cluster_name, namespace) + enable_local_interactive(resources, cluster_name, namespace, ingress_domain) else: disable_raycluster_tls(resources["resources"]) @@ -654,7 +789,7 @@

    Functions

    -def enable_local_interactive(resources, cluster_name, namespace) +def enable_local_interactive(resources, cluster_name, namespace, ingress_domain)
    @@ -662,11 +797,10 @@

    Functions

    Expand source code -
    def enable_local_interactive(resources, cluster_name, namespace):
    -    rayclient_route_item = resources["resources"].get("GenericItems")[2]
    +
    def enable_local_interactive(resources, cluster_name, namespace, ingress_domain):
    +    rayclient_ingress_item = resources["resources"].get("GenericItems")[2]
         ca_secret_item = resources["resources"].get("GenericItems")[3]
         item = resources["resources"].get("GenericItems")[0]
    -    update_rayclient_route(rayclient_route_item, cluster_name, namespace)
         update_ca_secret(ca_secret_item, cluster_name, namespace)
         # update_ca_secret_volumes
         item["generictemplate"]["spec"]["headGroupSpec"]["template"]["spec"]["volumes"][0][
    @@ -688,16 +822,27 @@ 

    Functions

    ][0].get("command")[2] command = command.replace("deployment-name", cluster_name) - try: - config_check() - api_client = client.CustomObjectsApi(api_config_handler()) - ingress = api_client.get_cluster_custom_object( - "config.openshift.io", "v1", "ingresses", "cluster" + + if is_openshift_cluster(): + # We can try get the domain through checking ingresses.config.openshift.io + try: + config_check() + api_client = client.CustomObjectsApi(api_config_handler()) + ingress = api_client.get_cluster_custom_object( + "config.openshift.io", "v1", "ingresses", "cluster" + ) + except Exception as e: # pragma: no cover + return _kube_api_error_handling(e) + domain = ingress["spec"]["domain"] + elif ingress_domain is None: + raise ValueError( + "ingress_domain is invalid. For Kubernetes Clusters please specify an ingress domain" ) - except Exception as e: # pragma: no cover - return _kube_api_error_handling(e) - domain = ingress["spec"]["domain"] + else: + domain = ingress_domain + command = command.replace("server-name", domain) + update_rayclient_ingress(rayclient_ingress_item, cluster_name, namespace, domain) item["generictemplate"]["spec"]["headGroupSpec"]["template"]["spec"][ "initContainers" @@ -724,7 +869,7 @@

    Functions

    port_name = "oauth-proxy" host = _get_api_host(k8_client) host = host.replace( - "api.", f"{gen_dashboard_route_name(cluster_name)}-{namespace}.apps." + "api.", f"{gen_dashboard_ingress_name(cluster_name)}-{namespace}.apps." ) oauth_sidecar = _create_oauth_sidecar_object( namespace, @@ -759,8 +904,8 @@

    Functions

    )
    -
    -def gen_dashboard_route_name(cluster_name) +
    +def gen_dashboard_ingress_name(cluster_name)
    @@ -768,7 +913,7 @@

    Functions

    Expand source code -
    def gen_dashboard_route_name(cluster_name):
    +
    def gen_dashboard_ingress_name(cluster_name):
         return f"ray-dashboard-{cluster_name}"
    @@ -792,7 +937,7 @@

    Functions

    -def generate_appwrapper(name: str, namespace: str, head_cpus: int, head_memory: int, head_gpus: int, min_cpu: int, max_cpu: int, min_memory: int, max_memory: int, gpu: int, workers: int, template: str, image: str, instascale: bool, mcad: bool, instance_types: list, env, local_interactive: bool, image_pull_secrets: list, dispatch_priority: str, priority_val: int, openshift_oauth: bool) +def generate_appwrapper(name: str, namespace: str, head_cpus: int, head_memory: int, head_gpus: int, min_cpu: int, max_cpu: int, min_memory: int, max_memory: int, gpu: int, workers: int, template: str, image: str, instascale: bool, mcad: bool, instance_types: list, env, local_interactive: bool, image_pull_secrets: list, dispatch_priority: str, priority_val: int, openshift_oauth: bool, ingress_domain: str, ingress_options: dict)
    @@ -823,12 +968,14 @@

    Functions

    dispatch_priority: str, priority_val: int, openshift_oauth: bool, + ingress_domain: str, + ingress_options: dict, ): user_yaml = read_template(template) appwrapper_name, cluster_name = gen_names(name) resources = user_yaml.get("spec", "resources") item = resources["resources"].get("GenericItems")[0] - route_item = resources["resources"].get("GenericItems")[1] + ingress_item = resources["resources"].get("GenericItems")[1] update_names(user_yaml, item, appwrapper_name, cluster_name, namespace) update_labels(user_yaml, instascale, instance_types) update_priority(user_yaml, item, dispatch_priority, priority_val) @@ -861,9 +1008,11 @@

    Functions

    head_memory, head_gpus, ) - update_dashboard_route(route_item, cluster_name, namespace) + update_dashboard_ingress( + ingress_item, cluster_name, namespace, ingress_options, ingress_domain + ) if local_interactive: - enable_local_interactive(resources, cluster_name, namespace) + enable_local_interactive(resources, cluster_name, namespace, ingress_domain) else: disable_raycluster_tls(resources["resources"]) @@ -878,6 +1027,32 @@

    Functions

    return outfile
    +
    +def is_openshift_cluster() +
    +
    +
    +
    + +Expand source code + +
    def is_openshift_cluster():
    +    try:
    +        config_check()
    +        api_instance = client.CustomObjectsApi(api_config_handler())
    +        api_instance.get_cluster_custom_object(
    +            "config.openshift.io", "v1", "ingresses", "cluster"
    +        )
    +
    +        return True
    +    except client.ApiException as e:  # pragma: no cover
    +        if e.status == 404 or e.status == 403:
    +            return False
    +        else:
    +            print(f"Error detecting cluster type defaulting to Kubernetes: {e}")
    +            return False
    +
    +
    def read_template(template)
    @@ -997,8 +1172,8 @@

    Functions

    sys.exit("Error: malformed template")
    -
    -def update_dashboard_route(route_item, cluster_name, namespace) +
    +def update_dashboard_ingress(ingress_item, cluster_name, namespace, ingress_options, ingress_domain)
    @@ -1006,13 +1181,79 @@

    Functions

    Expand source code -
    def update_dashboard_route(route_item, cluster_name, namespace):
    -    metadata = route_item.get("generictemplate", {}).get("metadata")
    -    metadata["name"] = gen_dashboard_route_name(cluster_name)
    -    metadata["namespace"] = namespace
    -    metadata["labels"]["odh-ray-cluster-service"] = f"{cluster_name}-head-svc"
    -    spec = route_item.get("generictemplate", {}).get("spec")
    -    spec["to"]["name"] = f"{cluster_name}-head-svc"
    +
    def update_dashboard_ingress(
    +    ingress_item, cluster_name, namespace, ingress_options, ingress_domain
    +):  # pragma: no cover
    +    metadata = ingress_item.get("generictemplate", {}).get("metadata")
    +    spec = ingress_item.get("generictemplate", {}).get("spec")
    +    if ingress_options != {}:
    +        for index, ingress_option in enumerate(ingress_options["ingresses"]):
    +            if "ingressName" not in ingress_option.keys():
    +                raise ValueError(
    +                    f"Error: 'ingressName' is missing or empty for ingress item at index {index}"
    +                )
    +            if "port" not in ingress_option.keys():
    +                raise ValueError(
    +                    f"Error: 'port' is missing or empty for ingress item at index {index}"
    +                )
    +            elif not isinstance(ingress_option["port"], int):
    +                raise ValueError(
    +                    f"Error: 'port' is not of type int for ingress item at index {index}"
    +                )
    +            if ingress_option["port"] == 8265:
    +                metadata["name"] = ingress_option["ingressName"]
    +                metadata["namespace"] = namespace
    +                if "annotations" not in ingress_option.keys():
    +                    del metadata["annotations"]
    +                else:
    +                    metadata["annotations"] = ingress_option["annotations"]
    +                if "path" not in ingress_option.keys():
    +                    del spec["rules"][0]["http"]["paths"][0]["path"]
    +                else:
    +                    spec["rules"][0]["http"]["paths"][0]["path"] = ingress_option[
    +                        "path"
    +                    ]
    +                if "pathType" not in ingress_option.keys():
    +                    spec["rules"][0]["http"]["paths"][0][
    +                        "pathType"
    +                    ] = "ImplementationSpecific"
    +                if "host" not in ingress_option.keys():
    +                    del spec["rules"][0]["host"]
    +                else:
    +                    spec["rules"][0]["host"] = ingress_option["host"]
    +                if "ingressClassName" not in ingress_option.keys():
    +                    del spec["ingressClassName"]
    +                else:
    +                    spec["ingressClassName"] = ingress_option["ingressClassName"]
    +
    +                spec["rules"][0]["http"]["paths"][0]["backend"]["service"][
    +                    "name"
    +                ] = f"{cluster_name}-head-svc"
    +    else:
    +        metadata["name"] = f"ray-dashboard-{cluster_name}"
    +        metadata["namespace"] = namespace
    +        spec["rules"][0]["http"]["paths"][0]["backend"]["service"][
    +            "name"
    +        ] = f"{cluster_name}-head-svc"
    +        if is_openshift_cluster():
    +            try:
    +                config_check()
    +                api_client = client.CustomObjectsApi(api_config_handler())
    +                ingress = api_client.get_cluster_custom_object(
    +                    "config.openshift.io", "v1", "ingresses", "cluster"
    +                )
    +                del spec["ingressClassName"]
    +            except Exception as e:  # pragma: no cover
    +                return _kube_api_error_handling(e)
    +            domain = ingress["spec"]["domain"]
    +        elif ingress_domain is None:
    +            raise ValueError(
    +                "ingress_domain is invalid. For Kubernetes Clusters please specify an ingress domain"
    +            )
    +        else:
    +            domain = ingress_domain
    +        del metadata["annotations"]
    +        spec["rules"][0]["host"] = f"ray-dashboard-{cluster_name}-{namespace}.{domain}"
    @@ -1188,8 +1429,8 @@

    Functions

    spec.pop("priority")
    -
    -def update_rayclient_route(route_item, cluster_name, namespace) +
    +def update_rayclient_ingress(ingress_item, cluster_name, namespace, ingress_domain)
    @@ -1197,13 +1438,51 @@

    Functions

    Expand source code -
    def update_rayclient_route(route_item, cluster_name, namespace):
    -    metadata = route_item.get("generictemplate", {}).get("metadata")
    +
    def update_rayclient_ingress(
    +    ingress_item, cluster_name, namespace, ingress_domain
    +):  # pragma: no cover
    +    metadata = ingress_item.get("generictemplate", {}).get("metadata")
    +    spec = ingress_item.get("generictemplate", {}).get("spec")
         metadata["name"] = f"rayclient-{cluster_name}"
         metadata["namespace"] = namespace
         metadata["labels"]["odh-ray-cluster-service"] = f"{cluster_name}-head-svc"
    -    spec = route_item.get("generictemplate", {}).get("spec")
    -    spec["to"]["name"] = f"{cluster_name}-head-svc"
    + + spec["rules"][0]["http"]["paths"][0]["backend"]["service"][ + "name" + ] = f"{cluster_name}-head-svc" + + if is_openshift_cluster(): + try: + config_check() + api_client = client.CustomObjectsApi(api_config_handler()) + ingress = api_client.get_cluster_custom_object( + "config.openshift.io", "v1", "ingresses", "cluster" + ) + ingressClassName = "openshift-default" + annotations = { + "nginx.ingress.kubernetes.io/rewrite-target": "/", + "nginx.ingress.kubernetes.io/ssl-redirect": "true", + "route.openshift.io/termination": "passthrough", + } + except Exception as e: # pragma: no cover + return _kube_api_error_handling(e) + domain = ingress["spec"]["domain"] + elif ingress_domain is None: + raise ValueError( + "ingress_domain is invalid. For Kubernetes Clusters please specify an ingress domain" + ) + else: + domain = ingress_domain + ingressClassName = "nginx" + annotations = { + "nginx.ingress.kubernetes.io/rewrite-target": "/", + "nginx.ingress.kubernetes.io/ssl-redirect": "true", + "nginx.ingress.kubernetes.io/ssl-passthrough": "true", + } + + metadata["annotations"] = annotations + spec["ingressClassName"] = ingressClassName + spec["rules"][0]["host"] = f"rayclient-{cluster_name}-{namespace}.{domain}"
    @@ -1288,14 +1567,15 @@

    Index

  • disable_raycluster_tls
  • enable_local_interactive
  • enable_openshift_oauth
  • -
  • gen_dashboard_route_name
  • +
  • gen_dashboard_ingress_name
  • gen_names
  • generate_appwrapper
  • +
  • is_openshift_cluster
  • read_template
  • update_affinity
  • update_ca_secret
  • update_custompodresources
  • -
  • update_dashboard_route
  • +
  • update_dashboard_ingress
  • update_env
  • update_image
  • update_image_pull_secrets
  • @@ -1303,7 +1583,7 @@

    Index

  • update_names
  • update_nodes
  • update_priority
  • -
  • update_rayclient_route
  • +
  • update_rayclient_ingress
  • update_resources
  • write_components
  • write_user_appwrapper
  • diff --git a/docs/utils/openshift_oauth.html b/docs/utils/openshift_oauth.html index 0fc27b6b2..e8df74823 100644 --- a/docs/utils/openshift_oauth.html +++ b/docs/utils/openshift_oauth.html @@ -27,7 +27,7 @@

    Module codeflare_sdk.utils.openshift_oauth

    Expand source code
    from urllib3.util import parse_url
    -from .generate_yaml import gen_dashboard_route_name
    +from .generate_yaml import gen_dashboard_ingress_name
     from .kube_api_helpers import _get_api_host
     from base64 import b64decode
     
    @@ -47,7 +47,7 @@ 

    Module codeflare_sdk.utils.openshift_oauth

    host = _get_api_host(api_client) # replace "^api" with the expected host - host = f"{gen_dashboard_route_name(cluster_name)}-{namespace}.apps" + host.lstrip( + host = f"{gen_dashboard_ingress_name(cluster_name)}-{namespace}.apps" + host.lstrip( "api" ) @@ -272,7 +272,7 @@

    Functions

    host = _get_api_host(api_client) # replace "^api" with the expected host - host = f"{gen_dashboard_route_name(cluster_name)}-{namespace}.apps" + host.lstrip( + host = f"{gen_dashboard_ingress_name(cluster_name)}-{namespace}.apps" + host.lstrip( "api" )