diff --git a/cmd/libs/go2idl/client-gen/main.go b/cmd/libs/go2idl/client-gen/main.go index fc6244ad0d..79d8e10190 100644 --- a/cmd/libs/go2idl/client-gen/main.go +++ b/cmd/libs/go2idl/client-gen/main.go @@ -32,7 +32,7 @@ import ( var ( test = flag.BoolP("test", "t", false, "set this flag to generate the client code for the testdata") - inputVersions = flag.StringSlice("input", []string{"api/", "extensions/"}, "group/versions that client-gen will generate clients for. At most one version per group is allowed. Specified in the format \"group1/version1,group2/version2...\". Default to \"api/,extensions\"") + inputVersions = flag.StringSlice("input", []string{"api/", "extensions/", "batch/"}, "group/versions that client-gen will generate clients for. At most one version per group is allowed. Specified in the format \"group1/version1,group2/version2...\". Default to \"api/,extensions/,batch/\"") clientsetName = flag.StringP("clientset-name", "n", "internalclientset", "the name of the generated clientset package.") clientsetPath = flag.String("clientset-path", "k8s.io/kubernetes/pkg/client/clientset_generated/", "the generated clientset will be output to /. Default to \"k8s.io/kubernetes/pkg/client/clientset_generated/\"") clientsetOnly = flag.Bool("clientset-only", false, "when set, client-gen only generates the clientset shell, without generating the individual typed clients") diff --git a/cmd/libs/go2idl/conversion-gen/generators/conversion.go b/cmd/libs/go2idl/conversion-gen/generators/conversion.go index 139e3cbc66..094ddaec5b 100644 --- a/cmd/libs/go2idl/conversion-gen/generators/conversion.go +++ b/cmd/libs/go2idl/conversion-gen/generators/conversion.go @@ -60,6 +60,7 @@ func DefaultNameSystem() string { var fallbackPackages = []string{ "k8s.io/kubernetes/pkg/api/unversioned", "k8s.io/kubernetes/pkg/apis/extensions", + "k8s.io/kubernetes/pkg/apis/batch", } func getInternalTypeFor(context *generator.Context, t *types.Type) (*types.Type, bool) { diff --git a/examples/examples_test.go b/examples/examples_test.go index 342e2f6993..5dc489b14f 100644 --- a/examples/examples_test.go +++ b/examples/examples_test.go @@ -29,6 +29,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" expvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation" "k8s.io/kubernetes/pkg/capabilities" @@ -109,7 +110,7 @@ func validateObject(obj runtime.Object) (errors field.ErrorList) { t.Namespace = api.NamespaceDefault } errors = expvalidation.ValidateDeployment(t) - case *extensions.Job: + case *batch.Job: if t.Namespace == "" { t.Namespace = api.NamespaceDefault } @@ -237,7 +238,7 @@ func TestExampleObjectSchemas(t *testing.T) { "../docs/user-guide": { "multi-pod": nil, "pod": &api.Pod{}, - "job": &extensions.Job{}, + "job": &batch.Job{}, "ingress": &extensions.Ingress{}, "nginx-deployment": &extensions.Deployment{}, "new-nginx-deployment": &extensions.Deployment{}, @@ -405,12 +406,12 @@ func TestExampleObjectSchemas(t *testing.T) { "javaweb-2": &api.Pod{}, }, "../examples/job/work-queue-1": { - "job": &extensions.Job{}, + "job": &batch.Job{}, }, "../examples/job/work-queue-2": { "redis-pod": &api.Pod{}, "redis-service": &api.Service{}, - "job": &extensions.Job{}, + "job": &batch.Job{}, }, "../examples/azure_file": { "azure": &api.Pod{}, diff --git a/pkg/api/testing/fuzzer.go b/pkg/api/testing/fuzzer.go index 72c3025c44..202d503a04 100644 --- a/pkg/api/testing/fuzzer.go +++ b/pkg/api/testing/fuzzer.go @@ -28,6 +28,7 @@ import ( "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" @@ -156,7 +157,7 @@ func FuzzerFor(t *testing.T, version unversioned.GroupVersion, src rand.Source) j.RollingUpdate = &rollingUpdate } }, - func(j *extensions.JobSpec, c fuzz.Continue) { + func(j *batch.JobSpec, c fuzz.Continue) { c.FuzzNoCustom(j) // fuzz self without calling this function again completions := int(c.Rand.Int31()) parallelism := int(c.Rand.Int31()) diff --git a/pkg/apis/batch/register.go b/pkg/apis/batch/register.go index a302fe7514..517d65e657 100644 --- a/pkg/apis/batch/register.go +++ b/pkg/apis/batch/register.go @@ -19,7 +19,6 @@ package batch import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" - "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/runtime" ) @@ -47,8 +46,11 @@ func AddToScheme(scheme *runtime.Scheme) { // Adds the list of known types to api.Scheme. func addKnownTypes(scheme *runtime.Scheme) { scheme.AddKnownTypes(SchemeGroupVersion, - &extensions.Job{}, - &extensions.JobList{}, + &Job{}, + &JobList{}, &api.ListOptions{}, ) } + +func (obj *Job) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } +func (obj *JobList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } diff --git a/pkg/apis/batch/types.go b/pkg/apis/batch/types.go new file mode 100644 index 0000000000..eca6bb70a2 --- /dev/null +++ b/pkg/apis/batch/types.go @@ -0,0 +1,143 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package batch + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" +) + +// +genclient=true + +// Job represents the configuration of a single job. +type Job struct { + unversioned.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata + api.ObjectMeta `json:"metadata,omitempty"` + + // Spec is a structure defining the expected behavior of a job. + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status + Spec JobSpec `json:"spec,omitempty"` + + // Status is a structure describing current status of a job. + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status + Status JobStatus `json:"status,omitempty"` +} + +// JobList is a collection of jobs. +type JobList struct { + unversioned.TypeMeta `json:",inline"` + // Standard list metadata + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata + unversioned.ListMeta `json:"metadata,omitempty"` + + // Items is the list of Job. + Items []Job `json:"items"` +} + +// JobSpec describes how the job execution will look like. +type JobSpec struct { + + // Parallelism specifies the maximum desired number of pods the job should + // run at any given time. The actual number of pods running in steady state will + // be less than this number when ((.spec.completions - .status.successful) < .spec.parallelism), + // i.e. when the work left to do is less than max parallelism. + Parallelism *int `json:"parallelism,omitempty"` + + // Completions specifies the desired number of successfully finished pods the + // job should be run with. Setting to nil means that the success of any + // pod signals the success of all pods, and allows parallelism to have any positive + // value. Setting to 1 means that parallelism is limited to 1 and the success of that + // pod signals the success of the job. + Completions *int `json:"completions,omitempty"` + + // Optional duration in seconds relative to the startTime that the job may be active + // before the system tries to terminate it; value must be positive integer + ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty"` + + // Selector is a label query over pods that should match the pod count. + // Normally, the system sets this field for you. + Selector *unversioned.LabelSelector `json:"selector,omitempty"` + + // ManualSelector controls generation of pod labels and pod selectors. + // Leave `manualSelector` unset unless you are certain what you are doing. + // When false or unset, the system pick labels unique to this job + // and appends those labels to the pod template. When true, + // the user is responsible for picking unique labels and specifying + // the selector. Failure to pick a unique label may cause this + // and other jobs to not function correctly. However, You may see + // `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` + // API. + ManualSelector *bool `json:"manualSelector,omitempty"` + + // Template is the object that describes the pod that will be created when + // executing a job. + Template api.PodTemplateSpec `json:"template"` +} + +// JobStatus represents the current state of a Job. +type JobStatus struct { + + // Conditions represent the latest available observations of an object's current state. + Conditions []JobCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + + // StartTime represents time when the job was acknowledged by the Job Manager. + // It is not guaranteed to be set in happens-before order across separate operations. + // It is represented in RFC3339 form and is in UTC. + StartTime *unversioned.Time `json:"startTime,omitempty"` + + // CompletionTime represents time when the job was completed. It is not guaranteed to + // be set in happens-before order across separate operations. + // It is represented in RFC3339 form and is in UTC. + CompletionTime *unversioned.Time `json:"completionTime,omitempty"` + + // Active is the number of actively running pods. + Active int `json:"active,omitempty"` + + // Succeeded is the number of pods which reached Phase Succeeded. + Succeeded int `json:"succeeded,omitempty"` + + // Failed is the number of pods which reached Phase Failed. + Failed int `json:"failed,omitempty"` +} + +type JobConditionType string + +// These are valid conditions of a job. +const ( + // JobComplete means the job has completed its execution. + JobComplete JobConditionType = "Complete" + // JobFailed means the job has failed its execution. + JobFailed JobConditionType = "Failed" +) + +// JobCondition describes current state of a job. +type JobCondition struct { + // Type of job condition, Complete or Failed. + Type JobConditionType `json:"type"` + // Status of the condition, one of True, False, Unknown. + Status api.ConditionStatus `json:"status"` + // Last time the condition was checked. + LastProbeTime unversioned.Time `json:"lastProbeTime,omitempty"` + // Last time the condition transit from one status to another. + LastTransitionTime unversioned.Time `json:"lastTransitionTime,omitempty"` + // (brief) reason for the condition's last transition. + Reason string `json:"reason,omitempty"` + // Human readable message indicating details about last transition. + Message string `json:"message,omitempty"` +} diff --git a/pkg/apis/batch/v1/conversion.go b/pkg/apis/batch/v1/conversion.go index aeb705ec2f..ca6e0bcf5e 100644 --- a/pkg/apis/batch/v1/conversion.go +++ b/pkg/apis/batch/v1/conversion.go @@ -18,14 +18,22 @@ package v1 import ( "fmt" + "reflect" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + v1 "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/apis/batch" + "k8s.io/kubernetes/pkg/conversion" "k8s.io/kubernetes/pkg/runtime" ) func addConversionFuncs(scheme *runtime.Scheme) { // Add non-generated conversion functions - err := scheme.AddConversionFuncs() + err := scheme.AddConversionFuncs( + Convert_batch_JobSpec_To_v1_JobSpec, + Convert_v1_JobSpec_To_batch_JobSpec, + ) if err != nil { // If one of the conversion functions is malformed, detect it immediately. panic(err) @@ -45,3 +53,91 @@ func addConversionFuncs(scheme *runtime.Scheme) { panic(err) } } + +func Convert_batch_JobSpec_To_v1_JobSpec(in *batch.JobSpec, out *JobSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*batch.JobSpec))(in) + } + if in.Parallelism != nil { + out.Parallelism = new(int32) + *out.Parallelism = int32(*in.Parallelism) + } else { + out.Parallelism = nil + } + if in.Completions != nil { + out.Completions = new(int32) + *out.Completions = int32(*in.Completions) + } else { + out.Completions = nil + } + if in.ActiveDeadlineSeconds != nil { + out.ActiveDeadlineSeconds = new(int64) + *out.ActiveDeadlineSeconds = *in.ActiveDeadlineSeconds + } else { + out.ActiveDeadlineSeconds = nil + } + // unable to generate simple pointer conversion for unversioned.LabelSelector -> v1.LabelSelector + if in.Selector != nil { + out.Selector = new(LabelSelector) + if err := Convert_unversioned_LabelSelector_To_v1_LabelSelector(in.Selector, out.Selector, s); err != nil { + return err + } + } else { + out.Selector = nil + } + if in.ManualSelector != nil { + out.ManualSelector = new(bool) + *out.ManualSelector = *in.ManualSelector + } else { + out.ManualSelector = nil + } + + if err := v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + return nil +} + +func Convert_v1_JobSpec_To_batch_JobSpec(in *JobSpec, out *batch.JobSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*JobSpec))(in) + } + if in.Parallelism != nil { + out.Parallelism = new(int) + *out.Parallelism = int(*in.Parallelism) + } else { + out.Parallelism = nil + } + if in.Completions != nil { + out.Completions = new(int) + *out.Completions = int(*in.Completions) + } else { + out.Completions = nil + } + if in.ActiveDeadlineSeconds != nil { + out.ActiveDeadlineSeconds = new(int64) + *out.ActiveDeadlineSeconds = *in.ActiveDeadlineSeconds + } else { + out.ActiveDeadlineSeconds = nil + } + // unable to generate simple pointer conversion for v1.LabelSelector -> unversioned.LabelSelector + if in.Selector != nil { + out.Selector = new(unversioned.LabelSelector) + if err := Convert_v1_LabelSelector_To_unversioned_LabelSelector(in.Selector, out.Selector, s); err != nil { + return err + } + } else { + out.Selector = nil + } + if in.ManualSelector != nil { + out.ManualSelector = new(bool) + *out.ManualSelector = *in.ManualSelector + } else { + out.ManualSelector = nil + } + + if err := v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + return nil +} diff --git a/pkg/apis/batch/validation/validation.go b/pkg/apis/batch/validation/validation.go new file mode 100644 index 0000000000..d4d1875783 --- /dev/null +++ b/pkg/apis/batch/validation/validation.go @@ -0,0 +1,157 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validation + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + unversionedvalidation "k8s.io/kubernetes/pkg/api/unversioned/validation" + apivalidation "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/apis/batch" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// TODO: generalize for other controller objects that will follow the same pattern, such as ReplicaSet and DaemonSet, and +// move to new location. Replace batch.Job with an interface. +// +// ValidateGeneratedSelector validates that the generated selector on a controller object match the controller object +// metadata, and the labels on the pod template are as generated. +func ValidateGeneratedSelector(obj *batch.Job) field.ErrorList { + allErrs := field.ErrorList{} + if obj.Spec.ManualSelector != nil && *obj.Spec.ManualSelector { + return allErrs + } + + if obj.Spec.Selector == nil { + return allErrs // This case should already have been checked in caller. No need for more errors. + } + + // If somehow uid was unset then we would get "controller-uid=" as the selector + // which is bad. + if obj.ObjectMeta.UID == "" { + allErrs = append(allErrs, field.Required(field.NewPath("metadata").Child("uid"), "")) + } + + // If somehow uid was unset then we would get "controller-uid=" as the selector + // which is bad. + if obj.ObjectMeta.UID == "" { + allErrs = append(allErrs, field.Required(field.NewPath("metadata").Child("uid"), "")) + } + + // If selector generation was requested, then expected labels must be + // present on pod template, and much match job's uid and name. The + // generated (not-manual) selectors/labels ensure no overlap with other + // controllers. The manual mode allows orphaning, adoption, + // backward-compatibility, and experimentation with new + // labeling/selection schemes. Automatic selector generation should + // have placed certain labels on the pod, but this could have failed if + // the user added coflicting labels. Validate that the expected + // generated ones are there. + + allErrs = append(allErrs, apivalidation.ValidateHasLabel(obj.Spec.Template.ObjectMeta, field.NewPath("spec").Child("template").Child("metadata"), "controller-uid", string(obj.UID))...) + allErrs = append(allErrs, apivalidation.ValidateHasLabel(obj.Spec.Template.ObjectMeta, field.NewPath("spec").Child("template").Child("metadata"), "job-name", string(obj.Name))...) + expectedLabels := make(map[string]string) + expectedLabels["controller-uid"] = string(obj.UID) + expectedLabels["job-name"] = string(obj.Name) + // Whether manually or automatically generated, the selector of the job must match the pods it will produce. + if selector, err := unversioned.LabelSelectorAsSelector(obj.Spec.Selector); err == nil { + if !selector.Matches(labels.Set(expectedLabels)) { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("selector"), obj.Spec.Selector, "`selector` not auto-generated")) + } + } + + return allErrs +} + +func ValidateJob(job *batch.Job) field.ErrorList { + // Jobs and rcs have the same name validation + allErrs := apivalidation.ValidateObjectMeta(&job.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateGeneratedSelector(job)...) + allErrs = append(allErrs, ValidateJobSpec(&job.Spec, field.NewPath("spec"))...) + return allErrs +} + +func ValidateJobSpec(spec *batch.JobSpec, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if spec.Parallelism != nil { + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.Parallelism), fldPath.Child("parallelism"))...) + } + if spec.Completions != nil { + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.Completions), fldPath.Child("completions"))...) + } + if spec.ActiveDeadlineSeconds != nil { + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.ActiveDeadlineSeconds), fldPath.Child("activeDeadlineSeconds"))...) + } + if spec.Selector == nil { + allErrs = append(allErrs, field.Required(fldPath.Child("selector"), "")) + } else { + allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...) + } + + // Whether manually or automatically generated, the selector of the job must match the pods it will produce. + if selector, err := unversioned.LabelSelectorAsSelector(spec.Selector); err == nil { + labels := labels.Set(spec.Template.Labels) + if !selector.Matches(labels) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("template", "metadata", "labels"), spec.Template.Labels, "`selector` does not match template `labels`")) + } + } + + allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template, fldPath.Child("template"))...) + if spec.Template.Spec.RestartPolicy != api.RestartPolicyOnFailure && + spec.Template.Spec.RestartPolicy != api.RestartPolicyNever { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"), + spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyOnFailure), string(api.RestartPolicyNever)})) + } + return allErrs +} + +func ValidateJobStatus(status *batch.JobStatus, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Active), fldPath.Child("active"))...) + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Succeeded), fldPath.Child("succeeded"))...) + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Failed), fldPath.Child("failed"))...) + return allErrs +} + +func ValidateJobUpdate(job, oldJob *batch.Job) field.ErrorList { + allErrs := apivalidation.ValidateObjectMetaUpdate(&oldJob.ObjectMeta, &job.ObjectMeta, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateJobSpecUpdate(job.Spec, oldJob.Spec, field.NewPath("spec"))...) + return allErrs +} + +func ValidateJobUpdateStatus(job, oldJob *batch.Job) field.ErrorList { + allErrs := apivalidation.ValidateObjectMetaUpdate(&oldJob.ObjectMeta, &job.ObjectMeta, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateJobStatusUpdate(job.Status, oldJob.Status)...) + return allErrs +} + +func ValidateJobSpecUpdate(spec, oldSpec batch.JobSpec, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, ValidateJobSpec(&spec, fldPath)...) + allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Completions, oldSpec.Completions, fldPath.Child("completions"))...) + allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Selector, oldSpec.Selector, fldPath.Child("selector"))...) + allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Template, oldSpec.Template, fldPath.Child("template"))...) + return allErrs +} + +func ValidateJobStatusUpdate(status, oldStatus batch.JobStatus) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, ValidateJobStatus(&status, field.NewPath("status"))...) + return allErrs +} diff --git a/pkg/apis/batch/validation/validation_test.go b/pkg/apis/batch/validation/validation_test.go new file mode 100644 index 0000000000..f7a6392211 --- /dev/null +++ b/pkg/apis/batch/validation/validation_test.go @@ -0,0 +1,297 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validation + +import ( + "strings" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/batch" + "k8s.io/kubernetes/pkg/types" +) + +func TestValidateJob(t *testing.T) { + validManualSelector := &unversioned.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + } + validGeneratedSelector := &unversioned.LabelSelector{ + MatchLabels: map[string]string{"controller-uid": "1a2b3c", "job-name": "myjob"}, + } + validPodTemplateSpecForManual := api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: validManualSelector.MatchLabels, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyOnFailure, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + } + validPodTemplateSpecForGenerated := api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: validGeneratedSelector.MatchLabels, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyOnFailure, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + } + successCases := map[string]batch.Job{ + "manual selector": { + ObjectMeta: api.ObjectMeta{ + Name: "myjob", + Namespace: api.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.JobSpec{ + Selector: validManualSelector, + ManualSelector: newBool(true), + Template: validPodTemplateSpecForManual, + }, + }, + "generated selector": { + ObjectMeta: api.ObjectMeta{ + Name: "myjob", + Namespace: api.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.JobSpec{ + Selector: validGeneratedSelector, + ManualSelector: newBool(false), + Template: validPodTemplateSpecForGenerated, + }, + }, + } + for k, v := range successCases { + if errs := ValidateJob(&v); len(errs) != 0 { + t.Errorf("expected success for %s: %v", k, errs) + } + } + negative := -1 + negative64 := int64(-1) + errorCases := map[string]batch.Job{ + "spec.parallelism:must be greater than or equal to 0": { + ObjectMeta: api.ObjectMeta{ + Name: "myjob", + Namespace: api.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.JobSpec{ + Parallelism: &negative, + ManualSelector: newBool(true), + Template: validPodTemplateSpecForGenerated, + }, + }, + "spec.completions:must be greater than or equal to 0": { + ObjectMeta: api.ObjectMeta{ + Name: "myjob", + Namespace: api.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.JobSpec{ + Completions: &negative, + Selector: validManualSelector, + ManualSelector: newBool(true), + Template: validPodTemplateSpecForGenerated, + }, + }, + "spec.activeDeadlineSeconds:must be greater than or equal to 0": { + ObjectMeta: api.ObjectMeta{ + Name: "myjob", + Namespace: api.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.JobSpec{ + ActiveDeadlineSeconds: &negative64, + Selector: validManualSelector, + ManualSelector: newBool(true), + Template: validPodTemplateSpecForGenerated, + }, + }, + "spec.selector:Required value": { + ObjectMeta: api.ObjectMeta{ + Name: "myjob", + Namespace: api.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.JobSpec{ + Template: validPodTemplateSpecForGenerated, + }, + }, + "spec.template.metadata.labels: Invalid value: {\"y\":\"z\"}: `selector` does not match template `labels`": { + ObjectMeta: api.ObjectMeta{ + Name: "myjob", + Namespace: api.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.JobSpec{ + Selector: validManualSelector, + ManualSelector: newBool(true), + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"y": "z"}, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyOnFailure, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + }, + }, + }, + "spec.template.metadata.labels: Invalid value: {\"controller-uid\":\"4d5e6f\"}: `selector` does not match template `labels`": { + ObjectMeta: api.ObjectMeta{ + Name: "myjob", + Namespace: api.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.JobSpec{ + Selector: validManualSelector, + ManualSelector: newBool(true), + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"controller-uid": "4d5e6f"}, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyOnFailure, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + }, + }, + }, + "spec.template.spec.restartPolicy: Unsupported value": { + ObjectMeta: api.ObjectMeta{ + Name: "myjob", + Namespace: api.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.JobSpec{ + Selector: validManualSelector, + ManualSelector: newBool(true), + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: validManualSelector.MatchLabels, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + }, + }, + }, + } + + for k, v := range errorCases { + errs := ValidateJob(&v) + if len(errs) == 0 { + t.Errorf("expected failure for %s", k) + } else { + s := strings.Split(k, ":") + err := errs[0] + if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) { + t.Errorf("unexpected error: %v, expected: %s", err, k) + } + } + } +} + +func TestValidateJobUpdateStatus(t *testing.T) { + type testcase struct { + old batch.Job + update batch.Job + } + + successCases := []testcase{ + { + old: batch.Job{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Status: batch.JobStatus{ + Active: 1, + Succeeded: 2, + Failed: 3, + }, + }, + update: batch.Job{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Status: batch.JobStatus{ + Active: 1, + Succeeded: 1, + Failed: 3, + }, + }, + }, + } + + for _, successCase := range successCases { + successCase.old.ObjectMeta.ResourceVersion = "1" + successCase.update.ObjectMeta.ResourceVersion = "1" + if errs := ValidateJobUpdateStatus(&successCase.update, &successCase.old); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := map[string]testcase{ + "[status.active: Invalid value: -1: must be greater than or equal to 0, status.succeeded: Invalid value: -2: must be greater than or equal to 0]": { + old: batch.Job{ + ObjectMeta: api.ObjectMeta{ + Name: "abc", + Namespace: api.NamespaceDefault, + ResourceVersion: "10", + }, + Status: batch.JobStatus{ + Active: 1, + Succeeded: 2, + Failed: 3, + }, + }, + update: batch.Job{ + ObjectMeta: api.ObjectMeta{ + Name: "abc", + Namespace: api.NamespaceDefault, + ResourceVersion: "10", + }, + Status: batch.JobStatus{ + Active: -1, + Succeeded: -2, + Failed: 3, + }, + }, + }, + } + + for testName, errorCase := range errorCases { + errs := ValidateJobUpdateStatus(&errorCase.update, &errorCase.old) + if len(errs) == 0 { + t.Errorf("expected failure: %s", testName) + continue + } + if errs.ToAggregate().Error() != testName { + t.Errorf("expected '%s' got '%s'", errs.ToAggregate().Error(), testName) + } + } +} + +func newBool(val bool) *bool { + p := new(bool) + *p = val + return p +} diff --git a/pkg/apis/extensions/register.go b/pkg/apis/extensions/register.go index 057940d161..712adf91d9 100644 --- a/pkg/apis/extensions/register.go +++ b/pkg/apis/extensions/register.go @@ -19,6 +19,7 @@ package extensions import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/runtime" ) @@ -52,8 +53,8 @@ func addKnownTypes(scheme *runtime.Scheme) { &DeploymentRollback{}, &HorizontalPodAutoscaler{}, &HorizontalPodAutoscalerList{}, - &Job{}, - &JobList{}, + &batch.Job{}, + &batch.JobList{}, &ReplicationControllerDummy{}, &Scale{}, &ThirdPartyResource{}, @@ -78,8 +79,6 @@ func (obj *DeploymentList) GetObjectKind() unversioned.ObjectKind { func (obj *DeploymentRollback) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *HorizontalPodAutoscaler) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *HorizontalPodAutoscalerList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } -func (obj *Job) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } -func (obj *JobList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *ReplicationControllerDummy) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *Scale) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *ThirdPartyResource) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } diff --git a/pkg/apis/extensions/types.go b/pkg/apis/extensions/types.go index edcc939884..e98e48c165 100644 --- a/pkg/apis/extensions/types.go +++ b/pkg/apis/extensions/types.go @@ -496,127 +496,6 @@ type ThirdPartyResourceDataList struct { // +genclient=true -// Job represents the configuration of a single job. -type Job struct { - unversioned.TypeMeta `json:",inline"` - // Standard object's metadata. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata - api.ObjectMeta `json:"metadata,omitempty"` - - // Spec is a structure defining the expected behavior of a job. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status - Spec JobSpec `json:"spec,omitempty"` - - // Status is a structure describing current status of a job. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status - Status JobStatus `json:"status,omitempty"` -} - -// JobList is a collection of jobs. -type JobList struct { - unversioned.TypeMeta `json:",inline"` - // Standard list metadata - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata - unversioned.ListMeta `json:"metadata,omitempty"` - - // Items is the list of Job. - Items []Job `json:"items"` -} - -// JobSpec describes how the job execution will look like. -type JobSpec struct { - - // Parallelism specifies the maximum desired number of pods the job should - // run at any given time. The actual number of pods running in steady state will - // be less than this number when ((.spec.completions - .status.successful) < .spec.parallelism), - // i.e. when the work left to do is less than max parallelism. - Parallelism *int `json:"parallelism,omitempty"` - - // Completions specifies the desired number of successfully finished pods the - // job should be run with. Setting to nil means that the success of any - // pod signals the success of all pods, and allows parallelism to have any positive - // value. Setting to 1 means that parallelism is limited to 1 and the success of that - // pod signals the success of the job. - Completions *int `json:"completions,omitempty"` - - // Optional duration in seconds relative to the startTime that the job may be active - // before the system tries to terminate it; value must be positive integer - ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty"` - - // Selector is a label query over pods that should match the pod count. - // Normally, the system sets this field for you. - Selector *unversioned.LabelSelector `json:"selector,omitempty"` - - // ManualSelector controls generation of pod labels and pod selectors. - // Leave `manualSelector` unset unless you are certain what you are doing. - // When false or unset, the system pick labels unique to this job - // and appends those labels to the pod template. When true, - // the user is responsible for picking unique labels and specifying - // the selector. Failure to pick a unique label may cause this - // and other jobs to not function correctly. However, You may see - // `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` - // API. - ManualSelector *bool `json:"manualSelector,omitempty"` - - // Template is the object that describes the pod that will be created when - // executing a job. - Template api.PodTemplateSpec `json:"template"` -} - -// JobStatus represents the current state of a Job. -type JobStatus struct { - - // Conditions represent the latest available observations of an object's current state. - Conditions []JobCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` - - // StartTime represents time when the job was acknowledged by the Job Manager. - // It is not guaranteed to be set in happens-before order across separate operations. - // It is represented in RFC3339 form and is in UTC. - StartTime *unversioned.Time `json:"startTime,omitempty"` - - // CompletionTime represents time when the job was completed. It is not guaranteed to - // be set in happens-before order across separate operations. - // It is represented in RFC3339 form and is in UTC. - CompletionTime *unversioned.Time `json:"completionTime,omitempty"` - - // Active is the number of actively running pods. - Active int `json:"active,omitempty"` - - // Succeeded is the number of pods which reached Phase Succeeded. - Succeeded int `json:"succeeded,omitempty"` - - // Failed is the number of pods which reached Phase Failed. - Failed int `json:"failed,omitempty"` -} - -type JobConditionType string - -// These are valid conditions of a job. -const ( - // JobComplete means the job has completed its execution. - JobComplete JobConditionType = "Complete" - // JobFailed means the job has failed its execution. - JobFailed JobConditionType = "Failed" -) - -// JobCondition describes current state of a job. -type JobCondition struct { - // Type of job condition, Complete or Failed. - Type JobConditionType `json:"type"` - // Status of the condition, one of True, False, Unknown. - Status api.ConditionStatus `json:"status"` - // Last time the condition was checked. - LastProbeTime unversioned.Time `json:"lastProbeTime,omitempty"` - // Last time the condition transit from one status to another. - LastTransitionTime unversioned.Time `json:"lastTransitionTime,omitempty"` - // (brief) reason for the condition's last transition. - Reason string `json:"reason,omitempty"` - // Human readable message indicating details about last transition. - Message string `json:"message,omitempty"` -} - -// +genclient=true - // Ingress is a collection of rules that allow inbound connections to reach the // endpoints defined by a backend. An Ingress can be configured to give services // externally-reachable urls, load balance traffic, terminate SSL, offer name diff --git a/pkg/apis/extensions/v1beta1/conversion.go b/pkg/apis/extensions/v1beta1/conversion.go index 683495d2d4..4d93b71813 100644 --- a/pkg/apis/extensions/v1beta1/conversion.go +++ b/pkg/apis/extensions/v1beta1/conversion.go @@ -23,6 +23,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" v1 "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/conversion" "k8s.io/kubernetes/pkg/runtime" @@ -42,8 +43,9 @@ func addConversionFuncs(scheme *runtime.Scheme) { Convert_v1beta1_RollingUpdateDeployment_To_extensions_RollingUpdateDeployment, Convert_extensions_ReplicaSetSpec_To_v1beta1_ReplicaSetSpec, Convert_v1beta1_ReplicaSetSpec_To_extensions_ReplicaSetSpec, - Convert_extensions_JobSpec_To_v1beta1_JobSpec, - Convert_v1beta1_JobSpec_To_extensions_JobSpec, + // batch + Convert_batch_JobSpec_To_v1beta1_JobSpec, + Convert_v1beta1_JobSpec_To_batch_JobSpec, ) if err != nil { // If one of the conversion functions is malformed, detect it immediately. @@ -312,9 +314,9 @@ func Convert_v1beta1_ReplicaSetSpec_To_extensions_ReplicaSetSpec(in *ReplicaSetS return nil } -func Convert_extensions_JobSpec_To_v1beta1_JobSpec(in *extensions.JobSpec, out *JobSpec, s conversion.Scope) error { +func Convert_batch_JobSpec_To_v1beta1_JobSpec(in *batch.JobSpec, out *JobSpec, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*extensions.JobSpec))(in) + defaulting.(func(*batch.JobSpec))(in) } if in.Parallelism != nil { out.Parallelism = new(int32) @@ -364,7 +366,7 @@ func Convert_extensions_JobSpec_To_v1beta1_JobSpec(in *extensions.JobSpec, out * return nil } -func Convert_v1beta1_JobSpec_To_extensions_JobSpec(in *JobSpec, out *extensions.JobSpec, s conversion.Scope) error { +func Convert_v1beta1_JobSpec_To_batch_JobSpec(in *JobSpec, out *batch.JobSpec, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*JobSpec))(in) } diff --git a/pkg/apis/extensions/v1beta1/conversion_test.go b/pkg/apis/extensions/v1beta1/conversion_test.go index a0e43dfbca..759e38ca10 100644 --- a/pkg/apis/extensions/v1beta1/conversion_test.go +++ b/pkg/apis/extensions/v1beta1/conversion_test.go @@ -21,7 +21,7 @@ import ( "testing" "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/batch" versioned "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" ) @@ -55,7 +55,7 @@ func TestJobSpecConversion(t *testing.T) { // Test internal -> v1beta1. for _, test := range tests { - i := &extensions.JobSpec{ + i := &batch.JobSpec{ ManualSelector: test.in, } v := versioned.JobSpec{} @@ -72,7 +72,7 @@ func TestJobSpecConversion(t *testing.T) { i := &versioned.JobSpec{ AutoSelector: test.in, } - e := extensions.JobSpec{} + e := batch.JobSpec{} if err := api.Scheme.Convert(i, &e); err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/pkg/apis/extensions/validation/validation.go b/pkg/apis/extensions/validation/validation.go index 2bed2dd902..181e13e352 100644 --- a/pkg/apis/extensions/validation/validation.go +++ b/pkg/apis/extensions/validation/validation.go @@ -382,136 +382,6 @@ func ValidateThirdPartyResourceData(obj *extensions.ThirdPartyResourceData) fiel return allErrs } -// TODO: generalize for other controller objects that will follow the same pattern, such as ReplicaSet and DaemonSet, and -// move to new location. Replace extensions.Job with an interface. -// -// ValidateGeneratedSelector validates that the generated selector on a controller object match the controller object -// metadata, and the labels on the pod template are as generated. -func ValidateGeneratedSelector(obj *extensions.Job) field.ErrorList { - allErrs := field.ErrorList{} - if obj.Spec.ManualSelector != nil && *obj.Spec.ManualSelector { - return allErrs - } - - if obj.Spec.Selector == nil { - return allErrs // This case should already have been checked in caller. No need for more errors. - } - - // If somehow uid was unset then we would get "controller-uid=" as the selector - // which is bad. - if obj.ObjectMeta.UID == "" { - allErrs = append(allErrs, field.Required(field.NewPath("metadata").Child("uid"), "")) - } - - // If somehow uid was unset then we would get "controller-uid=" as the selector - // which is bad. - if obj.ObjectMeta.UID == "" { - allErrs = append(allErrs, field.Required(field.NewPath("metadata").Child("uid"), "")) - } - - // If selector generation was requested, then expected labels must be - // present on pod template, and much match job's uid and name. The - // generated (not-manual) selectors/labels ensure no overlap with other - // controllers. The manual mode allows orphaning, adoption, - // backward-compatibility, and experimentation with new - // labeling/selection schemes. Automatic selector generation should - // have placed certain labels on the pod, but this could have failed if - // the user added coflicting labels. Validate that the expected - // generated ones are there. - - allErrs = append(allErrs, apivalidation.ValidateHasLabel(obj.Spec.Template.ObjectMeta, field.NewPath("spec").Child("template").Child("metadata"), "controller-uid", string(obj.UID))...) - allErrs = append(allErrs, apivalidation.ValidateHasLabel(obj.Spec.Template.ObjectMeta, field.NewPath("spec").Child("template").Child("metadata"), "job-name", string(obj.Name))...) - expectedLabels := make(map[string]string) - expectedLabels["controller-uid"] = string(obj.UID) - expectedLabels["job-name"] = string(obj.Name) - // Whether manually or automatically generated, the selector of the job must match the pods it will produce. - if selector, err := unversioned.LabelSelectorAsSelector(obj.Spec.Selector); err == nil { - if !selector.Matches(labels.Set(expectedLabels)) { - allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("selector"), obj.Spec.Selector, "`selector` not auto-generated")) - } - } - - return allErrs -} - -func ValidateJob(job *extensions.Job) field.ErrorList { - // Jobs and rcs have the same name validation - allErrs := apivalidation.ValidateObjectMeta(&job.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata")) - allErrs = append(allErrs, ValidateGeneratedSelector(job)...) - allErrs = append(allErrs, ValidateJobSpec(&job.Spec, field.NewPath("spec"))...) - return allErrs -} - -func ValidateJobSpec(spec *extensions.JobSpec, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if spec.Parallelism != nil { - allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.Parallelism), fldPath.Child("parallelism"))...) - } - if spec.Completions != nil { - allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.Completions), fldPath.Child("completions"))...) - } - if spec.ActiveDeadlineSeconds != nil { - allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.ActiveDeadlineSeconds), fldPath.Child("activeDeadlineSeconds"))...) - } - if spec.Selector == nil { - allErrs = append(allErrs, field.Required(fldPath.Child("selector"), "")) - } else { - allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...) - } - - // Whether manually or automatically generated, the selector of the job must match the pods it will produce. - if selector, err := unversioned.LabelSelectorAsSelector(spec.Selector); err == nil { - labels := labels.Set(spec.Template.Labels) - if !selector.Matches(labels) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("template", "metadata", "labels"), spec.Template.Labels, "`selector` does not match template `labels`")) - } - } - - allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template, fldPath.Child("template"))...) - if spec.Template.Spec.RestartPolicy != api.RestartPolicyOnFailure && - spec.Template.Spec.RestartPolicy != api.RestartPolicyNever { - allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"), - spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyOnFailure), string(api.RestartPolicyNever)})) - } - return allErrs -} - -func ValidateJobStatus(status *extensions.JobStatus, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Active), fldPath.Child("active"))...) - allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Succeeded), fldPath.Child("succeeded"))...) - allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Failed), fldPath.Child("failed"))...) - return allErrs -} - -func ValidateJobUpdate(job, oldJob *extensions.Job) field.ErrorList { - allErrs := apivalidation.ValidateObjectMetaUpdate(&oldJob.ObjectMeta, &job.ObjectMeta, field.NewPath("metadata")) - allErrs = append(allErrs, ValidateJobSpecUpdate(job.Spec, oldJob.Spec, field.NewPath("spec"))...) - return allErrs -} - -func ValidateJobUpdateStatus(job, oldJob *extensions.Job) field.ErrorList { - allErrs := apivalidation.ValidateObjectMetaUpdate(&oldJob.ObjectMeta, &job.ObjectMeta, field.NewPath("metadata")) - allErrs = append(allErrs, ValidateJobStatusUpdate(job.Status, oldJob.Status)...) - return allErrs -} - -func ValidateJobSpecUpdate(spec, oldSpec extensions.JobSpec, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - allErrs = append(allErrs, ValidateJobSpec(&spec, fldPath)...) - allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Completions, oldSpec.Completions, fldPath.Child("completions"))...) - allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Selector, oldSpec.Selector, fldPath.Child("selector"))...) - allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Template, oldSpec.Template, fldPath.Child("template"))...) - return allErrs -} - -func ValidateJobStatusUpdate(status, oldStatus extensions.JobStatus) field.ErrorList { - allErrs := field.ErrorList{} - allErrs = append(allErrs, ValidateJobStatus(&status, field.NewPath("status"))...) - return allErrs -} - // ValidateIngress tests if required fields in the Ingress are set. func ValidateIngress(ingress *extensions.Ingress) field.ErrorList { allErrs := apivalidation.ValidateObjectMeta(&ingress.ObjectMeta, true, ValidateIngressName, field.NewPath("metadata")) diff --git a/pkg/apis/extensions/validation/validation_test.go b/pkg/apis/extensions/validation/validation_test.go index 6a7db3b81d..28464bc2e8 100644 --- a/pkg/apis/extensions/validation/validation_test.go +++ b/pkg/apis/extensions/validation/validation_test.go @@ -25,7 +25,6 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/controller/podautoscaler" - "k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/util/intstr" ) @@ -989,270 +988,6 @@ func TestValidateDeploymentRollback(t *testing.T) { } } -func TestValidateJob(t *testing.T) { - validManualSelector := &unversioned.LabelSelector{ - MatchLabels: map[string]string{"a": "b"}, - } - validGeneratedSelector := &unversioned.LabelSelector{ - MatchLabels: map[string]string{"controller-uid": "1a2b3c", "job-name": "myjob"}, - } - validPodTemplateSpecForManual := api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - Labels: validManualSelector.MatchLabels, - }, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyOnFailure, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, - }, - } - validPodTemplateSpecForGenerated := api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - Labels: validGeneratedSelector.MatchLabels, - }, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyOnFailure, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, - }, - } - successCases := map[string]extensions.Job{ - "manual selector": { - ObjectMeta: api.ObjectMeta{ - Name: "myjob", - Namespace: api.NamespaceDefault, - UID: types.UID("1a2b3c"), - }, - Spec: extensions.JobSpec{ - Selector: validManualSelector, - ManualSelector: newBool(true), - Template: validPodTemplateSpecForManual, - }, - }, - "generated selector": { - ObjectMeta: api.ObjectMeta{ - Name: "myjob", - Namespace: api.NamespaceDefault, - UID: types.UID("1a2b3c"), - }, - Spec: extensions.JobSpec{ - Selector: validGeneratedSelector, - ManualSelector: newBool(false), - Template: validPodTemplateSpecForGenerated, - }, - }, - } - for k, v := range successCases { - if errs := ValidateJob(&v); len(errs) != 0 { - t.Errorf("expected success for %s: %v", k, errs) - } - } - negative := -1 - negative64 := int64(-1) - errorCases := map[string]extensions.Job{ - "spec.parallelism:must be greater than or equal to 0": { - ObjectMeta: api.ObjectMeta{ - Name: "myjob", - Namespace: api.NamespaceDefault, - UID: types.UID("1a2b3c"), - }, - Spec: extensions.JobSpec{ - Parallelism: &negative, - ManualSelector: newBool(true), - Template: validPodTemplateSpecForGenerated, - }, - }, - "spec.completions:must be greater than or equal to 0": { - ObjectMeta: api.ObjectMeta{ - Name: "myjob", - Namespace: api.NamespaceDefault, - UID: types.UID("1a2b3c"), - }, - Spec: extensions.JobSpec{ - Completions: &negative, - Selector: validManualSelector, - ManualSelector: newBool(true), - Template: validPodTemplateSpecForGenerated, - }, - }, - "spec.activeDeadlineSeconds:must be greater than or equal to 0": { - ObjectMeta: api.ObjectMeta{ - Name: "myjob", - Namespace: api.NamespaceDefault, - UID: types.UID("1a2b3c"), - }, - Spec: extensions.JobSpec{ - ActiveDeadlineSeconds: &negative64, - Selector: validManualSelector, - ManualSelector: newBool(true), - Template: validPodTemplateSpecForGenerated, - }, - }, - "spec.selector:Required value": { - ObjectMeta: api.ObjectMeta{ - Name: "myjob", - Namespace: api.NamespaceDefault, - UID: types.UID("1a2b3c"), - }, - Spec: extensions.JobSpec{ - Template: validPodTemplateSpecForGenerated, - }, - }, - "spec.template.metadata.labels: Invalid value: {\"y\":\"z\"}: `selector` does not match template `labels`": { - ObjectMeta: api.ObjectMeta{ - Name: "myjob", - Namespace: api.NamespaceDefault, - UID: types.UID("1a2b3c"), - }, - Spec: extensions.JobSpec{ - Selector: validManualSelector, - ManualSelector: newBool(true), - Template: api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - Labels: map[string]string{"y": "z"}, - }, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyOnFailure, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, - }, - }, - }, - }, - "spec.template.metadata.labels: Invalid value: {\"controller-uid\":\"4d5e6f\"}: `selector` does not match template `labels`": { - ObjectMeta: api.ObjectMeta{ - Name: "myjob", - Namespace: api.NamespaceDefault, - UID: types.UID("1a2b3c"), - }, - Spec: extensions.JobSpec{ - Selector: validManualSelector, - ManualSelector: newBool(true), - Template: api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - Labels: map[string]string{"controller-uid": "4d5e6f"}, - }, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyOnFailure, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, - }, - }, - }, - }, - "spec.template.spec.restartPolicy: Unsupported value": { - ObjectMeta: api.ObjectMeta{ - Name: "myjob", - Namespace: api.NamespaceDefault, - UID: types.UID("1a2b3c"), - }, - Spec: extensions.JobSpec{ - Selector: validManualSelector, - ManualSelector: newBool(true), - Template: api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - Labels: validManualSelector.MatchLabels, - }, - Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSClusterFirst, - Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, - }, - }, - }, - }, - } - - for k, v := range errorCases { - errs := ValidateJob(&v) - if len(errs) == 0 { - t.Errorf("expected failure for %s", k) - } else { - s := strings.Split(k, ":") - err := errs[0] - if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) { - t.Errorf("unexpected error: %v, expected: %s", err, k) - } - } - } -} - -func TestValidateJobUpdateStatus(t *testing.T) { - type testcase struct { - old extensions.Job - update extensions.Job - } - - successCases := []testcase{ - { - old: extensions.Job{ - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, - Status: extensions.JobStatus{ - Active: 1, - Succeeded: 2, - Failed: 3, - }, - }, - update: extensions.Job{ - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, - Status: extensions.JobStatus{ - Active: 1, - Succeeded: 1, - Failed: 3, - }, - }, - }, - } - - for _, successCase := range successCases { - successCase.old.ObjectMeta.ResourceVersion = "1" - successCase.update.ObjectMeta.ResourceVersion = "1" - if errs := ValidateJobUpdateStatus(&successCase.update, &successCase.old); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - - errorCases := map[string]testcase{ - "[status.active: Invalid value: -1: must be greater than or equal to 0, status.succeeded: Invalid value: -2: must be greater than or equal to 0]": { - old: extensions.Job{ - ObjectMeta: api.ObjectMeta{ - Name: "abc", - Namespace: api.NamespaceDefault, - ResourceVersion: "10", - }, - Status: extensions.JobStatus{ - Active: 1, - Succeeded: 2, - Failed: 3, - }, - }, - update: extensions.Job{ - ObjectMeta: api.ObjectMeta{ - Name: "abc", - Namespace: api.NamespaceDefault, - ResourceVersion: "10", - }, - Status: extensions.JobStatus{ - Active: -1, - Succeeded: -2, - Failed: 3, - }, - }, - }, - } - - for testName, errorCase := range errorCases { - errs := ValidateJobUpdateStatus(&errorCase.update, &errorCase.old) - if len(errs) == 0 { - t.Errorf("expected failure: %s", testName) - continue - } - if errs.ToAggregate().Error() != testName { - t.Errorf("expected '%s' got '%s'", errs.ToAggregate().Error(), testName) - } - } -} - type ingressRules map[string]string func TestValidateIngress(t *testing.T) { diff --git a/pkg/client/cache/listers.go b/pkg/client/cache/listers.go index 3963a6941a..3e8fa20889 100644 --- a/pkg/client/cache/listers.go +++ b/pkg/client/cache/listers.go @@ -22,6 +22,7 @@ import ( "github.com/golang/glog" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/labels" ) @@ -474,7 +475,7 @@ type StoreToJobLister struct { } // Exists checks if the given job exists in the store. -func (s *StoreToJobLister) Exists(job *extensions.Job) (bool, error) { +func (s *StoreToJobLister) Exists(job *batch.Job) (bool, error) { _, exists, err := s.Store.Get(job) if err != nil { return false, err @@ -483,17 +484,17 @@ func (s *StoreToJobLister) Exists(job *extensions.Job) (bool, error) { } // StoreToJobLister lists all jobs in the store. -func (s *StoreToJobLister) List() (jobs extensions.JobList, err error) { +func (s *StoreToJobLister) List() (jobs batch.JobList, err error) { for _, c := range s.Store.List() { - jobs.Items = append(jobs.Items, *(c.(*extensions.Job))) + jobs.Items = append(jobs.Items, *(c.(*batch.Job))) } return jobs, nil } // GetPodJobs returns a list of jobs managing a pod. Returns an error only if no matching jobs are found. -func (s *StoreToJobLister) GetPodJobs(pod *api.Pod) (jobs []extensions.Job, err error) { +func (s *StoreToJobLister) GetPodJobs(pod *api.Pod) (jobs []batch.Job, err error) { var selector labels.Selector - var job extensions.Job + var job batch.Job if len(pod.Labels) == 0 { err = fmt.Errorf("no jobs found for pod %v because it has no labels", pod.Name) @@ -501,7 +502,7 @@ func (s *StoreToJobLister) GetPodJobs(pod *api.Pod) (jobs []extensions.Job, err } for _, m := range s.Store.List() { - job = *m.(*extensions.Job) + job = *m.(*batch.Job) if job.Namespace != pod.Namespace { continue } diff --git a/pkg/client/cache/listers_test.go b/pkg/client/cache/listers_test.go index e95df1f21c..404d8ae393 100644 --- a/pkg/client/cache/listers_test.go +++ b/pkg/client/cache/listers_test.go @@ -21,6 +21,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/util/sets" @@ -476,18 +477,18 @@ func TestStoreToJobLister(t *testing.T) { store := NewStore(MetaNamespaceKeyFunc) lister := StoreToJobLister{store} testCases := []struct { - inJobs []*extensions.Job - list func() ([]extensions.Job, error) + inJobs []*batch.Job + list func() ([]batch.Job, error) outJobNames sets.String expectErr bool msg string }{ // Basic listing { - inJobs: []*extensions.Job{ + inJobs: []*batch.Job{ {ObjectMeta: api.ObjectMeta{Name: "basic"}}, }, - list: func() ([]extensions.Job, error) { + list: func() ([]batch.Job, error) { list, err := lister.List() return list.Items, err }, @@ -496,12 +497,12 @@ func TestStoreToJobLister(t *testing.T) { }, // Listing multiple jobs { - inJobs: []*extensions.Job{ + inJobs: []*batch.Job{ {ObjectMeta: api.ObjectMeta{Name: "basic"}}, {ObjectMeta: api.ObjectMeta{Name: "complex"}}, {ObjectMeta: api.ObjectMeta{Name: "complex2"}}, }, - list: func() ([]extensions.Job, error) { + list: func() ([]batch.Job, error) { list, err := lister.List() return list.Items, err }, @@ -510,17 +511,17 @@ func TestStoreToJobLister(t *testing.T) { }, // No pod labels { - inJobs: []*extensions.Job{ + inJobs: []*batch.Job{ { ObjectMeta: api.ObjectMeta{Name: "basic", Namespace: "ns"}, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Selector: &unversioned.LabelSelector{ MatchLabels: map[string]string{"foo": "baz"}, }, }, }, }, - list: func() ([]extensions.Job, error) { + list: func() ([]batch.Job, error) { pod := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "pod", Namespace: "ns"}, } @@ -532,12 +533,12 @@ func TestStoreToJobLister(t *testing.T) { }, // No Job selectors { - inJobs: []*extensions.Job{ + inJobs: []*batch.Job{ { ObjectMeta: api.ObjectMeta{Name: "basic", Namespace: "ns"}, }, }, - list: func() ([]extensions.Job, error) { + list: func() ([]batch.Job, error) { pod := &api.Pod{ ObjectMeta: api.ObjectMeta{ Name: "pod", @@ -553,10 +554,10 @@ func TestStoreToJobLister(t *testing.T) { }, // Matching labels to selectors and namespace { - inJobs: []*extensions.Job{ + inJobs: []*batch.Job{ { ObjectMeta: api.ObjectMeta{Name: "foo"}, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Selector: &unversioned.LabelSelector{ MatchLabels: map[string]string{"foo": "bar"}, }, @@ -564,14 +565,14 @@ func TestStoreToJobLister(t *testing.T) { }, { ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "ns"}, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Selector: &unversioned.LabelSelector{ MatchLabels: map[string]string{"foo": "bar"}, }, }, }, }, - list: func() ([]extensions.Job, error) { + list: func() ([]batch.Job, error) { pod := &api.Pod{ ObjectMeta: api.ObjectMeta{ Name: "pod", @@ -586,10 +587,10 @@ func TestStoreToJobLister(t *testing.T) { }, // Matching labels to selectors and namespace, error case { - inJobs: []*extensions.Job{ + inJobs: []*batch.Job{ { ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "foo"}, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Selector: &unversioned.LabelSelector{ MatchLabels: map[string]string{"foo": "bar"}, }, @@ -597,14 +598,14 @@ func TestStoreToJobLister(t *testing.T) { }, { ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "bar"}, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Selector: &unversioned.LabelSelector{ MatchLabels: map[string]string{"foo": "bar"}, }, }, }, }, - list: func() ([]extensions.Job, error) { + list: func() ([]batch.Job, error) { pod := &api.Pod{ ObjectMeta: api.ObjectMeta{ Name: "pod", diff --git a/pkg/client/unversioned/conditions.go b/pkg/client/unversioned/conditions.go index 20987c8abb..a61674e2ba 100644 --- a/pkg/client/unversioned/conditions.go +++ b/pkg/client/unversioned/conditions.go @@ -23,6 +23,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/util/wait" "k8s.io/kubernetes/pkg/watch" @@ -129,7 +130,7 @@ func ReplicaSetHasDesiredReplicas(c ExtensionsInterface, replicaSet *extensions. // JobHasDesiredParallelism returns a condition that will be true if the desired parallelism count // for a job equals the current active counts or is less by an appropriate successful/unsuccessful count. -func JobHasDesiredParallelism(c ExtensionsInterface, job *extensions.Job) wait.ConditionFunc { +func JobHasDesiredParallelism(c BatchInterface, job *batch.Job) wait.ConditionFunc { return func() (bool, error) { job, err := c.Jobs(job.Namespace).Get(job.Name) diff --git a/pkg/client/unversioned/jobs.go b/pkg/client/unversioned/jobs.go index f965a08749..94b819079a 100644 --- a/pkg/client/unversioned/jobs.go +++ b/pkg/client/unversioned/jobs.go @@ -18,7 +18,7 @@ package unversioned import ( "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/watch" ) @@ -29,13 +29,13 @@ type JobsNamespacer interface { // JobInterface exposes methods to work on Job resources. type JobInterface interface { - List(opts api.ListOptions) (*extensions.JobList, error) - Get(name string) (*extensions.Job, error) - Create(job *extensions.Job) (*extensions.Job, error) - Update(job *extensions.Job) (*extensions.Job, error) + List(opts api.ListOptions) (*batch.JobList, error) + Get(name string) (*batch.Job, error) + Create(job *batch.Job) (*batch.Job, error) + Update(job *batch.Job) (*batch.Job, error) Delete(name string, options *api.DeleteOptions) error Watch(opts api.ListOptions) (watch.Interface, error) - UpdateStatus(job *extensions.Job) (*extensions.Job, error) + UpdateStatus(job *batch.Job) (*batch.Job, error) } // jobs implements JobsNamespacer interface @@ -53,29 +53,29 @@ func newJobs(c *ExtensionsClient, namespace string) *jobs { var _ JobInterface = &jobs{} // List returns a list of jobs that match the label and field selectors. -func (c *jobs) List(opts api.ListOptions) (result *extensions.JobList, err error) { - result = &extensions.JobList{} +func (c *jobs) List(opts api.ListOptions) (result *batch.JobList, err error) { + result = &batch.JobList{} err = c.r.Get().Namespace(c.ns).Resource("jobs").VersionedParams(&opts, api.ParameterCodec).Do().Into(result) return } // Get returns information about a particular job. -func (c *jobs) Get(name string) (result *extensions.Job, err error) { - result = &extensions.Job{} +func (c *jobs) Get(name string) (result *batch.Job, err error) { + result = &batch.Job{} err = c.r.Get().Namespace(c.ns).Resource("jobs").Name(name).Do().Into(result) return } // Create creates a new job. -func (c *jobs) Create(job *extensions.Job) (result *extensions.Job, err error) { - result = &extensions.Job{} +func (c *jobs) Create(job *batch.Job) (result *batch.Job, err error) { + result = &batch.Job{} err = c.r.Post().Namespace(c.ns).Resource("jobs").Body(job).Do().Into(result) return } // Update updates an existing job. -func (c *jobs) Update(job *extensions.Job) (result *extensions.Job, err error) { - result = &extensions.Job{} +func (c *jobs) Update(job *batch.Job) (result *batch.Job, err error) { + result = &batch.Job{} err = c.r.Put().Namespace(c.ns).Resource("jobs").Name(job.Name).Body(job).Do().Into(result) return } @@ -96,8 +96,8 @@ func (c *jobs) Watch(opts api.ListOptions) (watch.Interface, error) { } // UpdateStatus takes the name of the job and the new status. Returns the server's representation of the job, and an error, if it occurs. -func (c *jobs) UpdateStatus(job *extensions.Job) (result *extensions.Job, err error) { - result = &extensions.Job{} +func (c *jobs) UpdateStatus(job *batch.Job) (result *batch.Job, err error) { + result = &batch.Job{} err = c.r.Put().Namespace(c.ns).Resource("jobs").Name(job.Name).SubResource("status").Body(job).Do().Into(result) return } @@ -117,29 +117,29 @@ func newJobsV1(c *BatchClient, namespace string) *jobsV1 { var _ JobInterface = &jobsV1{} // List returns a list of jobs that match the label and field selectors. -func (c *jobsV1) List(opts api.ListOptions) (result *extensions.JobList, err error) { - result = &extensions.JobList{} +func (c *jobsV1) List(opts api.ListOptions) (result *batch.JobList, err error) { + result = &batch.JobList{} err = c.r.Get().Namespace(c.ns).Resource("jobs").VersionedParams(&opts, api.ParameterCodec).Do().Into(result) return } // Get returns information about a particular job. -func (c *jobsV1) Get(name string) (result *extensions.Job, err error) { - result = &extensions.Job{} +func (c *jobsV1) Get(name string) (result *batch.Job, err error) { + result = &batch.Job{} err = c.r.Get().Namespace(c.ns).Resource("jobs").Name(name).Do().Into(result) return } // Create creates a new job. -func (c *jobsV1) Create(job *extensions.Job) (result *extensions.Job, err error) { - result = &extensions.Job{} +func (c *jobsV1) Create(job *batch.Job) (result *batch.Job, err error) { + result = &batch.Job{} err = c.r.Post().Namespace(c.ns).Resource("jobs").Body(job).Do().Into(result) return } // Update updates an existing job. -func (c *jobsV1) Update(job *extensions.Job) (result *extensions.Job, err error) { - result = &extensions.Job{} +func (c *jobsV1) Update(job *batch.Job) (result *batch.Job, err error) { + result = &batch.Job{} err = c.r.Put().Namespace(c.ns).Resource("jobs").Name(job.Name).Body(job).Do().Into(result) return } @@ -160,8 +160,8 @@ func (c *jobsV1) Watch(opts api.ListOptions) (watch.Interface, error) { } // UpdateStatus takes the name of the job and the new status. Returns the server's representation of the job, and an error, if it occurs. -func (c *jobsV1) UpdateStatus(job *extensions.Job) (result *extensions.Job, err error) { - result = &extensions.Job{} +func (c *jobsV1) UpdateStatus(job *batch.Job) (result *batch.Job, err error) { + result = &batch.Job{} err = c.r.Put().Namespace(c.ns).Resource("jobs").Name(job.Name).SubResource("status").Body(job).Do().Into(result) return } diff --git a/pkg/client/unversioned/jobs_test.go b/pkg/client/unversioned/jobs_test.go index a3f8c2270f..e47d49d51e 100644 --- a/pkg/client/unversioned/jobs_test.go +++ b/pkg/client/unversioned/jobs_test.go @@ -51,8 +51,8 @@ func testListJob(t *testing.T, group testapi.TestGroup, resourceGroup string) { Path: group.ResourcePath(getJobsResourceName(), ns, ""), }, Response: simple.Response{StatusCode: 200, - Body: &extensions.JobList{ - Items: []extensions.Job{ + Body: &batch.JobList{ + Items: []batch.Job{ { ObjectMeta: api.ObjectMeta{ Name: "foo", @@ -61,7 +61,7 @@ func testListJob(t *testing.T, group testapi.TestGroup, resourceGroup string) { "name": "baz", }, }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Template: api.PodTemplateSpec{}, }, }, @@ -90,7 +90,7 @@ func testGetJob(t *testing.T, group testapi.TestGroup, resourceGroup string) { }, Response: simple.Response{ StatusCode: 200, - Body: &extensions.Job{ + Body: &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "foo", Labels: map[string]string{ @@ -98,7 +98,7 @@ func testGetJob(t *testing.T, group testapi.TestGroup, resourceGroup string) { "name": "baz", }, }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Template: api.PodTemplateSpec{}, }, }, @@ -117,7 +117,7 @@ func TestGetJob(t *testing.T) { func testUpdateJob(t *testing.T, group testapi.TestGroup, resourceGroup string) { ns := api.NamespaceDefault - requestJob := &extensions.Job{ + requestJob := &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "foo", Namespace: ns, @@ -132,7 +132,7 @@ func testUpdateJob(t *testing.T, group testapi.TestGroup, resourceGroup string) }, Response: simple.Response{ StatusCode: 200, - Body: &extensions.Job{ + Body: &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "foo", Labels: map[string]string{ @@ -140,7 +140,7 @@ func testUpdateJob(t *testing.T, group testapi.TestGroup, resourceGroup string) "name": "baz", }, }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Template: api.PodTemplateSpec{}, }, }, @@ -159,7 +159,7 @@ func TestUpdateJob(t *testing.T) { func testUpdateJobStatus(t *testing.T, group testapi.TestGroup, resourceGroup string) { ns := api.NamespaceDefault - requestJob := &extensions.Job{ + requestJob := &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "foo", Namespace: ns, @@ -174,7 +174,7 @@ func testUpdateJobStatus(t *testing.T, group testapi.TestGroup, resourceGroup st }, Response: simple.Response{ StatusCode: 200, - Body: &extensions.Job{ + Body: &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "foo", Labels: map[string]string{ @@ -182,10 +182,10 @@ func testUpdateJobStatus(t *testing.T, group testapi.TestGroup, resourceGroup st "name": "baz", }, }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Template: api.PodTemplateSpec{}, }, - Status: extensions.JobStatus{ + Status: batch.JobStatus{ Active: 1, }, }, @@ -225,7 +225,7 @@ func TestDeleteJob(t *testing.T) { func testCreateJob(t *testing.T, group testapi.TestGroup, resourceGroup string) { ns := api.NamespaceDefault - requestJob := &extensions.Job{ + requestJob := &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "foo", Namespace: ns, @@ -240,7 +240,7 @@ func testCreateJob(t *testing.T, group testapi.TestGroup, resourceGroup string) }, Response: simple.Response{ StatusCode: 200, - Body: &extensions.Job{ + Body: &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "foo", Labels: map[string]string{ @@ -248,7 +248,7 @@ func testCreateJob(t *testing.T, group testapi.TestGroup, resourceGroup string) "name": "baz", }, }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Template: api.PodTemplateSpec{}, }, }, diff --git a/pkg/client/unversioned/testclient/fake_jobs.go b/pkg/client/unversioned/testclient/fake_jobs.go index 71ac8dfd65..dedde9dc3d 100644 --- a/pkg/client/unversioned/testclient/fake_jobs.go +++ b/pkg/client/unversioned/testclient/fake_jobs.go @@ -18,7 +18,7 @@ package testclient import ( "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/watch" ) @@ -29,44 +29,44 @@ type FakeJobs struct { Namespace string } -func (c *FakeJobs) Get(name string) (*extensions.Job, error) { - obj, err := c.Fake.Invokes(NewGetAction("jobs", c.Namespace, name), &extensions.Job{}) +func (c *FakeJobs) Get(name string) (*batch.Job, error) { + obj, err := c.Fake.Invokes(NewGetAction("jobs", c.Namespace, name), &batch.Job{}) if obj == nil { return nil, err } - return obj.(*extensions.Job), err + return obj.(*batch.Job), err } -func (c *FakeJobs) List(opts api.ListOptions) (*extensions.JobList, error) { - obj, err := c.Fake.Invokes(NewListAction("jobs", c.Namespace, opts), &extensions.JobList{}) +func (c *FakeJobs) List(opts api.ListOptions) (*batch.JobList, error) { + obj, err := c.Fake.Invokes(NewListAction("jobs", c.Namespace, opts), &batch.JobList{}) if obj == nil { return nil, err } - return obj.(*extensions.JobList), err + return obj.(*batch.JobList), err } -func (c *FakeJobs) Create(job *extensions.Job) (*extensions.Job, error) { +func (c *FakeJobs) Create(job *batch.Job) (*batch.Job, error) { obj, err := c.Fake.Invokes(NewCreateAction("jobs", c.Namespace, job), job) if obj == nil { return nil, err } - return obj.(*extensions.Job), err + return obj.(*batch.Job), err } -func (c *FakeJobs) Update(job *extensions.Job) (*extensions.Job, error) { +func (c *FakeJobs) Update(job *batch.Job) (*batch.Job, error) { obj, err := c.Fake.Invokes(NewUpdateAction("jobs", c.Namespace, job), job) if obj == nil { return nil, err } - return obj.(*extensions.Job), err + return obj.(*batch.Job), err } func (c *FakeJobs) Delete(name string, options *api.DeleteOptions) error { - _, err := c.Fake.Invokes(NewDeleteAction("jobs", c.Namespace, name), &extensions.Job{}) + _, err := c.Fake.Invokes(NewDeleteAction("jobs", c.Namespace, name), &batch.Job{}) return err } @@ -74,13 +74,13 @@ func (c *FakeJobs) Watch(opts api.ListOptions) (watch.Interface, error) { return c.Fake.InvokesWatch(NewWatchAction("jobs", c.Namespace, opts)) } -func (c *FakeJobs) UpdateStatus(job *extensions.Job) (result *extensions.Job, err error) { +func (c *FakeJobs) UpdateStatus(job *batch.Job) (result *batch.Job, err error) { obj, err := c.Fake.Invokes(NewUpdateSubresourceAction("jobs", "status", c.Namespace, job), job) if obj == nil { return nil, err } - return obj.(*extensions.Job), err + return obj.(*batch.Job), err } // FakeJobs implements JobInterface. Meant to be embedded into a struct to get a default @@ -92,44 +92,44 @@ type FakeJobsV1 struct { Namespace string } -func (c *FakeJobsV1) Get(name string) (*extensions.Job, error) { - obj, err := c.Fake.Invokes(NewGetAction("jobs", c.Namespace, name), &extensions.Job{}) +func (c *FakeJobsV1) Get(name string) (*batch.Job, error) { + obj, err := c.Fake.Invokes(NewGetAction("jobs", c.Namespace, name), &batch.Job{}) if obj == nil { return nil, err } - return obj.(*extensions.Job), err + return obj.(*batch.Job), err } -func (c *FakeJobsV1) List(opts api.ListOptions) (*extensions.JobList, error) { - obj, err := c.Fake.Invokes(NewListAction("jobs", c.Namespace, opts), &extensions.JobList{}) +func (c *FakeJobsV1) List(opts api.ListOptions) (*batch.JobList, error) { + obj, err := c.Fake.Invokes(NewListAction("jobs", c.Namespace, opts), &batch.JobList{}) if obj == nil { return nil, err } - return obj.(*extensions.JobList), err + return obj.(*batch.JobList), err } -func (c *FakeJobsV1) Create(job *extensions.Job) (*extensions.Job, error) { +func (c *FakeJobsV1) Create(job *batch.Job) (*batch.Job, error) { obj, err := c.Fake.Invokes(NewCreateAction("jobs", c.Namespace, job), job) if obj == nil { return nil, err } - return obj.(*extensions.Job), err + return obj.(*batch.Job), err } -func (c *FakeJobsV1) Update(job *extensions.Job) (*extensions.Job, error) { +func (c *FakeJobsV1) Update(job *batch.Job) (*batch.Job, error) { obj, err := c.Fake.Invokes(NewUpdateAction("jobs", c.Namespace, job), job) if obj == nil { return nil, err } - return obj.(*extensions.Job), err + return obj.(*batch.Job), err } func (c *FakeJobsV1) Delete(name string, options *api.DeleteOptions) error { - _, err := c.Fake.Invokes(NewDeleteAction("jobs", c.Namespace, name), &extensions.Job{}) + _, err := c.Fake.Invokes(NewDeleteAction("jobs", c.Namespace, name), &batch.Job{}) return err } @@ -137,11 +137,11 @@ func (c *FakeJobsV1) Watch(opts api.ListOptions) (watch.Interface, error) { return c.Fake.InvokesWatch(NewWatchAction("jobs", c.Namespace, opts)) } -func (c *FakeJobsV1) UpdateStatus(job *extensions.Job) (result *extensions.Job, err error) { +func (c *FakeJobsV1) UpdateStatus(job *batch.Job) (result *batch.Job, err error) { obj, err := c.Fake.Invokes(NewUpdateSubresourceAction("jobs", "status", c.Namespace, job), job) if obj == nil { return nil, err } - return obj.(*extensions.Job), err + return obj.(*batch.Job), err } diff --git a/pkg/controller/job/controller.go b/pkg/controller/job/controller.go index 1db765e8e2..63e504a2eb 100644 --- a/pkg/controller/job/controller.go +++ b/pkg/controller/job/controller.go @@ -25,7 +25,7 @@ import ( "github.com/golang/glog" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" - "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/client/cache" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" @@ -53,7 +53,7 @@ type JobController struct { internalPodInformer framework.SharedInformer // To allow injection of updateJobStatus for testing. - updateHandler func(job *extensions.Job) error + updateHandler func(job *batch.Job) error syncHandler func(jobKey string) error // podStoreSynced returns true if the pod store has been synced at least once. // Added as a member to the struct to allow injection for testing. @@ -96,19 +96,19 @@ func NewJobController(podInformer framework.SharedInformer, kubeClient clientset jm.jobStore.Store, jm.jobController = framework.NewInformer( &cache.ListWatch{ ListFunc: func(options api.ListOptions) (runtime.Object, error) { - return jm.kubeClient.Extensions().Jobs(api.NamespaceAll).List(options) + return jm.kubeClient.Batch().Jobs(api.NamespaceAll).List(options) }, WatchFunc: func(options api.ListOptions) (watch.Interface, error) { - return jm.kubeClient.Extensions().Jobs(api.NamespaceAll).Watch(options) + return jm.kubeClient.Batch().Jobs(api.NamespaceAll).Watch(options) }, }, - &extensions.Job{}, + &batch.Job{}, // TODO: Can we have much longer period here? replicationcontroller.FullControllerResyncPeriod, framework.ResourceEventHandlerFuncs{ AddFunc: jm.enqueueController, UpdateFunc: func(old, cur interface{}) { - if job := cur.(*extensions.Job); !isJobFinished(job) { + if job := cur.(*batch.Job); !isJobFinished(job) { jm.enqueueController(job) } }, @@ -155,7 +155,7 @@ func (jm *JobController) Run(workers int, stopCh <-chan struct{}) { } // getPodJob returns the job managing the given pod. -func (jm *JobController) getPodJob(pod *api.Pod) *extensions.Job { +func (jm *JobController) getPodJob(pod *api.Pod) *batch.Job { jobs, err := jm.jobStore.GetPodJobs(pod) if err != nil { glog.V(4).Infof("No jobs found for pod %v, job controller will avoid syncing", pod.Name) @@ -251,7 +251,7 @@ func (jm *JobController) deletePod(obj interface{}) { } } -// obj could be an *extensions.Job, or a DeletionFinalStateUnknown marker item. +// obj could be an *batch.Job, or a DeletionFinalStateUnknown marker item. func (jm *JobController) enqueueController(obj interface{}) { key, err := controller.KeyFunc(obj) if err != nil { @@ -314,7 +314,7 @@ func (jm *JobController) syncJob(key string) error { jm.queue.Add(key) return err } - job := *obj.(*extensions.Job) + job := *obj.(*batch.Job) // Check the expectations of the job before counting active pods, otherwise a new pod can sneak in // and update the expectations after we've retrieved active pods from the store. If a new pod enters @@ -366,7 +366,7 @@ func (jm *JobController) syncJob(key string) error { // update status values accordingly failed += active active = 0 - job.Status.Conditions = append(job.Status.Conditions, newCondition(extensions.JobFailed, "DeadlineExceeded", "Job was active longer than specified deadline")) + job.Status.Conditions = append(job.Status.Conditions, newCondition(batch.JobFailed, "DeadlineExceeded", "Job was active longer than specified deadline")) jm.recorder.Event(&job, api.EventTypeNormal, "DeadlineExceeded", "Job was active longer than specified deadline") } else { if jobNeedsSync { @@ -400,7 +400,7 @@ func (jm *JobController) syncJob(key string) error { } } if complete { - job.Status.Conditions = append(job.Status.Conditions, newCondition(extensions.JobComplete, "", "")) + job.Status.Conditions = append(job.Status.Conditions, newCondition(batch.JobComplete, "", "")) now := unversioned.Now() job.Status.CompletionTime = &now } @@ -421,7 +421,7 @@ func (jm *JobController) syncJob(key string) error { } // pastActiveDeadline checks if job has ActiveDeadlineSeconds field set and if it is exceeded. -func pastActiveDeadline(job *extensions.Job) bool { +func pastActiveDeadline(job *batch.Job) bool { if job.Spec.ActiveDeadlineSeconds == nil || job.Status.StartTime == nil { return false } @@ -432,8 +432,8 @@ func pastActiveDeadline(job *extensions.Job) bool { return duration >= allowedDuration } -func newCondition(conditionType extensions.JobConditionType, reason, message string) extensions.JobCondition { - return extensions.JobCondition{ +func newCondition(conditionType batch.JobConditionType, reason, message string) batch.JobCondition { + return batch.JobCondition{ Type: conditionType, Status: api.ConditionTrue, LastProbeTime: unversioned.Now(), @@ -452,7 +452,7 @@ func getStatus(pods []api.Pod) (succeeded, failed int) { // manageJob is the core method responsible for managing the number of running // pods according to what is specified in the job.Spec. -func (jm *JobController) manageJob(activePods []*api.Pod, succeeded int, job *extensions.Job) int { +func (jm *JobController) manageJob(activePods []*api.Pod, succeeded int, job *batch.Job) int { var activeLock sync.Mutex active := len(activePods) parallelism := *job.Spec.Parallelism @@ -538,8 +538,8 @@ func (jm *JobController) manageJob(activePods []*api.Pod, succeeded int, job *ex return active } -func (jm *JobController) updateJobStatus(job *extensions.Job) error { - _, err := jm.kubeClient.Extensions().Jobs(job.Namespace).UpdateStatus(job) +func (jm *JobController) updateJobStatus(job *batch.Job) error { + _, err := jm.kubeClient.Batch().Jobs(job.Namespace).UpdateStatus(job) return err } @@ -554,9 +554,9 @@ func filterPods(pods []api.Pod, phase api.PodPhase) int { return result } -func isJobFinished(j *extensions.Job) bool { +func isJobFinished(j *batch.Job) bool { for _, c := range j.Status.Conditions { - if (c.Type == extensions.JobComplete || c.Type == extensions.JobFailed) && c.Status == api.ConditionTrue { + if (c.Type == batch.JobComplete || c.Type == batch.JobFailed) && c.Status == api.ConditionTrue { return true } } @@ -564,7 +564,7 @@ func isJobFinished(j *extensions.Job) bool { } // byCreationTimestamp sorts a list by creation timestamp, using their names as a tie breaker. -type byCreationTimestamp []extensions.Job +type byCreationTimestamp []batch.Job func (o byCreationTimestamp) Len() int { return len(o) } func (o byCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] } diff --git a/pkg/controller/job/controller_test.go b/pkg/controller/job/controller_test.go index 5bf7b50d1f..f16d4eda5c 100644 --- a/pkg/controller/job/controller_test.go +++ b/pkg/controller/job/controller_test.go @@ -24,7 +24,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" - "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/batch" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" "k8s.io/kubernetes/pkg/client/restclient" @@ -37,13 +37,13 @@ import ( var alwaysReady = func() bool { return true } -func newJob(parallelism, completions int) *extensions.Job { - j := &extensions.Job{ +func newJob(parallelism, completions int) *batch.Job { + j := &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "foobar", Namespace: api.NamespaceDefault, }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Selector: &unversioned.LabelSelector{ MatchLabels: map[string]string{"foo": "bar"}, }, @@ -76,7 +76,7 @@ func newJob(parallelism, completions int) *extensions.Job { return j } -func getKey(job *extensions.Job, t *testing.T) string { +func getKey(job *batch.Job, t *testing.T) string { if key, err := controller.KeyFunc(job); err != nil { t.Errorf("Unexpected error getting key for job %v: %v", job.Name, err) return "" @@ -86,7 +86,7 @@ func getKey(job *extensions.Job, t *testing.T) string { } // create count pods with the given phase for the given job -func newPodList(count int, status api.PodPhase, job *extensions.Job) []api.Pod { +func newPodList(count int, status api.PodPhase, job *batch.Job) []api.Pod { pods := []api.Pod{} for i := 0; i < count; i++ { newPod := api.Pod{ @@ -211,8 +211,8 @@ func TestControllerSyncJob(t *testing.T) { fakePodControl := controller.FakePodControl{Err: tc.podControllerError} manager.podControl = &fakePodControl manager.podStoreSynced = alwaysReady - var actual *extensions.Job - manager.updateHandler = func(job *extensions.Job) error { + var actual *batch.Job + manager.updateHandler = func(job *batch.Job) error { actual = job return nil } @@ -257,7 +257,7 @@ func TestControllerSyncJob(t *testing.T) { t.Errorf("%s: .status.startTime was not set", name) } // validate conditions - if tc.expectedComplete && !getCondition(actual, extensions.JobComplete) { + if tc.expectedComplete && !getCondition(actual, batch.JobComplete) { t.Errorf("%s: expected completion condition. Got %#v", name, actual.Status.Conditions) } } @@ -306,8 +306,8 @@ func TestSyncJobPastDeadline(t *testing.T) { fakePodControl := controller.FakePodControl{} manager.podControl = &fakePodControl manager.podStoreSynced = alwaysReady - var actual *extensions.Job - manager.updateHandler = func(job *extensions.Job) error { + var actual *batch.Job + manager.updateHandler = func(job *batch.Job) error { actual = job return nil } @@ -355,13 +355,13 @@ func TestSyncJobPastDeadline(t *testing.T) { t.Errorf("%s: .status.startTime was not set", name) } // validate conditions - if !getCondition(actual, extensions.JobFailed) { + if !getCondition(actual, batch.JobFailed) { t.Errorf("%s: expected fail condition. Got %#v", name, actual.Status.Conditions) } } } -func getCondition(job *extensions.Job, condition extensions.JobConditionType) bool { +func getCondition(job *batch.Job, condition batch.JobConditionType) bool { for _, v := range job.Status.Conditions { if v.Type == condition && v.Status == api.ConditionTrue { return true @@ -376,8 +376,8 @@ func TestSyncPastDeadlineJobFinished(t *testing.T) { fakePodControl := controller.FakePodControl{} manager.podControl = &fakePodControl manager.podStoreSynced = alwaysReady - var actual *extensions.Job - manager.updateHandler = func(job *extensions.Job) error { + var actual *batch.Job + manager.updateHandler = func(job *batch.Job) error { actual = job return nil } @@ -387,7 +387,7 @@ func TestSyncPastDeadlineJobFinished(t *testing.T) { job.Spec.ActiveDeadlineSeconds = &activeDeadlineSeconds start := unversioned.Unix(unversioned.Now().Time.Unix()-15, 0) job.Status.StartTime = &start - job.Status.Conditions = append(job.Status.Conditions, newCondition(extensions.JobFailed, "DeadlineExceeded", "Job was active longer than specified deadline")) + job.Status.Conditions = append(job.Status.Conditions, newCondition(batch.JobFailed, "DeadlineExceeded", "Job was active longer than specified deadline")) manager.jobStore.Store.Add(job) err := manager.syncJob(getKey(job, t)) if err != nil { @@ -412,7 +412,7 @@ func TestSyncJobComplete(t *testing.T) { manager.podStoreSynced = alwaysReady job := newJob(1, 1) - job.Status.Conditions = append(job.Status.Conditions, newCondition(extensions.JobComplete, "", "")) + job.Status.Conditions = append(job.Status.Conditions, newCondition(batch.JobComplete, "", "")) manager.jobStore.Store.Add(job) err := manager.syncJob(getKey(job, t)) if err != nil { @@ -422,7 +422,7 @@ func TestSyncJobComplete(t *testing.T) { if err != nil { t.Fatalf("Unexpected error when trying to get job from the store: %v", err) } - actual := uncastJob.(*extensions.Job) + actual := uncastJob.(*batch.Job) // Verify that after syncing a complete job, the conditions are the same. if got, expected := len(actual.Status.Conditions), 1; got != expected { t.Fatalf("Unexpected job status conditions amount; expected %d, got %d", expected, got) @@ -435,7 +435,7 @@ func TestSyncJobDeleted(t *testing.T) { fakePodControl := controller.FakePodControl{} manager.podControl = &fakePodControl manager.podStoreSynced = alwaysReady - manager.updateHandler = func(job *extensions.Job) error { return nil } + manager.updateHandler = func(job *batch.Job) error { return nil } job := newJob(2, 2) err := manager.syncJob(getKey(job, t)) if err != nil { @@ -455,7 +455,7 @@ func TestSyncJobUpdateRequeue(t *testing.T) { fakePodControl := controller.FakePodControl{} manager.podControl = &fakePodControl manager.podStoreSynced = alwaysReady - manager.updateHandler = func(job *extensions.Job) error { return fmt.Errorf("Fake error") } + manager.updateHandler = func(job *batch.Job) error { return fmt.Errorf("Fake error") } job := newJob(2, 2) manager.jobStore.Store.Add(job) err := manager.syncJob(getKey(job, t)) @@ -475,14 +475,14 @@ func TestJobPodLookup(t *testing.T) { manager := NewJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) manager.podStoreSynced = alwaysReady testCases := []struct { - job *extensions.Job + job *batch.Job pod *api.Pod expectedName string }{ // pods without labels don't match any job { - job: &extensions.Job{ + job: &batch.Job{ ObjectMeta: api.ObjectMeta{Name: "basic"}, }, pod: &api.Pod{ @@ -492,9 +492,9 @@ func TestJobPodLookup(t *testing.T) { }, // matching labels, different namespace { - job: &extensions.Job{ + job: &batch.Job{ ObjectMeta: api.ObjectMeta{Name: "foo"}, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Selector: &unversioned.LabelSelector{ MatchLabels: map[string]string{"foo": "bar"}, }, @@ -511,9 +511,9 @@ func TestJobPodLookup(t *testing.T) { }, // matching ns and labels returns { - job: &extensions.Job{ + job: &batch.Job{ ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "ns"}, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Selector: &unversioned.LabelSelector{ MatchExpressions: []unversioned.LabelSelectorRequirement{ { @@ -566,7 +566,7 @@ func TestSyncJobExpectations(t *testing.T) { fakePodControl := controller.FakePodControl{} manager.podControl = &fakePodControl manager.podStoreSynced = alwaysReady - manager.updateHandler = func(job *extensions.Job) error { return nil } + manager.updateHandler = func(job *batch.Job) error { return nil } job := newJob(2, 2) manager.jobStore.Store.Add(job) @@ -602,7 +602,7 @@ func TestWatchJobs(t *testing.T) { manager := NewJobControllerFromClient(clientset, controller.NoResyncPeriodFunc) manager.podStoreSynced = alwaysReady - var testJob extensions.Job + var testJob batch.Job received := make(chan struct{}) // The update sent through the fakeWatcher should make its way into the workqueue, @@ -613,7 +613,7 @@ func TestWatchJobs(t *testing.T) { if !exists || err != nil { t.Errorf("Expected to find job under key %v", key) } - job, ok := obj.(*extensions.Job) + job, ok := obj.(*batch.Job) if !ok { t.Fatalf("unexpected type: %v %#v", reflect.TypeOf(obj), obj) } @@ -637,10 +637,10 @@ func TestWatchJobs(t *testing.T) { } func TestIsJobFinished(t *testing.T) { - job := &extensions.Job{ - Status: extensions.JobStatus{ - Conditions: []extensions.JobCondition{{ - Type: extensions.JobComplete, + job := &batch.Job{ + Status: batch.JobStatus{ + Conditions: []batch.JobCondition{{ + Type: batch.JobComplete, Status: api.ConditionTrue, }}, }, @@ -681,7 +681,7 @@ func TestWatchPods(t *testing.T) { close(received) return nil } - job, ok := obj.(*extensions.Job) + job, ok := obj.(*batch.Job) if !ok { t.Errorf("unexpected type: %v %#v", reflect.TypeOf(obj), obj) close(received) diff --git a/pkg/kubectl/cmd/drain_test.go b/pkg/kubectl/cmd/drain_test.go index 9db7347121..e1f421bcd6 100644 --- a/pkg/kubectl/cmd/drain_test.go +++ b/pkg/kubectl/cmd/drain_test.go @@ -33,6 +33,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/unversioned/fake" @@ -262,14 +263,14 @@ func TestDrain(t *testing.T) { }, } - job := extensions.Job{ + job := batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "job", Namespace: "default", CreationTimestamp: unversioned.Time{Time: time.Now()}, SelfLink: "/apis/extensions/v1beta1/namespaces/default/jobs/job", }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Selector: &unversioned.LabelSelector{MatchLabels: labels}, }, } diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index cd1eb110c3..35c585ed8c 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -665,7 +665,7 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { } pod, _, err := GetFirstPod(client, t.Namespace, selector) return pod, err - case *extensions.Job: + case *batch.Job: selector, err := unversioned.LabelSelectorAsSelector(t.Spec.Selector) if err != nil { return nil, fmt.Errorf("invalid label selector: %v", err) diff --git a/pkg/kubectl/describe.go b/pkg/kubectl/describe.go index d5a9fd7717..0a66b987db 100644 --- a/pkg/kubectl/describe.go +++ b/pkg/kubectl/describe.go @@ -1089,7 +1089,7 @@ func (d *JobDescriber) Describe(namespace, name string) (string, error) { return describeJob(job, events) } -func describeJob(job *extensions.Job, events *api.EventList) (string, error) { +func describeJob(job *batch.Job, events *api.EventList) (string, error) { return tabbedString(func(out io.Writer) error { fmt.Fprintf(out, "Name:\t%s\n", job.Name) fmt.Fprintf(out, "Namespace:\t%s\n", job.Namespace) diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index 623bf6f2b6..3160886b1a 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -36,6 +36,7 @@ import ( "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/apps" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/runtime" @@ -795,7 +796,7 @@ func printReplicaSetList(list *extensions.ReplicaSetList, w io.Writer, options P return nil } -func printJob(job *extensions.Job, w io.Writer, options PrintOptions) error { +func printJob(job *batch.Job, w io.Writer, options PrintOptions) error { name := job.Name namespace := job.Namespace containers := job.Spec.Template.Spec.Containers @@ -848,7 +849,7 @@ func printJob(job *extensions.Job, w io.Writer, options PrintOptions) error { return nil } -func printJobList(list *extensions.JobList, w io.Writer, options PrintOptions) error { +func printJobList(list *batch.JobList, w io.Writer, options PrintOptions) error { for _, job := range list.Items { if err := printJob(&job, w, options); err != nil { return err @@ -1784,7 +1785,7 @@ func formatWideHeaders(wide bool, t reflect.Type) []string { if t.String() == "*api.ReplicationController" || t.String() == "*api.ReplicationControllerList" { return []string{"CONTAINER(S)", "IMAGE(S)", "SELECTOR"} } - if t.String() == "*extensions.Job" || t.String() == "*extensions.JobList" { + if t.String() == "*batch.Job" || t.String() == "*batch.JobList" { return []string{"CONTAINER(S)", "IMAGE(S)", "SELECTOR"} } if t.String() == "*api.Service" || t.String() == "*api.ServiceList" { diff --git a/pkg/kubectl/resource_printer_test.go b/pkg/kubectl/resource_printer_test.go index ccc1f9b9da..f9905d35f2 100644 --- a/pkg/kubectl/resource_printer_test.go +++ b/pkg/kubectl/resource_printer_test.go @@ -30,6 +30,7 @@ import ( "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" kubectltesting "k8s.io/kubernetes/pkg/kubectl/testing" "k8s.io/kubernetes/pkg/runtime" @@ -1343,34 +1344,34 @@ func TestPrintDaemonSet(t *testing.T) { func TestPrintJob(t *testing.T) { completions := 2 tests := []struct { - job extensions.Job + job batch.Job expect string }{ { - extensions.Job{ + batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "job1", CreationTimestamp: unversioned.Time{Time: time.Now().Add(1.9e9)}, }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Completions: &completions, }, - Status: extensions.JobStatus{ + Status: batch.JobStatus{ Succeeded: 1, }, }, "job1\t2\t1\t0s\n", }, { - extensions.Job{ + batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "job2", CreationTimestamp: unversioned.Time{Time: time.Now().AddDate(-10, 0, 0)}, }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Completions: nil, }, - Status: extensions.JobStatus{ + Status: batch.JobStatus{ Succeeded: 0, }, }, diff --git a/pkg/kubectl/run.go b/pkg/kubectl/run.go index 688b570e7d..0fc3ec85eb 100644 --- a/pkg/kubectl/run.go +++ b/pkg/kubectl/run.go @@ -25,6 +25,7 @@ import ( "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/apis/batch" batchv1 "k8s.io/kubernetes/pkg/apis/batch/v1" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/runtime" @@ -280,12 +281,12 @@ func (JobV1Beta1) Generate(genericParams map[string]interface{}) (runtime.Object } podSpec.RestartPolicy = restartPolicy - job := extensions.Job{ + job := batch.Job{ ObjectMeta: api.ObjectMeta{ Name: name, Labels: labels, }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Selector: &unversioned.LabelSelector{ MatchLabels: labels, }, diff --git a/pkg/kubectl/run_test.go b/pkg/kubectl/run_test.go index 46ca5984eb..b211fc363e 100644 --- a/pkg/kubectl/run_test.go +++ b/pkg/kubectl/run_test.go @@ -23,6 +23,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" ) @@ -721,7 +722,7 @@ func TestGenerateDeployment(t *testing.T) { func TestGenerateJob(t *testing.T) { tests := []struct { params map[string]interface{} - expected *extensions.Job + expected *batch.Job expectErr bool }{ { @@ -740,12 +741,12 @@ func TestGenerateJob(t *testing.T) { "limits": "cpu=400m,memory=200Mi", "restart": "OnFailure", }, - expected: &extensions.Job{ + expected: &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "foo", Labels: map[string]string{"foo": "bar", "baz": "blah"}, }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Selector: &unversioned.LabelSelector{ MatchLabels: map[string]string{"foo": "bar", "baz": "blah"}, }, @@ -807,8 +808,8 @@ func TestGenerateJob(t *testing.T) { if test.expectErr && err != nil { continue } - if !reflect.DeepEqual(obj.(*extensions.Job), test.expected) { - t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*extensions.Job)) + if !reflect.DeepEqual(obj.(*batch.Job), test.expected) { + t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*batch.Job)) } } } diff --git a/pkg/kubectl/scale.go b/pkg/kubectl/scale.go index 14951c0287..7e9b6ba190 100644 --- a/pkg/kubectl/scale.go +++ b/pkg/kubectl/scale.go @@ -48,7 +48,7 @@ func ScalerFor(kind unversioned.GroupKind, c client.Interface) (Scaler, error) { case extensions.Kind("ReplicaSet"): return &ReplicaSetScaler{c.Extensions()}, nil case extensions.Kind("Job"), batch.Kind("Job"): - return &JobScaler{c.Extensions()}, nil // Either kind of job can be scaled with Extensions interface. + return &JobScaler{c.Batch()}, nil // Either kind of job can be scaled with Batch interface. case extensions.Kind("Deployment"): return &DeploymentScaler{c.Extensions()}, nil } @@ -252,7 +252,7 @@ func (scaler *ReplicaSetScaler) Scale(namespace, name string, newSize uint, prec } // ValidateJob ensures that the preconditions match. Returns nil if they are valid, an error otherwise. -func (precondition *ScalePrecondition) ValidateJob(job *extensions.Job) error { +func (precondition *ScalePrecondition) ValidateJob(job *batch.Job) error { if precondition.Size != -1 && job.Spec.Parallelism == nil { return PreconditionError{"parallelism", strconv.Itoa(precondition.Size), "nil"} } @@ -266,7 +266,7 @@ func (precondition *ScalePrecondition) ValidateJob(job *extensions.Job) error { } type JobScaler struct { - c client.ExtensionsInterface + c client.BatchInterface } // ScaleSimple is responsible for updating job's parallelism. diff --git a/pkg/kubectl/scale_test.go b/pkg/kubectl/scale_test.go index e303fc34ac..d1af0105d3 100644 --- a/pkg/kubectl/scale_test.go +++ b/pkg/kubectl/scale_test.go @@ -22,6 +22,7 @@ import ( "k8s.io/kubernetes/pkg/api" kerrors "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/testclient" @@ -252,17 +253,17 @@ type ErrorJobs struct { invalid bool } -func (c *ErrorJobs) Update(job *extensions.Job) (*extensions.Job, error) { +func (c *ErrorJobs) Update(job *batch.Job) (*batch.Job, error) { if c.invalid { - return nil, kerrors.NewInvalid(extensions.Kind(job.Kind), job.Name, nil) + return nil, kerrors.NewInvalid(batch.Kind(job.Kind), job.Name, nil) } return nil, errors.New("Job update failure") } -func (c *ErrorJobs) Get(name string) (*extensions.Job, error) { +func (c *ErrorJobs) Get(name string) (*batch.Job, error) { zero := 0 - return &extensions.Job{ - Spec: extensions.JobSpec{ + return &batch.Job{ + Spec: batch.JobSpec{ Parallelism: &zero, }, }, nil @@ -316,7 +317,7 @@ func TestJobScale(t *testing.T) { if action, ok := actions[0].(testclient.GetAction); !ok || action.GetResource() != "jobs" || action.GetName() != name { t.Errorf("unexpected action: %v, expected get-replicationController %s", actions[0], name) } - if action, ok := actions[1].(testclient.UpdateAction); !ok || action.GetResource() != "jobs" || *action.GetObject().(*extensions.Job).Spec.Parallelism != int(count) { + if action, ok := actions[1].(testclient.UpdateAction); !ok || action.GetResource() != "jobs" || *action.GetObject().(*batch.Job).Spec.Parallelism != int(count) { t.Errorf("unexpected action %v, expected update-job with parallelism = %d", actions[1], count) } } @@ -342,8 +343,8 @@ func TestJobScaleInvalid(t *testing.T) { func TestJobScaleFailsPreconditions(t *testing.T) { ten := 10 - fake := testclient.NewSimpleFake(&extensions.Job{ - Spec: extensions.JobSpec{ + fake := testclient.NewSimpleFake(&batch.Job{ + Spec: batch.JobSpec{ Parallelism: &ten, }, }) @@ -366,7 +367,7 @@ func TestValidateJob(t *testing.T) { zero, ten, twenty := 0, 10, 20 tests := []struct { preconditions ScalePrecondition - job extensions.Job + job batch.Job expectError bool test string }{ @@ -377,11 +378,11 @@ func TestValidateJob(t *testing.T) { }, { preconditions: ScalePrecondition{-1, ""}, - job: extensions.Job{ + job: batch.Job{ ObjectMeta: api.ObjectMeta{ ResourceVersion: "foo", }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Parallelism: &ten, }, }, @@ -390,11 +391,11 @@ func TestValidateJob(t *testing.T) { }, { preconditions: ScalePrecondition{0, ""}, - job: extensions.Job{ + job: batch.Job{ ObjectMeta: api.ObjectMeta{ ResourceVersion: "foo", }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Parallelism: &zero, }, }, @@ -403,11 +404,11 @@ func TestValidateJob(t *testing.T) { }, { preconditions: ScalePrecondition{-1, "foo"}, - job: extensions.Job{ + job: batch.Job{ ObjectMeta: api.ObjectMeta{ ResourceVersion: "foo", }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Parallelism: &ten, }, }, @@ -416,11 +417,11 @@ func TestValidateJob(t *testing.T) { }, { preconditions: ScalePrecondition{10, "foo"}, - job: extensions.Job{ + job: batch.Job{ ObjectMeta: api.ObjectMeta{ ResourceVersion: "foo", }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Parallelism: &ten, }, }, @@ -429,11 +430,11 @@ func TestValidateJob(t *testing.T) { }, { preconditions: ScalePrecondition{10, "foo"}, - job: extensions.Job{ + job: batch.Job{ ObjectMeta: api.ObjectMeta{ ResourceVersion: "foo", }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Parallelism: &twenty, }, }, @@ -442,7 +443,7 @@ func TestValidateJob(t *testing.T) { }, { preconditions: ScalePrecondition{10, "foo"}, - job: extensions.Job{ + job: batch.Job{ ObjectMeta: api.ObjectMeta{ ResourceVersion: "foo", }, @@ -452,11 +453,11 @@ func TestValidateJob(t *testing.T) { }, { preconditions: ScalePrecondition{10, "foo"}, - job: extensions.Job{ + job: batch.Job{ ObjectMeta: api.ObjectMeta{ ResourceVersion: "bar", }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Parallelism: &ten, }, }, @@ -465,11 +466,11 @@ func TestValidateJob(t *testing.T) { }, { preconditions: ScalePrecondition{10, "foo"}, - job: extensions.Job{ + job: batch.Job{ ObjectMeta: api.ObjectMeta{ ResourceVersion: "bar", }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Parallelism: &twenty, }, }, diff --git a/pkg/kubectl/stop.go b/pkg/kubectl/stop.go index 9112decf7e..e7f21f71af 100644 --- a/pkg/kubectl/stop.go +++ b/pkg/kubectl/stop.go @@ -314,9 +314,9 @@ func (reaper *DaemonSetReaper) Stop(namespace, name string, timeout time.Duratio } func (reaper *JobReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error { - jobs := reaper.Extensions().Jobs(namespace) + jobs := reaper.Batch().Jobs(namespace) pods := reaper.Pods(namespace) - scaler, err := ScalerFor(extensions.Kind("Job"), *reaper) + scaler, err := ScalerFor(batch.Kind("Job"), *reaper) if err != nil { return err } diff --git a/pkg/kubectl/stop_test.go b/pkg/kubectl/stop_test.go index efa8f2b05e..1596b3b970 100644 --- a/pkg/kubectl/stop_test.go +++ b/pkg/kubectl/stop_test.go @@ -25,6 +25,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/testclient" @@ -388,26 +389,26 @@ func TestJobStop(t *testing.T) { { Name: "OnlyOneJob", Objs: []runtime.Object{ - &extensions.Job{ // GET + &batch.Job{ // GET ObjectMeta: api.ObjectMeta{ Name: name, Namespace: ns, }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Parallelism: &zero, Selector: &unversioned.LabelSelector{ MatchLabels: map[string]string{"k1": "v1"}, }, }, }, - &extensions.JobList{ // LIST - Items: []extensions.Job{ + &batch.JobList{ // LIST + Items: []batch.Job{ { ObjectMeta: api.ObjectMeta{ Name: name, Namespace: ns, }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Parallelism: &zero, Selector: &unversioned.LabelSelector{ MatchLabels: map[string]string{"k1": "v1"}, @@ -424,26 +425,26 @@ func TestJobStop(t *testing.T) { { Name: "JobWithDeadPods", Objs: []runtime.Object{ - &extensions.Job{ // GET + &batch.Job{ // GET ObjectMeta: api.ObjectMeta{ Name: name, Namespace: ns, }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Parallelism: &zero, Selector: &unversioned.LabelSelector{ MatchLabels: map[string]string{"k1": "v1"}, }, }, }, - &extensions.JobList{ // LIST - Items: []extensions.Job{ + &batch.JobList{ // LIST + Items: []batch.Job{ { ObjectMeta: api.ObjectMeta{ Name: name, Namespace: ns, }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Parallelism: &zero, Selector: &unversioned.LabelSelector{ MatchLabels: map[string]string{"k1": "v1"}, diff --git a/pkg/registry/job/etcd/etcd.go b/pkg/registry/job/etcd/etcd.go index 8f8f6a5568..60311c69d2 100644 --- a/pkg/registry/job/etcd/etcd.go +++ b/pkg/registry/job/etcd/etcd.go @@ -18,7 +18,7 @@ package etcd import ( "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/registry/cachesize" @@ -37,12 +37,12 @@ type REST struct { func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { prefix := "/jobs" - newListFunc := func() runtime.Object { return &extensions.JobList{} } + newListFunc := func() runtime.Object { return &batch.JobList{} } storageInterface := opts.Decorator( - opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.Jobs), &extensions.Job{}, prefix, job.Strategy, newListFunc) + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.Jobs), &batch.Job{}, prefix, job.Strategy, newListFunc) store := &etcdgeneric.Etcd{ - NewFunc: func() runtime.Object { return &extensions.Job{} }, + NewFunc: func() runtime.Object { return &batch.Job{} }, // NewListFunc returns an object capable of storing results of an etcd list. NewListFunc: newListFunc, @@ -58,13 +58,13 @@ func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { }, // Retrieve the name field of a job ObjectNameFunc: func(obj runtime.Object) (string, error) { - return obj.(*extensions.Job).Name, nil + return obj.(*batch.Job).Name, nil }, // Used to match objects based on labels/fields for list and watch PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return job.MatchJob(label, field) }, - QualifiedResource: extensions.Resource("jobs"), + QualifiedResource: batch.Resource("jobs"), DeleteCollectionWorkers: opts.DeleteCollectionWorkers, // Used to validate job creation @@ -89,7 +89,7 @@ type StatusREST struct { } func (r *StatusREST) New() runtime.Object { - return &extensions.Job{} + return &batch.Job{} } // Update alters the status subset of an object. diff --git a/pkg/registry/job/etcd/etcd_test.go b/pkg/registry/job/etcd/etcd_test.go index fae531dc55..a58bc4a976 100644 --- a/pkg/registry/job/etcd/etcd_test.go +++ b/pkg/registry/job/etcd/etcd_test.go @@ -21,9 +21,8 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" - // Ensure that extensions/v1beta1 package is initialized. - _ "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/registry/generic" @@ -39,15 +38,15 @@ func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer) return jobStorage, statusStorage, server } -func validNewJob() *extensions.Job { +func validNewJob() *batch.Job { completions := 1 parallelism := 1 - return &extensions.Job{ + return &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "foo", Namespace: "default", }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Completions: &completions, Parallelism: ¶llelism, Selector: &unversioned.LabelSelector{ @@ -84,8 +83,8 @@ func TestCreate(t *testing.T) { // valid validJob, // invalid (empty selector) - &extensions.Job{ - Spec: extensions.JobSpec{ + &batch.Job{ + Spec: batch.JobSpec{ Completions: validJob.Spec.Completions, Selector: &unversioned.LabelSelector{}, Template: validJob.Spec.Template, @@ -104,18 +103,18 @@ func TestUpdate(t *testing.T) { validNewJob(), // updateFunc func(obj runtime.Object) runtime.Object { - object := obj.(*extensions.Job) + object := obj.(*batch.Job) object.Spec.Parallelism = &two return object }, // invalid updateFunc func(obj runtime.Object) runtime.Object { - object := obj.(*extensions.Job) + object := obj.(*batch.Job) object.Spec.Selector = &unversioned.LabelSelector{} return object }, func(obj runtime.Object) runtime.Object { - object := obj.(*extensions.Job) + object := obj.(*batch.Job) object.Spec.Completions = &two return object }, diff --git a/pkg/registry/job/strategy.go b/pkg/registry/job/strategy.go index aef9eebf20..2a445bf652 100644 --- a/pkg/registry/job/strategy.go +++ b/pkg/registry/job/strategy.go @@ -22,8 +22,8 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" - "k8s.io/kubernetes/pkg/apis/extensions" - "k8s.io/kubernetes/pkg/apis/extensions/validation" + "k8s.io/kubernetes/pkg/apis/batch" + "k8s.io/kubernetes/pkg/apis/batch/validation" "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/registry/generic" @@ -47,20 +47,20 @@ func (jobStrategy) NamespaceScoped() bool { // PrepareForCreate clears the status of a job before creation. func (jobStrategy) PrepareForCreate(obj runtime.Object) { - job := obj.(*extensions.Job) - job.Status = extensions.JobStatus{} + job := obj.(*batch.Job) + job.Status = batch.JobStatus{} } // PrepareForUpdate clears fields that are not allowed to be set by end users on update. func (jobStrategy) PrepareForUpdate(obj, old runtime.Object) { - newJob := obj.(*extensions.Job) - oldJob := old.(*extensions.Job) + newJob := obj.(*batch.Job) + oldJob := old.(*batch.Job) newJob.Status = oldJob.Status } // Validate validates a new job. func (jobStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { - job := obj.(*extensions.Job) + job := obj.(*batch.Job) // TODO: move UID generation earlier and do this in defaulting logic? if job.Spec.ManualSelector == nil || *job.Spec.ManualSelector == false { generateSelector(job) @@ -71,7 +71,7 @@ func (jobStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList // generateSelector adds a selector to a job and labels to its template // which can be used to uniquely identify the pods created by that job, // if the user has requested this behavior. -func generateSelector(obj *extensions.Job) { +func generateSelector(obj *batch.Job) { if obj.Spec.Template.Labels == nil { obj.Spec.Template.Labels = make(map[string]string) } @@ -133,8 +133,8 @@ func (jobStrategy) AllowCreateOnUpdate() bool { // ValidateUpdate is the default update validation for an end user. func (jobStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { - validationErrorList := validation.ValidateJob(obj.(*extensions.Job)) - updateErrorList := validation.ValidateJobUpdate(obj.(*extensions.Job), old.(*extensions.Job)) + validationErrorList := validation.ValidateJob(obj.(*batch.Job)) + updateErrorList := validation.ValidateJobUpdate(obj.(*batch.Job), old.(*batch.Job)) return append(validationErrorList, updateErrorList...) } @@ -145,17 +145,17 @@ type jobStatusStrategy struct { var StatusStrategy = jobStatusStrategy{Strategy} func (jobStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { - newJob := obj.(*extensions.Job) - oldJob := old.(*extensions.Job) + newJob := obj.(*batch.Job) + oldJob := old.(*batch.Job) newJob.Spec = oldJob.Spec } func (jobStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { - return validation.ValidateJobUpdateStatus(obj.(*extensions.Job), old.(*extensions.Job)) + return validation.ValidateJobUpdateStatus(obj.(*batch.Job), old.(*batch.Job)) } // JobSelectableFields returns a field set that represents the object for matching purposes. -func JobToSelectableFields(job *extensions.Job) fields.Set { +func JobToSelectableFields(job *batch.Job) fields.Set { objectMetaFieldsSet := generic.ObjectMetaFieldsSet(job.ObjectMeta, true) specificFieldsSet := fields.Set{ "status.successful": strconv.Itoa(job.Status.Succeeded), @@ -171,7 +171,7 @@ func MatchJob(label labels.Selector, field fields.Selector) generic.Matcher { Label: label, Field: field, GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { - job, ok := obj.(*extensions.Job) + job, ok := obj.(*batch.Job) if !ok { return nil, nil, fmt.Errorf("Given object is not a job.") } diff --git a/pkg/registry/job/strategy_test.go b/pkg/registry/job/strategy_test.go index 13eb6c5827..c42cc319bc 100644 --- a/pkg/registry/job/strategy_test.go +++ b/pkg/registry/job/strategy_test.go @@ -24,7 +24,7 @@ import ( "k8s.io/kubernetes/pkg/api/testapi" apitesting "k8s.io/kubernetes/pkg/api/testing" "k8s.io/kubernetes/pkg/api/unversioned" - "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/types" ) @@ -57,17 +57,17 @@ func TestJobStrategy(t *testing.T) { Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, }, } - job := &extensions.Job{ + job := &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "myjob", Namespace: api.NamespaceDefault, }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, ManualSelector: newBool(true), }, - Status: extensions.JobStatus{ + Status: batch.JobStatus{ Active: 11, }, } @@ -81,12 +81,12 @@ func TestJobStrategy(t *testing.T) { t.Errorf("Unexpected error validating %v", errs) } parallelism := 10 - updatedJob := &extensions.Job{ + updatedJob := &batch.Job{ ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "4"}, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Parallelism: ¶llelism, }, - Status: extensions.JobStatus{ + Status: batch.JobStatus{ Active: 11, }, } @@ -114,13 +114,13 @@ func TestJobStrategyWithGeneration(t *testing.T) { Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, }, } - job := &extensions.Job{ + job := &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "myjob2", Namespace: api.NamespaceDefault, UID: theUID, }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Selector: nil, Template: validPodTemplateSpec, }, @@ -175,33 +175,33 @@ func TestJobStatusStrategy(t *testing.T) { } oldParallelism := 10 newParallelism := 11 - oldJob := &extensions.Job{ + oldJob := &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "myjob", Namespace: api.NamespaceDefault, ResourceVersion: "10", }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, Parallelism: &oldParallelism, }, - Status: extensions.JobStatus{ + Status: batch.JobStatus{ Active: 11, }, } - newJob := &extensions.Job{ + newJob := &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "myjob", Namespace: api.NamespaceDefault, ResourceVersion: "9", }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, Parallelism: &newParallelism, }, - Status: extensions.JobStatus{ + Status: batch.JobStatus{ Active: 12, }, } @@ -226,7 +226,7 @@ func TestSelectableFieldLabelConversions(t *testing.T) { apitesting.TestSelectableFieldLabelConversionsOfKind(t, testapi.Extensions.GroupVersion().String(), "Job", - labels.Set(JobToSelectableFields(&extensions.Job{})), + labels.Set(JobToSelectableFields(&batch.Job{})), nil, ) } diff --git a/test/e2e/batch_v1_jobs.go b/test/e2e/batch_v1_jobs.go index b09e611e28..a28be96e8b 100644 --- a/test/e2e/batch_v1_jobs.go +++ b/test/e2e/batch_v1_jobs.go @@ -25,7 +25,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" - "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/batch" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/labels" @@ -128,7 +128,7 @@ var _ = framework.KubeDescribe("V1Job", func() { Expect(err).NotTo(HaveOccurred()) By("scale job up") - scaler, err := kubectl.ScalerFor(extensions.Kind("Job"), f.Client) + scaler, err := kubectl.ScalerFor(batch.Kind("Job"), f.Client) Expect(err).NotTo(HaveOccurred()) waitForScale := kubectl.NewRetryParams(5*time.Second, 1*time.Minute) waitForReplicas := kubectl.NewRetryParams(5*time.Second, 5*time.Minute) @@ -153,7 +153,7 @@ var _ = framework.KubeDescribe("V1Job", func() { Expect(err).NotTo(HaveOccurred()) By("scale job down") - scaler, err := kubectl.ScalerFor(extensions.Kind("Job"), f.Client) + scaler, err := kubectl.ScalerFor(batch.Kind("Job"), f.Client) Expect(err).NotTo(HaveOccurred()) waitForScale := kubectl.NewRetryParams(5*time.Second, 1*time.Minute) waitForReplicas := kubectl.NewRetryParams(5*time.Second, 5*time.Minute) @@ -176,7 +176,7 @@ var _ = framework.KubeDescribe("V1Job", func() { Expect(err).NotTo(HaveOccurred()) By("delete a job") - reaper, err := kubectl.ReaperFor(extensions.Kind("Job"), f.Client) + reaper, err := kubectl.ReaperFor(batch.Kind("Job"), f.Client) Expect(err).NotTo(HaveOccurred()) timeout := 1 * time.Minute err = reaper.Stop(f.Namespace.Name, job.Name, timeout, api.NewDeleteOptions(0)) @@ -203,12 +203,12 @@ var _ = framework.KubeDescribe("V1Job", func() { }) // newTestV1Job returns a job which does one of several testing behaviors. -func newTestV1Job(behavior, name string, rPol api.RestartPolicy, parallelism, completions int) *extensions.Job { - job := &extensions.Job{ +func newTestV1Job(behavior, name string, rPol api.RestartPolicy, parallelism, completions int) *batch.Job { + job := &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: name, }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Parallelism: ¶llelism, Completions: &completions, Template: api.PodTemplateSpec{ @@ -264,7 +264,7 @@ func newTestV1Job(behavior, name string, rPol api.RestartPolicy, parallelism, co return job } -func createV1Job(c *client.Client, ns string, job *extensions.Job) (*extensions.Job, error) { +func createV1Job(c *client.Client, ns string, job *batch.Job) (*batch.Job, error) { return c.Batch().Jobs(ns).Create(job) } @@ -310,7 +310,7 @@ func waitForV1JobFail(c *client.Client, ns, jobName string) error { return false, err } for _, c := range curr.Status.Conditions { - if c.Type == extensions.JobFailed && c.Status == api.ConditionTrue { + if c.Type == batch.JobFailed && c.Status == api.ConditionTrue { return true, nil } } diff --git a/test/e2e/job.go b/test/e2e/job.go index 80c2510f22..05e03b7280 100644 --- a/test/e2e/job.go +++ b/test/e2e/job.go @@ -21,7 +21,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" - "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/batch" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/labels" @@ -124,7 +124,7 @@ var _ = framework.KubeDescribe("Job", func() { Expect(err).NotTo(HaveOccurred()) By("scale job up") - scaler, err := kubectl.ScalerFor(extensions.Kind("Job"), f.Client) + scaler, err := kubectl.ScalerFor(batch.Kind("Job"), f.Client) Expect(err).NotTo(HaveOccurred()) waitForScale := kubectl.NewRetryParams(5*time.Second, 1*time.Minute) waitForReplicas := kubectl.NewRetryParams(5*time.Second, 5*time.Minute) @@ -149,7 +149,7 @@ var _ = framework.KubeDescribe("Job", func() { Expect(err).NotTo(HaveOccurred()) By("scale job down") - scaler, err := kubectl.ScalerFor(extensions.Kind("Job"), f.Client) + scaler, err := kubectl.ScalerFor(batch.Kind("Job"), f.Client) Expect(err).NotTo(HaveOccurred()) waitForScale := kubectl.NewRetryParams(5*time.Second, 1*time.Minute) waitForReplicas := kubectl.NewRetryParams(5*time.Second, 5*time.Minute) @@ -172,7 +172,7 @@ var _ = framework.KubeDescribe("Job", func() { Expect(err).NotTo(HaveOccurred()) By("delete a job") - reaper, err := kubectl.ReaperFor(extensions.Kind("Job"), f.Client) + reaper, err := kubectl.ReaperFor(batch.Kind("Job"), f.Client) Expect(err).NotTo(HaveOccurred()) timeout := 1 * time.Minute err = reaper.Stop(f.Namespace.Name, job.Name, timeout, api.NewDeleteOptions(0)) @@ -199,12 +199,12 @@ var _ = framework.KubeDescribe("Job", func() { }) // newTestJob returns a job which does one of several testing behaviors. -func newTestJob(behavior, name string, rPol api.RestartPolicy, parallelism, completions int) *extensions.Job { - job := &extensions.Job{ +func newTestJob(behavior, name string, rPol api.RestartPolicy, parallelism, completions int) *batch.Job { + job := &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: name, }, - Spec: extensions.JobSpec{ + Spec: batch.JobSpec{ Parallelism: ¶llelism, Completions: &completions, ManualSelector: newBool(true), @@ -261,7 +261,7 @@ func newTestJob(behavior, name string, rPol api.RestartPolicy, parallelism, comp return job } -func createJob(c *client.Client, ns string, job *extensions.Job) (*extensions.Job, error) { +func createJob(c *client.Client, ns string, job *batch.Job) (*batch.Job, error) { return c.Extensions().Jobs(ns).Create(job) } @@ -307,7 +307,7 @@ func waitForJobFail(c *client.Client, ns, jobName string) error { return false, err } for _, c := range curr.Status.Conditions { - if c.Type == extensions.JobFailed && c.Status == api.ConditionTrue { + if c.Type == batch.JobFailed && c.Status == api.ConditionTrue { return true, nil } }