diff --git a/api/jobset/v1alpha2/jobset_types.go b/api/jobset/v1alpha2/jobset_types.go index d0a417b4b..439bb2521 100644 --- a/api/jobset/v1alpha2/jobset_types.go +++ b/api/jobset/v1alpha2/jobset_types.go @@ -40,12 +40,9 @@ const ( NamespacedJobKey string = "alpha.jobset.sigs.k8s.io/namespaced-job" NoScheduleTaintKey string = "alpha.jobset.sigs.k8s.io/no-schedule" - // LabelManagedBy is used to indicate the controller or entity that manages an JobSet - LabelManagedBy = "alpha.jobset.sigs.k8s.io/managed-by" - - // JobSetManager is used as the value for LabelManagedBy to identify the jobset controller manager - // as the manager of a specific JobSet. - JobSetManager = "jobset" + // JobSetControllerName is the reserved value for the managedBy field for the built-in + // JobSet controller. + JobSetControllerName = "jobset.sigs.k8s.io/jobset-controller" ) type JobSetConditionType string @@ -94,6 +91,9 @@ type JobSetSpec struct { // Suspend suspends all running child Jobs when set to true. Suspend *bool `json:"suspend,omitempty"` + + // ManagedBy is used to indicate the controller or entity that manages a JobSet + ManagedBy *string `json:"managedBy,omitempty"` } // JobSetStatus defines the observed state of JobSet diff --git a/api/jobset/v1alpha2/jobset_webhook.go b/api/jobset/v1alpha2/jobset_webhook.go index a6e7e96af..1fb478e07 100644 --- a/api/jobset/v1alpha2/jobset_webhook.go +++ b/api/jobset/v1alpha2/jobset_webhook.go @@ -37,6 +37,9 @@ import ( corev1 "k8s.io/api/core/v1" ) +// maximum lnegth of the value of the managedBy field +const maxManagedByLength = 63 + const ( // This is the error message returned by IsDNS1035Label when the given input // is longer than 63 characters. @@ -93,11 +96,8 @@ func (js *JobSet) Default() { } } - if _, found := js.Labels[LabelManagedBy]; !found { - if js.Labels == nil { - js.Labels = make(map[string]string, 1) - } - js.Labels[LabelManagedBy] = JobSetManager + if js.Spec.ManagedBy == nil { + js.Spec.ManagedBy = ptr.To(JobSetControllerName) } } @@ -128,6 +128,17 @@ func (js *JobSet) ValidateCreate() (admission.Warnings, error) { } } + if js.Spec.ManagedBy != nil { + manager := *js.Spec.ManagedBy + fieldPath := field.NewPath("spec", "managedBy") + for _, err := range validation.IsDomainPrefixedPath(fieldPath, manager) { + allErrs = append(allErrs, err) + } + if len(manager) > maxManagedByLength { + allErrs = append(allErrs, field.TooLongMaxLength(fieldPath, manager, maxManagedByLength)) + } + } + for _, rjob := range js.Spec.ReplicatedJobs { var parallelism int32 = 1 if rjob.Template.Spec.Parallelism != nil { @@ -179,7 +190,7 @@ func (js *JobSet) ValidateUpdate(old runtime.Object) (admission.Warnings, error) } // Note that SucccessPolicy and failurePolicy are made immutable via CEL. errs := apivalidation.ValidateImmutableField(mungedSpec.ReplicatedJobs, oldJS.Spec.ReplicatedJobs, field.NewPath("spec").Child("replicatedJobs")) - errs = append(errs, apivalidation.ValidateImmutableField(js.Labels[LabelManagedBy], oldJS.Labels[LabelManagedBy], field.NewPath("metadata").Child("labels").Key(LabelManagedBy))...) + errs = append(errs, apivalidation.ValidateImmutableField(mungedSpec.ManagedBy, oldJS.Spec.ManagedBy, field.NewPath("spec").Child("labels").Key("managedBy"))...) return nil, errs.ToAggregate() } diff --git a/api/jobset/v1alpha2/jobset_webhook_test.go b/api/jobset/v1alpha2/jobset_webhook_test.go index 674f00441..6d66275de 100644 --- a/api/jobset/v1alpha2/jobset_webhook_test.go +++ b/api/jobset/v1alpha2/jobset_webhook_test.go @@ -12,6 +12,8 @@ import ( batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/utils/ptr" ) @@ -39,9 +41,6 @@ func TestJobSetDefaulting(t *testing.T) { { name: "job completion mode is unset", js: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: JobSetManager}, - }, Spec: JobSetSpec{ SuccessPolicy: defaultSuccessPolicy, StartupPolicy: defaultStartupPolicy, @@ -55,12 +54,10 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To(JobSetControllerName), }, }, want: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: JobSetManager}, - }, Spec: JobSetSpec{ SuccessPolicy: defaultSuccessPolicy, StartupPolicy: defaultStartupPolicy, @@ -75,15 +72,13 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To(JobSetControllerName), }, }, }, { name: "job completion mode is set to non-indexed", js: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: JobSetManager}, - }, Spec: JobSetSpec{ SuccessPolicy: defaultSuccessPolicy, Network: &Network{EnableDNSHostnames: ptr.To(true)}, @@ -97,12 +92,10 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To(JobSetControllerName), }, }, want: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: JobSetManager}, - }, Spec: JobSetSpec{ SuccessPolicy: defaultSuccessPolicy, StartupPolicy: defaultStartupPolicy, @@ -117,15 +110,13 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To(JobSetControllerName), }, }, }, { name: "enableDNSHostnames is unset", js: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: JobSetManager}, - }, Spec: JobSetSpec{ SuccessPolicy: defaultSuccessPolicy, StartupPolicy: defaultStartupPolicy, @@ -139,12 +130,10 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To(JobSetControllerName), }, }, want: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: JobSetManager}, - }, Spec: JobSetSpec{ SuccessPolicy: defaultSuccessPolicy, StartupPolicy: defaultStartupPolicy, @@ -159,15 +148,13 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To(JobSetControllerName), }, }, }, { name: "enableDNSHostnames is false", js: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: JobSetManager}, - }, Spec: JobSetSpec{ SuccessPolicy: defaultSuccessPolicy, StartupPolicy: defaultStartupPolicy, @@ -182,12 +169,10 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To(JobSetControllerName), }, }, want: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: JobSetManager}, - }, Spec: JobSetSpec{ SuccessPolicy: defaultSuccessPolicy, StartupPolicy: defaultStartupPolicy, @@ -202,15 +187,13 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To(JobSetControllerName), }, }, }, { name: "pod restart policy unset", js: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: JobSetManager}, - }, Spec: JobSetSpec{ SuccessPolicy: defaultSuccessPolicy, StartupPolicy: defaultStartupPolicy, @@ -227,12 +210,10 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To(JobSetControllerName), }, }, want: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: JobSetManager}, - }, Spec: JobSetSpec{ SuccessPolicy: defaultSuccessPolicy, StartupPolicy: defaultStartupPolicy, @@ -251,15 +232,13 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To(JobSetControllerName), }, }, }, { name: "pod restart policy set", js: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: JobSetManager}, - }, Spec: JobSetSpec{ SuccessPolicy: defaultSuccessPolicy, StartupPolicy: defaultStartupPolicy, @@ -278,12 +257,10 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To(JobSetControllerName), }, }, want: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: JobSetManager}, - }, Spec: JobSetSpec{ SuccessPolicy: defaultSuccessPolicy, StartupPolicy: defaultStartupPolicy, @@ -302,15 +279,13 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To(JobSetControllerName), }, }, }, { name: "success policy unset", js: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: JobSetManager}, - }, Spec: JobSetSpec{ StartupPolicy: defaultStartupPolicy, Network: &Network{EnableDNSHostnames: ptr.To(true)}, @@ -328,12 +303,10 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To(JobSetControllerName), }, }, want: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: JobSetManager}, - }, Spec: JobSetSpec{ StartupPolicy: defaultStartupPolicy, SuccessPolicy: defaultSuccessPolicy, @@ -352,15 +325,13 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To(JobSetControllerName), }, }, }, { name: "success policy operator set, replicatedJobNames unset", js: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: JobSetManager}, - }, Spec: JobSetSpec{ SuccessPolicy: &SuccessPolicy{ Operator: OperatorAny, @@ -381,12 +352,10 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To(JobSetControllerName), }, }, want: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: JobSetManager}, - }, Spec: JobSetSpec{ SuccessPolicy: &SuccessPolicy{ Operator: OperatorAny, @@ -407,6 +376,7 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To(JobSetControllerName), }, }, }, @@ -438,9 +408,6 @@ func TestJobSetDefaulting(t *testing.T) { }, }, want: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: JobSetManager}, - }, Spec: JobSetSpec{ SuccessPolicy: &SuccessPolicy{ Operator: OperatorAny, @@ -463,6 +430,7 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To(JobSetControllerName), }, }, }, @@ -485,9 +453,6 @@ func TestJobSetDefaulting(t *testing.T) { }, }, want: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: JobSetManager}, - }, Spec: JobSetSpec{ SuccessPolicy: defaultSuccessPolicy, StartupPolicy: defaultStartupPolicy, @@ -502,15 +467,13 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To(JobSetControllerName), }, }, }, { name: "when provided, managed-by label is preserved", js: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: "other-controller"}, - }, Spec: JobSetSpec{ SuccessPolicy: defaultSuccessPolicy, Network: &Network{EnableDNSHostnames: ptr.To(true)}, @@ -524,12 +487,10 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To("other-controller"), }, }, want: &JobSet{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{LabelManagedBy: "other-controller"}, - }, Spec: JobSetSpec{ SuccessPolicy: defaultSuccessPolicy, StartupPolicy: defaultStartupPolicy, @@ -544,6 +505,7 @@ func TestJobSetDefaulting(t *testing.T) { }, }, }, + ManagedBy: ptr.To("other-controller"), }, }, }, @@ -559,9 +521,22 @@ func TestJobSetDefaulting(t *testing.T) { } func TestValidateCreate(t *testing.T) { + managedByFieldPath := field.NewPath("spec", "managedBy") + + notDomainPrefixedPathControllerName := "notDomainPrefixedPathControllerName" + var notDomainPrefixedPathControllerErrors []error + for _, err := range validation.IsDomainPrefixedPath(managedByFieldPath, notDomainPrefixedPathControllerName) { + notDomainPrefixedPathControllerErrors = append(notDomainPrefixedPathControllerErrors, err) + } + + maxManagedByLength := 63 + tooLongControllerName := "foo.bar/" + strings.Repeat("a", maxManagedByLength) + tooLongControllerNameError := field.TooLongMaxLength(managedByFieldPath, tooLongControllerName, maxManagedByLength) + validObjectMeta := metav1.ObjectMeta{ Name: "js", } + testCases := []struct { name string js *JobSet @@ -738,6 +713,105 @@ func TestValidateCreate(t *testing.T) { fmt.Errorf(podNameTooLongErrorMsg), ), }, + { + name: "jobset controller name is not a domain-prefixed path", + js: &JobSet{ + ObjectMeta: validObjectMeta, + Spec: JobSetSpec{ + ManagedBy: ptr.To(notDomainPrefixedPathControllerName), + ReplicatedJobs: []ReplicatedJob{ + { + Name: "rj", + Replicas: 1, + Template: batchv1.JobTemplateSpec{ + Spec: batchv1.JobSpec{ + CompletionMode: ptr.To(batchv1.IndexedCompletion), + Completions: ptr.To(int32(1)), + Parallelism: ptr.To(int32(1)), + }, + }, + }, + }, + SuccessPolicy: &SuccessPolicy{}, + }, + }, + want: errors.Join( + notDomainPrefixedPathControllerErrors..., + ), + }, + { + name: "jobset controller name is too long", + js: &JobSet{ + ObjectMeta: validObjectMeta, + Spec: JobSetSpec{ + ManagedBy: ptr.To(tooLongControllerName), + ReplicatedJobs: []ReplicatedJob{ + { + Name: "rj", + Replicas: 1, + Template: batchv1.JobTemplateSpec{ + Spec: batchv1.JobSpec{ + CompletionMode: ptr.To(batchv1.IndexedCompletion), + Completions: ptr.To(int32(1)), + Parallelism: ptr.To(int32(1)), + }, + }, + }, + }, + SuccessPolicy: &SuccessPolicy{}, + }, + }, + want: errors.Join( + tooLongControllerNameError, + ), + }, + { + name: "jobset controller name is set and valid", + js: &JobSet{ + ObjectMeta: validObjectMeta, + Spec: JobSetSpec{ + ManagedBy: ptr.To(JobSetControllerName), + ReplicatedJobs: []ReplicatedJob{ + { + Name: "rj", + Replicas: 1, + Template: batchv1.JobTemplateSpec{ + Spec: batchv1.JobSpec{ + CompletionMode: ptr.To(batchv1.IndexedCompletion), + Completions: ptr.To(int32(1)), + Parallelism: ptr.To(int32(1)), + }, + }, + }, + }, + SuccessPolicy: &SuccessPolicy{}, + }, + }, + want: errors.Join(), + }, + { + name: "jobset controller name is unset", + js: &JobSet{ + ObjectMeta: validObjectMeta, + Spec: JobSetSpec{ + ReplicatedJobs: []ReplicatedJob{ + { + Name: "rj", + Replicas: 1, + Template: batchv1.JobTemplateSpec{ + Spec: batchv1.JobSpec{ + CompletionMode: ptr.To(batchv1.IndexedCompletion), + Completions: ptr.To(int32(1)), + Parallelism: ptr.To(int32(1)), + }, + }, + }, + }, + SuccessPolicy: &SuccessPolicy{}, + }, + }, + want: errors.Join(), + }, } for _, tc := range testCases { diff --git a/api/jobset/v1alpha2/openapi_generated.go b/api/jobset/v1alpha2/openapi_generated.go index 3c2c11bce..47dcc8f5c 100644 --- a/api/jobset/v1alpha2/openapi_generated.go +++ b/api/jobset/v1alpha2/openapi_generated.go @@ -214,6 +214,13 @@ func schema_jobset_api_jobset_v1alpha2_JobSetSpec(ref common.ReferenceCallback) Format: "", }, }, + "managedBy": { + SchemaProps: spec.SchemaProps{ + Description: "ManagedBy is used to indicate the controller or entity that manages a JobSet", + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, diff --git a/api/jobset/v1alpha2/zz_generated.deepcopy.go b/api/jobset/v1alpha2/zz_generated.deepcopy.go index 112890b3b..a8ce765f7 100644 --- a/api/jobset/v1alpha2/zz_generated.deepcopy.go +++ b/api/jobset/v1alpha2/zz_generated.deepcopy.go @@ -131,6 +131,11 @@ func (in *JobSetSpec) DeepCopyInto(out *JobSetSpec) { *out = new(bool) **out = **in } + if in.ManagedBy != nil { + in, out := &in.ManagedBy, &out.ManagedBy + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobSetSpec. diff --git a/client-go/applyconfiguration/jobset/v1alpha2/jobsetspec.go b/client-go/applyconfiguration/jobset/v1alpha2/jobsetspec.go index 670796529..1819e4ac0 100644 --- a/client-go/applyconfiguration/jobset/v1alpha2/jobsetspec.go +++ b/client-go/applyconfiguration/jobset/v1alpha2/jobsetspec.go @@ -23,6 +23,7 @@ type JobSetSpecApplyConfiguration struct { FailurePolicy *FailurePolicyApplyConfiguration `json:"failurePolicy,omitempty"` StartupPolicy *StartupPolicyApplyConfiguration `json:"startupPolicy,omitempty"` Suspend *bool `json:"suspend,omitempty"` + ManagedBy *string `json:"managedBy,omitempty"` } // JobSetSpecApplyConfiguration constructs an declarative configuration of the JobSetSpec type for use with @@ -83,3 +84,11 @@ func (b *JobSetSpecApplyConfiguration) WithSuspend(value bool) *JobSetSpecApplyC b.Suspend = &value return b } + +// WithManagedBy sets the ManagedBy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ManagedBy field is set to the value of the last call. +func (b *JobSetSpecApplyConfiguration) WithManagedBy(value string) *JobSetSpecApplyConfiguration { + b.ManagedBy = &value + return b +} diff --git a/config/components/crd/bases/jobset.x-k8s.io_jobsets.yaml b/config/components/crd/bases/jobset.x-k8s.io_jobsets.yaml index 33f532f67..9479ebf08 100644 --- a/config/components/crd/bases/jobset.x-k8s.io_jobsets.yaml +++ b/config/components/crd/bases/jobset.x-k8s.io_jobsets.yaml @@ -72,6 +72,10 @@ spec: x-kubernetes-validations: - message: Value is immutable rule: self == oldSelf + managedBy: + description: ManagedBy is used to indicate the controller or entity + that manages a JobSet + type: string network: description: Network defines the networking options for the jobset. properties: diff --git a/hack/python-sdk/swagger.json b/hack/python-sdk/swagger.json index a24816955..b7f7c8d24 100644 --- a/hack/python-sdk/swagger.json +++ b/hack/python-sdk/swagger.json @@ -79,6 +79,10 @@ "description": "FailurePolicy, if set, configures when to declare the JobSet as failed. The JobSet is always declared failed if any job in the set finished with status failed.", "$ref": "#/definitions/jobset.v1alpha2.FailurePolicy" }, + "managedBy": { + "description": "ManagedBy is used to indicate the controller or entity that manages a JobSet", + "type": "string" + }, "network": { "description": "Network defines the networking options for the jobset.", "$ref": "#/definitions/jobset.v1alpha2.Network" diff --git a/pkg/controllers/jobset_controller.go b/pkg/controllers/jobset_controller.go index 3d0a9d417..a87621c1d 100644 --- a/pkg/controllers/jobset_controller.go +++ b/pkg/controllers/jobset_controller.go @@ -86,7 +86,10 @@ func (r *JobSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr log := ctrl.LoggerFrom(ctx).WithValues("jobset", klog.KObj(&js)) ctx = ctrl.LoggerInto(ctx, log) - if manager, found := js.Labels[jobset.LabelManagedBy]; found && manager != jobset.JobSetManager { + // Check the controller configured for the JobSet. + // See https://github.com/kubernetes-sigs/kueue/tree/559faa1aece36d3e3e09001673278396ec28b0cb/keps/693-multikueue + // for why a JobSet would not be controlled by the default JobSet controller. + if manager := managedByExternalController(js); manager != nil { log.V(5).Info("Skipping JobSet managed by a different controller", "managed-by", manager) return ctrl.Result{}, nil } @@ -936,3 +939,10 @@ func findJobFailureTime(job *batchv1.Job) *metav1.Time { } return nil } + +func managedByExternalController(js jobset.JobSet) *string { + if controllerName := js.Spec.ManagedBy; controllerName != nil && *controllerName != jobset.JobSetControllerName { + return controllerName + } + return nil +} diff --git a/pkg/util/testing/wrappers.go b/pkg/util/testing/wrappers.go index 0946ef901..9e76cd01d 100644 --- a/pkg/util/testing/wrappers.go +++ b/pkg/util/testing/wrappers.go @@ -54,6 +54,12 @@ func MakeJobSet(name, ns string) *JobSetWrapper { } } +// ManagedBy sets the value of jobSet.spec.managedBy +func (j *JobSetWrapper) ManagedBy(managedBy string) *JobSetWrapper { + j.Spec.ManagedBy = ptr.To(managedBy) + return j +} + // SuccessPolicy sets the value of jobSet.spec.successPolicy func (j *JobSetWrapper) SuccessPolicy(policy *jobset.SuccessPolicy) *JobSetWrapper { j.Spec.SuccessPolicy = policy diff --git a/sdk/python/docs/JobsetV1alpha2JobSetSpec.md b/sdk/python/docs/JobsetV1alpha2JobSetSpec.md index 01102c616..bddb6040a 100644 --- a/sdk/python/docs/JobsetV1alpha2JobSetSpec.md +++ b/sdk/python/docs/JobsetV1alpha2JobSetSpec.md @@ -5,6 +5,7 @@ JobSetSpec defines the desired state of JobSet Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **failure_policy** | [**JobsetV1alpha2FailurePolicy**](JobsetV1alpha2FailurePolicy.md) | | [optional] +**managed_by** | **str** | ManagedBy is used to indicate the controller or entity that manages a JobSet | [optional] **network** | [**JobsetV1alpha2Network**](JobsetV1alpha2Network.md) | | [optional] **replicated_jobs** | [**list[JobsetV1alpha2ReplicatedJob]**](JobsetV1alpha2ReplicatedJob.md) | ReplicatedJobs is the group of jobs that will form the set. | [optional] **startup_policy** | [**JobsetV1alpha2StartupPolicy**](JobsetV1alpha2StartupPolicy.md) | | [optional] diff --git a/sdk/python/jobset/models/jobset_v1alpha2_job_set_spec.py b/sdk/python/jobset/models/jobset_v1alpha2_job_set_spec.py index 7f4ca82e4..2a2f8b6f4 100644 --- a/sdk/python/jobset/models/jobset_v1alpha2_job_set_spec.py +++ b/sdk/python/jobset/models/jobset_v1alpha2_job_set_spec.py @@ -34,6 +34,7 @@ class JobsetV1alpha2JobSetSpec(object): """ openapi_types = { 'failure_policy': 'JobsetV1alpha2FailurePolicy', + 'managed_by': 'str', 'network': 'JobsetV1alpha2Network', 'replicated_jobs': 'list[JobsetV1alpha2ReplicatedJob]', 'startup_policy': 'JobsetV1alpha2StartupPolicy', @@ -43,6 +44,7 @@ class JobsetV1alpha2JobSetSpec(object): attribute_map = { 'failure_policy': 'failurePolicy', + 'managed_by': 'managedBy', 'network': 'network', 'replicated_jobs': 'replicatedJobs', 'startup_policy': 'startupPolicy', @@ -50,13 +52,14 @@ class JobsetV1alpha2JobSetSpec(object): 'suspend': 'suspend' } - def __init__(self, failure_policy=None, network=None, replicated_jobs=None, startup_policy=None, success_policy=None, suspend=None, local_vars_configuration=None): # noqa: E501 + def __init__(self, failure_policy=None, managed_by=None, network=None, replicated_jobs=None, startup_policy=None, success_policy=None, suspend=None, local_vars_configuration=None): # noqa: E501 """JobsetV1alpha2JobSetSpec - a model defined in OpenAPI""" # noqa: E501 if local_vars_configuration is None: local_vars_configuration = Configuration() self.local_vars_configuration = local_vars_configuration self._failure_policy = None + self._managed_by = None self._network = None self._replicated_jobs = None self._startup_policy = None @@ -66,6 +69,8 @@ def __init__(self, failure_policy=None, network=None, replicated_jobs=None, star if failure_policy is not None: self.failure_policy = failure_policy + if managed_by is not None: + self.managed_by = managed_by if network is not None: self.network = network if replicated_jobs is not None: @@ -98,6 +103,29 @@ def failure_policy(self, failure_policy): self._failure_policy = failure_policy + @property + def managed_by(self): + """Gets the managed_by of this JobsetV1alpha2JobSetSpec. # noqa: E501 + + ManagedBy is used to indicate the controller or entity that manages a JobSet # noqa: E501 + + :return: The managed_by of this JobsetV1alpha2JobSetSpec. # noqa: E501 + :rtype: str + """ + return self._managed_by + + @managed_by.setter + def managed_by(self, managed_by): + """Sets the managed_by of this JobsetV1alpha2JobSetSpec. + + ManagedBy is used to indicate the controller or entity that manages a JobSet # noqa: E501 + + :param managed_by: The managed_by of this JobsetV1alpha2JobSetSpec. # noqa: E501 + :type: str + """ + + self._managed_by = managed_by + @property def network(self): """Gets the network of this JobsetV1alpha2JobSetSpec. # noqa: E501 diff --git a/sdk/python/test/test_jobset_v1alpha2_job_set.py b/sdk/python/test/test_jobset_v1alpha2_job_set.py index 0719e3a6a..8e785fa22 100644 --- a/sdk/python/test/test_jobset_v1alpha2_job_set.py +++ b/sdk/python/test/test_jobset_v1alpha2_job_set.py @@ -44,6 +44,7 @@ def make_instance(self, include_optional): spec = jobset.models.jobset_v1alpha2_job_set_spec.JobsetV1alpha2JobSetSpec( failure_policy = jobset.models.jobset_v1alpha2_failure_policy.JobsetV1alpha2FailurePolicy( max_restarts = 56, ), + managed_by = '0', network = jobset.models.jobset_v1alpha2_network.JobsetV1alpha2Network( enable_dns_hostnames = True, subdomain = '0', ), diff --git a/sdk/python/test/test_jobset_v1alpha2_job_set_list.py b/sdk/python/test/test_jobset_v1alpha2_job_set_list.py index 5d4681583..cf7174f6f 100644 --- a/sdk/python/test/test_jobset_v1alpha2_job_set_list.py +++ b/sdk/python/test/test_jobset_v1alpha2_job_set_list.py @@ -47,6 +47,7 @@ def make_instance(self, include_optional): spec = jobset.models.jobset_v1alpha2_job_set_spec.JobsetV1alpha2JobSetSpec( failure_policy = jobset.models.jobset_v1alpha2_failure_policy.JobsetV1alpha2FailurePolicy( max_restarts = 56, ), + managed_by = '0', network = jobset.models.jobset_v1alpha2_network.JobsetV1alpha2Network( enable_dns_hostnames = True, subdomain = '0', ), @@ -92,6 +93,7 @@ def make_instance(self, include_optional): spec = jobset.models.jobset_v1alpha2_job_set_spec.JobsetV1alpha2JobSetSpec( failure_policy = jobset.models.jobset_v1alpha2_failure_policy.JobsetV1alpha2FailurePolicy( max_restarts = 56, ), + managed_by = '0', network = jobset.models.jobset_v1alpha2_network.JobsetV1alpha2Network( enable_dns_hostnames = True, subdomain = '0', ), diff --git a/sdk/python/test/test_jobset_v1alpha2_job_set_spec.py b/sdk/python/test/test_jobset_v1alpha2_job_set_spec.py index 7fc988784..255e01d8d 100644 --- a/sdk/python/test/test_jobset_v1alpha2_job_set_spec.py +++ b/sdk/python/test/test_jobset_v1alpha2_job_set_spec.py @@ -40,6 +40,7 @@ def make_instance(self, include_optional): return JobsetV1alpha2JobSetSpec( failure_policy = jobset.models.jobset_v1alpha2_failure_policy.JobsetV1alpha2FailurePolicy( max_restarts = 56, ), + managed_by = '0', network = jobset.models.jobset_v1alpha2_network.JobsetV1alpha2Network( enable_dns_hostnames = True, subdomain = '0', ), diff --git a/test/integration/controller/jobset_controller_test.go b/test/integration/controller/jobset_controller_test.go index 0849a22ce..1d332ccf7 100644 --- a/test/integration/controller/jobset_controller_test.go +++ b/test/integration/controller/jobset_controller_test.go @@ -198,6 +198,9 @@ var _ = ginkgo.Describe("JobSet controller", func() { } } }, + ginkgo.Entry("jobset should successfully create jobs", &testCase{ + makeJobSet: testJobSet, + }), ginkgo.Entry("jobset should succeed after all jobs succeed", &testCase{ makeJobSet: testJobSet, updates: []*update{ @@ -1144,9 +1147,7 @@ var _ = ginkgo.Describe("JobSet controller", func() { } gomega.Expect(k8sClient.Create(ctx, ns)).To(gomega.Succeed()) - js = testJobSet(ns).SetGenerateName("name-prefix").SetLabels(map[string]string{ - jobset.LabelManagedBy: "other-controller", - }).Obj() + js = testJobSet(ns).SetGenerateName("name-prefix").ManagedBy("other-controller").Obj() ginkgo.By(fmt.Sprintf("creating jobSet %s/%s", js.Name, js.Namespace)) gomega.Eventually(func() error { diff --git a/test/integration/webhook/jobset_webhook_test.go b/test/integration/webhook/jobset_webhook_test.go index b5fd98db5..f61de4d3f 100644 --- a/test/integration/webhook/jobset_webhook_test.go +++ b/test/integration/webhook/jobset_webhook_test.go @@ -347,10 +347,10 @@ var _ = ginkgo.Describe("jobset webhook defaulting", func() { Obj()) }, defaultsApplied: func(js *jobset.JobSet) bool { - return js.Labels[jobset.LabelManagedBy] == jobset.JobSetManager + return ptr.Deref(js.Spec.ManagedBy, "") == jobset.JobSetControllerName }, updateJobSet: func(js *jobset.JobSet) { - js.Labels[jobset.LabelManagedBy] = "new-manager" + js.Spec.ManagedBy = ptr.To("new-manager") }, updateShouldFail: true, }),