diff --git a/cmd/experimental/kjobctl/apis/v1alpha1/application_profile_types.go b/cmd/experimental/kjobctl/apis/v1alpha1/application_profile_types.go index 451ff4ed9e..e96a71217d 100644 --- a/cmd/experimental/kjobctl/apis/v1alpha1/application_profile_types.go +++ b/cmd/experimental/kjobctl/apis/v1alpha1/application_profile_types.go @@ -93,7 +93,6 @@ type TemplateReference string // +kubebuilder:validation:XValidation:rule="!has(self.requiredFlags) || !('ntasks' in self.requiredFlags) || self.name == 'Slurm'", message="ntasks flag can be used only on Slurm mode" // +kubebuilder:validation:XValidation:rule="!has(self.requiredFlags) || !('output' in self.requiredFlags) || self.name == 'Slurm'", message="output flag can be used only on Slurm mode" // +kubebuilder:validation:XValidation:rule="!has(self.requiredFlags) || !('partition' in self.requiredFlags) || self.name == 'Slurm'", message="partition flag can be used only on Slurm mode" -// +kubebuilder:validation:XValidation:rule="!has(self.requiredFlags) || !('priority' in self.requiredFlags) || self.name == 'Slurm'", message="priority flag can be used only on Slurm mode" // +kubebuilder:validation:XValidation:rule="!has(self.requiredFlags) || self.name != 'Slurm' || !('parallelism' in self.requiredFlags)", message="parallelism flag can't be used on Slurm mode" // +kubebuilder:validation:XValidation:rule="!has(self.requiredFlags) || self.name != 'Slurm' || !('completions' in self.requiredFlags)", message="completions flag can't be used on Slurm mode" type SupportedMode struct { @@ -121,9 +120,10 @@ type SupportedMode struct { // The raycluster flag used only for the RayJob mode. // The request flag used only for Interactive and Job modes. // The cmd flag used only for Interactive, Job, and RayJob. + // The skip-priority-workload and priority flags can be used in all modes. // If the raycluster flag are set, none of localqueue, replicas, min-replicas, or max-replicas can be set. // For the Slurm mode, the possible values are: array, cpus-per-task, error, gpus-per-task, input, job-name, mem, mem-per-cpu, - // mem-per-gpu, mem-per-task, nodes, ntasks, output, partition, localqueue, priority. + // mem-per-gpu, mem-per-task, nodes, ntasks, output, partition, localqueue. // // cmd and requests values are going to be added only to the first primary container. // diff --git a/cmd/experimental/kjobctl/config/crd/bases/kjobctl.x-k8s.io_applicationprofiles.yaml b/cmd/experimental/kjobctl/config/crd/bases/kjobctl.x-k8s.io_applicationprofiles.yaml index 537d39d3e7..0a6ee0d7d7 100644 --- a/cmd/experimental/kjobctl/config/crd/bases/kjobctl.x-k8s.io_applicationprofiles.yaml +++ b/cmd/experimental/kjobctl/config/crd/bases/kjobctl.x-k8s.io_applicationprofiles.yaml @@ -62,9 +62,10 @@ spec: The raycluster flag used only for the RayJob mode. The request flag used only for Interactive and Job modes. The cmd flag used only for Interactive, Job, and RayJob. + The skip-priority-workload and priority flags can be used in all modes. If the raycluster flag are set, none of localqueue, replicas, min-replicas, or max-replicas can be set. For the Slurm mode, the possible values are: array, cpus-per-task, error, gpus-per-task, input, job-name, mem, mem-per-cpu, - mem-per-gpu, mem-per-task, nodes, ntasks, output, partition, localqueue, priority. + mem-per-gpu, mem-per-task, nodes, ntasks, output, partition, localqueue. cmd and requests values are going to be added only to the first primary container. items: @@ -183,9 +184,6 @@ spec: - message: partition flag can be used only on Slurm mode rule: '!has(self.requiredFlags) || !(''partition'' in self.requiredFlags) || self.name == ''Slurm''' - - message: priority flag can be used only on Slurm mode - rule: '!has(self.requiredFlags) || !(''priority'' in self.requiredFlags) - || self.name == ''Slurm''' - message: parallelism flag can't be used on Slurm mode rule: '!has(self.requiredFlags) || self.name != ''Slurm'' || !(''parallelism'' in self.requiredFlags)' diff --git a/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_interactive.md b/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_interactive.md index d9a84797f2..021cbd1538 100644 --- a/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_interactive.md +++ b/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_interactive.md @@ -12,7 +12,7 @@ The file is auto-generated from the Go source code of the component using the Create an interactive shell ``` -kjobctl create interactive --profile APPLICATION_PROFILE_NAME [--localqueue LOCAL_QUEUE_NAME] [--skip-localqueue-validation] [--cmd COMMAND] [--request RESOURCE_NAME=QUANTITY] [--pod-running-timeout DURATION] [--rm] +kjobctl create interactive --profile APPLICATION_PROFILE_NAME [--localqueue LOCAL_QUEUE_NAME] [--skip-localqueue-validation] [--priority NAME] [--skip-priority-validation] [--cmd COMMAND] [--request RESOURCE_NAME=QUANTITY] [--pod-running-timeout DURATION] [--rm] ``` @@ -99,6 +99,15 @@ kjobctl create interactive --profile APPLICATION_PROFILE_NAME [--localqueue LOCA

The length of time (like 5s, 2m, or 3h, higher than zero) to wait until at least one pod is running.

+ + --priority string + + + + +

Apply priority for the entire workload.

+ + -p, --profile string @@ -144,6 +153,15 @@ kjobctl create interactive --profile APPLICATION_PROFILE_NAME [--localqueue LOCA

Skip local queue validation. Add local queue even if the queue does not exist.

+ + --skip-priority-validation + + + + +

Skip workload priority class validation. Add priority class label even if the class does not exist.

+ + --template string diff --git a/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_job.md b/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_job.md index 944b79a08f..e0f1279039 100644 --- a/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_job.md +++ b/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_job.md @@ -12,7 +12,7 @@ The file is auto-generated from the Go source code of the component using the Create a job ``` -kjobctl create job --profile APPLICATION_PROFILE_NAME [--localqueue LOCAL_QUEUE_NAME] [--skip-localqueue-validation] [--cmd COMMAND] [--request RESOURCE_NAME=QUANTITY] [--parallelism PARALLELISM] [--completions COMPLETIONS] +kjobctl create job --profile APPLICATION_PROFILE_NAME [--localqueue LOCAL_QUEUE_NAME] [--skip-localqueue-validation] [--priority NAME] [--skip-priority-validation] [--cmd COMMAND] [--request RESOURCE_NAME=QUANTITY] [--parallelism PARALLELISM] [--completions COMPLETIONS] ``` @@ -111,6 +111,15 @@ kjobctl create job --profile APPLICATION_PROFILE_NAME [--localqueue LOCAL_QUEUE_

Parallelism specifies the maximum desired number of pods the job should run at any given time.

+ + --priority string + + + + +

Apply priority for the entire workload.

+ + -p, --profile string @@ -147,6 +156,15 @@ kjobctl create job --profile APPLICATION_PROFILE_NAME [--localqueue LOCAL_QUEUE_

Skip local queue validation. Add local queue even if the queue does not exist.

+ + --skip-priority-validation + + + + +

Skip workload priority class validation. Add priority class label even if the class does not exist.

+ + --template string diff --git a/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_raycluster.md b/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_raycluster.md index af3becab24..c0dd961f94 100644 --- a/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_raycluster.md +++ b/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_raycluster.md @@ -14,7 +14,7 @@ Create a raycluster. KubeRay operator is required for RayCluster. How to install KubeRay operator you can find here https://ray-project.github.io/kuberay/deploy/installation/. ``` -kjobctl create raycluster --profile APPLICATION_PROFILE_NAME [--localqueue LOCAL_QUEUE_NAME] [--skip-localqueue-validation] [--replicas [WORKER_GROUP]=REPLICAS] [--min-replicas [WORKER_GROUP]=MIN_REPLICAS] [--max-replicas [WORKER_GROUP]=MAX_REPLICAS] +kjobctl create raycluster --profile APPLICATION_PROFILE_NAME [--localqueue LOCAL_QUEUE_NAME] [--skip-localqueue-validation] [--priority NAME] [--skip-priority-validation] [--replicas [WORKER_GROUP]=REPLICAS] [--min-replicas [WORKER_GROUP]=MIN_REPLICAS] [--max-replicas [WORKER_GROUP]=MAX_REPLICAS] ``` @@ -103,6 +103,15 @@ kjobctl create raycluster --profile APPLICATION_PROFILE_NAME [--localqueue LOCAL

Output format. One of: (json, yaml, name, go-template, go-template-file, template, templatefile, jsonpath, jsonpath-as-json, jsonpath-file).

+ + --priority string + + + + +

Apply priority for the entire workload.

+ + -p, --profile string @@ -139,6 +148,15 @@ kjobctl create raycluster --profile APPLICATION_PROFILE_NAME [--localqueue LOCAL

Skip local queue validation. Add local queue even if the queue does not exist.

+ + --skip-priority-validation + + + + +

Skip workload priority class validation. Add priority class label even if the class does not exist.

+ + --template string diff --git a/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_rayjob.md b/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_rayjob.md index 1d0e778fb1..1ed7430122 100644 --- a/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_rayjob.md +++ b/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_rayjob.md @@ -14,7 +14,7 @@ Create a rayjob. KubeRay operator is required for RayJob. How to install KubeRay operator you can find here https://ray-project.github.io/kuberay/deploy/installation/. ``` -kjobctl create rayjob --profile APPLICATION_PROFILE_NAME [--localqueue LOCAL_QUEUE_NAME] [--skip-localqueue-validation] [--cmd COMMAND] [--replicas [WORKER_GROUP]=REPLICAS] [--min-replicas [WORKER_GROUP]=MIN_REPLICAS] [--max-replicas [WORKER_GROUP]=MAX_REPLICAS] +kjobctl create rayjob --profile APPLICATION_PROFILE_NAME [--localqueue LOCAL_QUEUE_NAME] [--skip-localqueue-validation] [--priority NAME] [--skip-priority-validation] [--cmd COMMAND] [--replicas [WORKER_GROUP]=REPLICAS] [--min-replicas [WORKER_GROUP]=MIN_REPLICAS] [--max-replicas [WORKER_GROUP]=MAX_REPLICAS] ``` @@ -113,6 +113,15 @@ kjobctl create rayjob --profile APPLICATION_PROFILE_NAME [--localqueue LOCAL_QUE

Output format. One of: (json, yaml, name, go-template, go-template-file, template, templatefile, jsonpath, jsonpath-as-json, jsonpath-file).

+ + --priority string + + + + +

Apply priority for the entire workload.

+ + -p, --profile string @@ -158,6 +167,15 @@ kjobctl create rayjob --profile APPLICATION_PROFILE_NAME [--localqueue LOCAL_QUE

Skip local queue validation. Add local queue even if the queue does not exist.

+ + --skip-priority-validation + + + + +

Skip workload priority class validation. Add priority class label even if the class does not exist.

+ + --template string diff --git a/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_slurm.md b/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_slurm.md index a3541c5dee..6f80852008 100644 --- a/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_slurm.md +++ b/cmd/experimental/kjobctl/docs/commands/kjobctl_create/kjobctl_create_slurm.md @@ -12,7 +12,7 @@ The file is auto-generated from the Go source code of the component using the Create a slurm job ``` -kjobctl create slurm --profile APPLICATION_PROFILE_NAME [--localqueue LOCAL_QUEUE_NAME] [--skip-localqueue-validation] [--ignore-unknown-flags] [--skip-priority-validation] [--init-image IMAGE] [--first-node-ip] [--first-node-ip-timeout DURATION] -- [--array ARRAY] [--cpus-per-task QUANTITY] [--gpus-per-task QUANTITY] [--mem QUANTITY] [--mem-per-task QUANTITY] [--mem-per-cpu QUANTITY] [--mem-per-gpu QUANTITY] [--nodes COUNT] [--ntasks COUNT] [--output FILENAME_PATTERN] [--error FILENAME_PATTERN] [--input FILENAME_PATTERN] [--job-name NAME] [--partition NAME] [--priority NAME] SCRIPT +kjobctl create slurm --profile APPLICATION_PROFILE_NAME [--localqueue LOCAL_QUEUE_NAME] [--skip-localqueue-validation] [--priority NAME] [--skip-priority-validation] [--ignore-unknown-flags] [--init-image IMAGE] [--first-node-ip] [--first-node-ip-timeout DURATION] -- [--array ARRAY] [--cpus-per-task QUANTITY] [--gpus-per-task QUANTITY] [--mem QUANTITY] [--mem-per-task QUANTITY] [--mem-per-cpu QUANTITY] [--mem-per-gpu QUANTITY] [--nodes COUNT] [--ntasks COUNT] [--output FILENAME_PATTERN] [--error FILENAME_PATTERN] [--input FILENAME_PATTERN] [--job-name NAME] [--partition NAME] SCRIPT ``` @@ -115,6 +115,15 @@ kjobctl create slurm --profile APPLICATION_PROFILE_NAME [--localqueue LOCAL_QUEU

Output format. One of: (json, yaml, name, go-template, go-template-file, template, templatefile, jsonpath, jsonpath-as-json, jsonpath-file).

+ + --priority string + + + + +

Apply priority for the entire workload.

+ + -p, --profile string diff --git a/cmd/experimental/kjobctl/pkg/builder/builder.go b/cmd/experimental/kjobctl/pkg/builder/builder.go index 679da8f09f..32c78bfb81 100644 --- a/cmd/experimental/kjobctl/pkg/builder/builder.go +++ b/cmd/experimental/kjobctl/pkg/builder/builder.go @@ -325,6 +325,14 @@ func (b *Builder) validateGeneral(ctx context.Context) error { } } + // check that priority class exists + if len(b.priority) != 0 && !b.skipPriorityValidation { + _, err := b.kueueClientset.KueueV1beta1().WorkloadPriorityClasses().Get(ctx, b.priority, metav1.GetOptions{}) + if err != nil { + return err + } + } + return nil } @@ -533,6 +541,10 @@ func (b *Builder) buildObjectMeta(templateObjectMeta metav1.ObjectMeta) metav1.O objectMeta.Labels[kueueconstants.QueueLabel] = b.localQueue } + if len(b.priority) != 0 { + objectMeta.Labels[kueueconstants.WorkloadPriorityClassLabel] = b.priority + } + return objectMeta } diff --git a/cmd/experimental/kjobctl/pkg/builder/slurm_builder.go b/cmd/experimental/kjobctl/pkg/builder/slurm_builder.go index 212334ffa1..f0381d59d3 100644 --- a/cmd/experimental/kjobctl/pkg/builder/slurm_builder.go +++ b/cmd/experimental/kjobctl/pkg/builder/slurm_builder.go @@ -35,7 +35,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" utilrand "k8s.io/apimachinery/pkg/util/rand" "k8s.io/utils/ptr" - kueue "sigs.k8s.io/kueue/pkg/controller/constants" "sigs.k8s.io/kueue/cmd/experimental/kjobctl/apis/v1alpha1" "sigs.k8s.io/kueue/cmd/experimental/kjobctl/pkg/parser" @@ -116,7 +115,7 @@ type slurmBuilder struct { var _ builder = (*slurmBuilder)(nil) -func (b *slurmBuilder) validateGeneral(ctx context.Context) error { +func (b *slurmBuilder) validateGeneral() error { if len(b.script) == 0 { return noScriptSpecifiedErr } @@ -129,14 +128,6 @@ func (b *slurmBuilder) validateGeneral(ctx context.Context) error { return noGpusPerTaskSpecifiedErr } - // check that priority class exists - if len(b.priority) != 0 && !b.skipPriorityValidation { - _, err := b.kueueClientset.KueueV1beta1().WorkloadPriorityClasses().Get(ctx, b.priority, metav1.GetOptions{}) - if err != nil { - return err - } - } - return nil } @@ -213,7 +204,7 @@ func (b *slurmBuilder) validateMutuallyExclusiveFlags() error { } func (b *slurmBuilder) build(ctx context.Context) (runtime.Object, []runtime.Object, error) { - if err := b.validateGeneral(ctx); err != nil { + if err := b.validateGeneral(); err != nil { return nil, nil, err } @@ -239,10 +230,6 @@ func (b *slurmBuilder) build(ctx context.Context) (runtime.Object, []runtime.Obj job.Spec.CompletionMode = ptr.To(batchv1.IndexedCompletion) job.Spec.Template.Spec.Subdomain = b.objectName - if len(b.priority) != 0 { - job.Labels[kueue.WorkloadPriorityClassLabel] = b.priority - } - b.buildPodSpecVolumesAndEnv(&job.Spec.Template.Spec) job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes, corev1.Volume{ diff --git a/cmd/experimental/kjobctl/pkg/cmd/create/create.go b/cmd/experimental/kjobctl/pkg/cmd/create/create.go index 9a55073b36..bc34f002c7 100644 --- a/cmd/experimental/kjobctl/pkg/cmd/create/create.go +++ b/cmd/experimental/kjobctl/pkg/cmd/create/create.go @@ -330,7 +330,6 @@ var createModeSubcommands = map[string]modeSubcommand{ ModeName: v1alpha1.SlurmMode, Setup: func(clientGetter util.ClientGetter, subcmd *cobra.Command, o *CreateOptions) { subcmd.Use += " [--ignore-unknown-flags]" + - " [--skip-priority-validation]" + " [--init-image IMAGE]" + " [--first-node-ip]" + " [--first-node-ip-timeout DURATION]" + @@ -349,7 +348,6 @@ var createModeSubcommands = map[string]modeSubcommand{ " [--input FILENAME_PATTERN]" + " [--job-name NAME]" + " [--partition NAME]" + - " [--priority NAME]" + " SCRIPT" subcmd.Short = "Create a slurm job" @@ -360,8 +358,6 @@ var createModeSubcommands = map[string]modeSubcommand{ "Ignore all the unsupported flags in the bash script.") subcmd.Flags().StringVar(&o.InitImage, initImageFlagName, "registry.k8s.io/busybox:1.27.2", "The image used for the init container.") - subcmd.Flags().BoolVar(&o.SkipPriorityValidation, skipPriorityValidationFlagName, false, - "Skip workload priority class validation. Add priority class label even if the class does not exist.") subcmd.Flags().BoolVar(&o.FirstNodeIP, firstNodeIPFlagName, false, "Enable the retrieval of the first node's IP address.") subcmd.Flags().DurationVar(&o.FirstNodeIPTimeout, firstNodeIPTimeoutFlagName, time.Minute, @@ -400,8 +396,6 @@ The minimum index value is 0. The maximum index value is 2147483647.`) "What is the job name.") o.SlurmFlagSet.StringVar(&o.Partition, partitionFlagName, "", "Local queue name.") - o.SlurmFlagSet.StringVar(&o.Priority, priorityFlagName, "", - "Apply priority for the entire workload.") o.SlurmFlagSet.StringVarP(&o.ChangeDir, changeDirFlagName, "D", "", "Change directory before executing the script.") }, @@ -428,7 +422,9 @@ func NewCreateCmd(clientGetter util.ClientGetter, streams genericiooptions.IOStr Use: modeName + " --profile APPLICATION_PROFILE_NAME" + " [--localqueue LOCAL_QUEUE_NAME]" + - " [--skip-localqueue-validation]", + " [--skip-localqueue-validation]" + + " [--priority NAME]" + + " [--skip-priority-validation]", DisableFlagsInUseLine: true, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { @@ -451,6 +447,10 @@ func NewCreateCmd(clientGetter util.ClientGetter, streams genericiooptions.IOStr "Kueue localqueue name which is associated with the resource.") subcmd.Flags().BoolVar(&o.SkipLocalQueueValidation, skipLocalQueueValidationFlagName, false, "Skip local queue validation. Add local queue even if the queue does not exist.") + subcmd.Flags().StringVar(&o.Priority, priorityFlagName, "", + "Apply priority for the entire workload.") + subcmd.Flags().BoolVar(&o.SkipPriorityValidation, skipPriorityValidationFlagName, false, + "Skip workload priority class validation. Add priority class label even if the class does not exist.") modeSubcommand.Setup(clientGetter, subcmd, o) diff --git a/cmd/experimental/kjobctl/pkg/cmd/create/create_test.go b/cmd/experimental/kjobctl/pkg/cmd/create/create_test.go index 36c9709d05..30e807ff1e 100644 --- a/cmd/experimental/kjobctl/pkg/cmd/create/create_test.go +++ b/cmd/experimental/kjobctl/pkg/cmd/create/create_test.go @@ -341,6 +341,69 @@ func TestCreateCmd(t *testing.T) { // Fake dynamic client not generating name. That's why we have . wantOut: "job.batch/ created\n", }, + "should create job with --priority flag": { + args: func(tc *createCmdTestCase) []string { + return []string{"job", "--profile", "profile", "--priority", "sample-priority"} + }, + kjobctlObjs: []runtime.Object{ + wrappers.MakeJobTemplate("job-template", metav1.NamespaceDefault). + Obj(), + wrappers.MakeApplicationProfile("profile", metav1.NamespaceDefault). + WithSupportedMode(*wrappers.MakeSupportedMode(v1alpha1.JobMode, "job-template").Obj()). + Obj(), + }, + kueueObjs: []runtime.Object{ + &kueue.WorkloadPriorityClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sample-priority", + }, + }, + }, + gvks: []schema.GroupVersionKind{{Group: "batch", Version: "v1", Kind: "Job"}}, + wantLists: []runtime.Object{ + &batchv1.JobList{ + TypeMeta: metav1.TypeMeta{Kind: "JobList", APIVersion: "batch/v1"}, + Items: []batchv1.Job{ + *wrappers.MakeJob("", metav1.NamespaceDefault). + Priority("sample-priority"). + GenerateName("profile-job-"). + Profile("profile"). + Mode(v1alpha1.JobMode). + Obj(), + }, + }, + }, + // Fake dynamic client not generating name. That's why we have . + wantOut: "job.batch/ created\n", + }, + "should create job with --priority flag and skip workload priority class validation": { + args: func(tc *createCmdTestCase) []string { + return []string{"job", "--profile", "profile", "--skip-priority-validation", "--priority", "sample-priority"} + }, + kjobctlObjs: []runtime.Object{ + wrappers.MakeJobTemplate("job-template", metav1.NamespaceDefault). + Obj(), + wrappers.MakeApplicationProfile("profile", metav1.NamespaceDefault). + WithSupportedMode(*wrappers.MakeSupportedMode(v1alpha1.JobMode, "job-template").Obj()). + Obj(), + }, + gvks: []schema.GroupVersionKind{{Group: "batch", Version: "v1", Kind: "Job"}}, + wantLists: []runtime.Object{ + &batchv1.JobList{ + TypeMeta: metav1.TypeMeta{Kind: "JobList", APIVersion: "batch/v1"}, + Items: []batchv1.Job{ + *wrappers.MakeJob("", metav1.NamespaceDefault). + Priority("sample-priority"). + GenerateName("profile-job-"). + Profile("profile"). + Mode(v1alpha1.JobMode). + Obj(), + }, + }, + }, + // Fake dynamic client not generating name. That's why we have . + wantOut: "job.batch/ created\n", + }, "should create job with completions replacement": { args: func(tc *createCmdTestCase) []string { return []string{"job", "--profile", "profile", "--completions", "5"} @@ -623,6 +686,69 @@ func TestCreateCmd(t *testing.T) { // Fake dynamic client not generating name. That's why we have . wantOut: "rayjob.ray.io/ created\n", }, + "should create ray job with --priority flag": { + args: func(tc *createCmdTestCase) []string { + return []string{"rayjob", "--profile", "profile", "--priority", "sample-priority"} + }, + kjobctlObjs: []runtime.Object{ + wrappers.MakeRayJobTemplate("ray-job-template", metav1.NamespaceDefault). + Obj(), + wrappers.MakeApplicationProfile("profile", metav1.NamespaceDefault). + WithSupportedMode(*wrappers.MakeSupportedMode(v1alpha1.RayJobMode, "ray-job-template").Obj()). + Obj(), + }, + kueueObjs: []runtime.Object{ + &kueue.WorkloadPriorityClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sample-priority", + }, + }, + }, + gvks: []schema.GroupVersionKind{{Group: "ray.io", Version: "v1", Kind: "RayJob"}}, + wantLists: []runtime.Object{ + &rayv1.RayJobList{ + TypeMeta: metav1.TypeMeta{Kind: "RayJobList", APIVersion: "ray.io/v1"}, + Items: []rayv1.RayJob{ + *wrappers.MakeRayJob("", metav1.NamespaceDefault). + Priority("sample-priority"). + GenerateName("profile-rayjob-"). + Profile("profile"). + Mode(v1alpha1.RayJobMode). + Obj(), + }, + }, + }, + // Fake dynamic client not generating name. That's why we have . + wantOut: "rayjob.ray.io/ created\n", + }, + "should create ray job with --priority flag and skip workload priority class validation": { + args: func(tc *createCmdTestCase) []string { + return []string{"rayjob", "--profile", "profile", "--skip-priority-validation", "--priority", "sample-priority"} + }, + kjobctlObjs: []runtime.Object{ + wrappers.MakeRayJobTemplate("ray-job-template", metav1.NamespaceDefault). + Obj(), + wrappers.MakeApplicationProfile("profile", metav1.NamespaceDefault). + WithSupportedMode(*wrappers.MakeSupportedMode(v1alpha1.RayJobMode, "ray-job-template").Obj()). + Obj(), + }, + gvks: []schema.GroupVersionKind{{Group: "ray.io", Version: "v1", Kind: "RayJob"}}, + wantLists: []runtime.Object{ + &rayv1.RayJobList{ + TypeMeta: metav1.TypeMeta{Kind: "RayJobList", APIVersion: "ray.io/v1"}, + Items: []rayv1.RayJob{ + *wrappers.MakeRayJob("", metav1.NamespaceDefault). + Priority("sample-priority"). + GenerateName("profile-rayjob-"). + Profile("profile"). + Mode(v1alpha1.RayJobMode). + Obj(), + }, + }, + }, + // Fake dynamic client not generating name. That's why we have . + wantOut: "rayjob.ray.io/ created\n", + }, "shouldn't create ray job with raycluster and localqueue replacements because mutually exclusive": { args: func(tc *createCmdTestCase) []string { return []string{"rayjob", "--profile", "profile", "--raycluster", "rc1", "--localqueue", "lq1"} @@ -671,6 +797,76 @@ func TestCreateCmd(t *testing.T) { // Fake dynamic client not generating name. That's why we have . wantOut: "raycluster.ray.io/ created\n", }, + "should create raycluster with --priority flag": { + args: func(tc *createCmdTestCase) []string { + return []string{ + "raycluster", + "--profile", "profile", + "--priority", "sample-priority", + } + }, + kjobctlObjs: []runtime.Object{ + wrappers.MakeRayClusterTemplate("ray-cluster-template", metav1.NamespaceDefault).Obj(), + wrappers.MakeApplicationProfile("profile", metav1.NamespaceDefault). + WithSupportedMode(*wrappers.MakeSupportedMode(v1alpha1.RayClusterMode, "ray-cluster-template").Obj()). + Obj(), + }, + kueueObjs: []runtime.Object{ + &kueue.WorkloadPriorityClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sample-priority", + }, + }, + }, + gvks: []schema.GroupVersionKind{{Group: "ray.io", Version: "v1", Kind: "RayCluster"}}, + wantLists: []runtime.Object{ + &rayv1.RayClusterList{ + TypeMeta: metav1.TypeMeta{Kind: "RayClusterList", APIVersion: "ray.io/v1"}, + Items: []rayv1.RayCluster{ + *wrappers.MakeRayCluster("", metav1.NamespaceDefault). + Priority("sample-priority"). + GenerateName("profile-raycluster-"). + Profile("profile"). + Mode(v1alpha1.RayClusterMode). + Obj(), + }, + }, + }, + // Fake dynamic client not generating name. That's why we have . + wantOut: "raycluster.ray.io/ created\n", + }, + "should create raycluster with --priority flag and skip workload priority class validation": { + args: func(tc *createCmdTestCase) []string { + return []string{ + "raycluster", + "--profile", "profile", + "--skip-priority-validation", + "--priority", "sample-priority", + } + }, + kjobctlObjs: []runtime.Object{ + wrappers.MakeRayClusterTemplate("ray-cluster-template", metav1.NamespaceDefault).Obj(), + wrappers.MakeApplicationProfile("profile", metav1.NamespaceDefault). + WithSupportedMode(*wrappers.MakeSupportedMode(v1alpha1.RayClusterMode, "ray-cluster-template").Obj()). + Obj(), + }, + gvks: []schema.GroupVersionKind{{Group: "ray.io", Version: "v1", Kind: "RayCluster"}}, + wantLists: []runtime.Object{ + &rayv1.RayClusterList{ + TypeMeta: metav1.TypeMeta{Kind: "RayClusterList", APIVersion: "ray.io/v1"}, + Items: []rayv1.RayCluster{ + *wrappers.MakeRayCluster("", metav1.NamespaceDefault). + Priority("sample-priority"). + GenerateName("profile-raycluster-"). + Profile("profile"). + Mode(v1alpha1.RayClusterMode). + Obj(), + }, + }, + }, + // Fake dynamic client not generating name. That's why we have . + wantOut: "raycluster.ray.io/ created\n", + }, "shouldn't create slurm because slurm args must be specified": { args: func(tc *createCmdTestCase) []string { return []string{"slurm", "--profile", "profile"} @@ -1478,8 +1674,8 @@ cd /mydir return []string{ "slurm", "--profile", "profile", - "--", "--priority", "sample-priority", + "--", tc.tempFile, } }, @@ -1640,9 +1836,9 @@ cd /mydir return []string{ "slurm", "--profile", "profile", + "--priority", "sample-priority", "--skip-priority-validation", "--", - "--priority", "sample-priority", tc.tempFile, } }, diff --git a/cmd/experimental/kjobctl/pkg/cmd/printcrds/embed/manifest.gz b/cmd/experimental/kjobctl/pkg/cmd/printcrds/embed/manifest.gz index 8b9e98e165..24ddd146c4 100644 Binary files a/cmd/experimental/kjobctl/pkg/cmd/printcrds/embed/manifest.gz and b/cmd/experimental/kjobctl/pkg/cmd/printcrds/embed/manifest.gz differ diff --git a/cmd/experimental/kjobctl/pkg/testing/wrappers/ray_cluster_wrappers.go b/cmd/experimental/kjobctl/pkg/testing/wrappers/ray_cluster_wrappers.go index 60072127a2..ad0f45e6e3 100644 --- a/cmd/experimental/kjobctl/pkg/testing/wrappers/ray_cluster_wrappers.go +++ b/cmd/experimental/kjobctl/pkg/testing/wrappers/ray_cluster_wrappers.go @@ -171,3 +171,8 @@ func (j *RayClusterWrapper) Reason(reason string) *RayClusterWrapper { j.RayCluster.Status.Reason = reason return j } + +// Priority sets the workload priority class label. +func (j *RayClusterWrapper) Priority(v string) *RayClusterWrapper { + return j.Label(kueueconstants.WorkloadPriorityClassLabel, v) +} diff --git a/cmd/experimental/kjobctl/pkg/testing/wrappers/ray_job_wrappers.go b/cmd/experimental/kjobctl/pkg/testing/wrappers/ray_job_wrappers.go index d93ff4f084..b8557d6544 100644 --- a/cmd/experimental/kjobctl/pkg/testing/wrappers/ray_job_wrappers.go +++ b/cmd/experimental/kjobctl/pkg/testing/wrappers/ray_job_wrappers.go @@ -174,3 +174,8 @@ func (j *RayJobWrapper) RayClusterName(rayClusterName string) *RayJobWrapper { j.RayJob.Status.RayClusterName = rayClusterName return j } + +// Priority sets the workload priority class label. +func (j *RayJobWrapper) Priority(v string) *RayJobWrapper { + return j.Label(kueueconstants.WorkloadPriorityClassLabel, v) +}