-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for Kueue scheduling options to RayJob runner (#24)
* feat(rayjob): Support Kueue scheduling options in RayJob runner * chore: Separate out `util` module into package
- Loading branch information
Showing
11 changed files
with
155 additions
and
126 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from __future__ import annotations | ||
|
||
from typing import Any, Mapping, TypeVar, cast | ||
|
||
T = TypeVar("T", bound=Mapping[str, Any]) | ||
|
||
|
||
def remove_none_values(d: T) -> T: | ||
"""Remove all keys with a ``None`` value from a dict.""" | ||
filtered_dict = {k: v for k, v in d.items() if v is not None} | ||
return cast(T, filtered_dict) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from __future__ import annotations | ||
|
||
import kubernetes | ||
|
||
|
||
def sanitize_rfc1123_domain_name(s: str) -> str: | ||
"""Sanitize a string to be compliant with RFC 1123 domain name | ||
Note: Any invalid characters are replaced with dashes.""" | ||
|
||
# TODO: This is obviously wildly incomplete | ||
return s.replace("_", "-") | ||
|
||
|
||
class KubernetesNamespaceMixin: | ||
"""Determine the desired or current Kubernetes namespace.""" | ||
|
||
def __init__(self, **kwargs): | ||
kubernetes.config.load_config() | ||
self._namespace: str | None = kwargs.get("namespace") | ||
|
||
@property | ||
def namespace(self) -> str: | ||
_, active_context = kubernetes.config.list_kube_config_contexts() | ||
current_namespace = active_context["context"].get("namespace") | ||
return self._namespace or current_namespace |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
from typing import Mapping, cast | ||
|
||
from kubernetes import client | ||
|
||
from jobs.job import Job | ||
from jobs.utils.helpers import remove_none_values | ||
|
||
|
||
def assert_kueue_localqueue(namespace: str, name: str) -> bool: | ||
"""Check the existence of a Kueue `LocalQueue` in a namespace.""" | ||
try: | ||
_ = client.CustomObjectsApi().get_namespaced_custom_object( | ||
"kueue.x-k8s.io", | ||
"v1beta1", | ||
namespace, | ||
"localqueues", | ||
name, | ||
) | ||
return True | ||
except client.exceptions.ApiException: | ||
return False | ||
|
||
|
||
def assert_kueue_workloadpriorityclass(name: str) -> bool: | ||
"""Check the existence of a Kueue `WorkloadPriorityClass` in the cluster.""" | ||
try: | ||
_ = client.CustomObjectsApi().get_cluster_custom_object( | ||
"kueue.x-k8s.io", | ||
"v1beta1", | ||
"workloadpriorityclasses", | ||
name, | ||
) | ||
return True | ||
except client.exceptions.ApiException: | ||
return False | ||
|
||
|
||
def kueue_scheduling_labels(job: Job, namespace: str) -> Mapping[str, str]: | ||
"""Determine the Kubernetes labels controlling Kueue features such as queues and priority for a job.""" | ||
|
||
if not job.options: | ||
return {} | ||
if not (sched_opts := job.options.scheduling): | ||
return {} | ||
|
||
if queue := sched_opts.queue_name: | ||
if not assert_kueue_localqueue(namespace, queue): | ||
raise ValueError(f"Specified Kueue local queue does not exist: {queue!r}") | ||
if pc := sched_opts.priority_class: | ||
if not assert_kueue_workloadpriorityclass(pc): | ||
raise ValueError( | ||
f"Specified Kueue workload priority class does not exist: {pc!r}" | ||
) | ||
|
||
return cast( | ||
Mapping[str, str], | ||
remove_none_values( | ||
{ | ||
"kueue.x-k8s.io/queue-name": ( | ||
sched_opts.queue_name if sched_opts else None | ||
), | ||
"kueue.x-k8s.io/priority-class": ( | ||
sched_opts.priority_class if sched_opts else None | ||
), | ||
} | ||
), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
from __future__ import annotations | ||
|
||
import re | ||
|
||
|
||
def to_rational(s: str) -> float: | ||
"""Convert a number with optional SI/binary unit to floating-point""" | ||
|
||
matches = re.match(r"(?P<magnitude>[+\-]?\d*[.,]?\d+)(?P<suffix>[a-zA-Z]*)", s) | ||
if not matches: | ||
raise ValueError(f"Could not parse {s}") | ||
magnitude = float(matches.group("magnitude")) | ||
suffix = matches.group("suffix") | ||
|
||
factor = { | ||
# SI / Metric | ||
"m": 1e-3, | ||
"k": 1e3, | ||
"M": 1e6, | ||
"G": 1e9, | ||
"T": 1e12, | ||
# Binary | ||
"Ki": 2**10, | ||
"Mi": 2**20, | ||
"Gi": 2**30, | ||
"Ti": 2**40, | ||
# default | ||
"": 1.0, | ||
}.get(suffix) | ||
if factor is None: | ||
raise ValueError(f"unknown unit suffix: {suffix}") | ||
|
||
return factor * magnitude |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters