mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-07 11:13:48 +00:00
Merge pull request #66840 from janetkuo/job-ttl
Automatic merge from submit-queue (batch tested with PRs 66840, 68159). If you want to cherry-pick this change to another branch, please follow the instructions here: https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md. TTL for cleaning up Jobs after they finish **What this PR does / why we need it**: https://github.com/kubernetes/features/issues/592 **Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: Fixes #64470 For https://github.com/kubernetes/features/issues/592 **Special notes for your reviewer**: @kubernetes/sig-apps-pr-reviews **Release note**: ```release-note Add a TTL machenism to clean up Jobs after they finish. ```
This commit is contained in:
commit
c50a347124
@ -126,6 +126,7 @@ API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,K
|
||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,ResourceQuotaController
|
||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,SAController
|
||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,ServiceController
|
||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,TTLAfterFinishedController
|
||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,NamespaceControllerConfiguration,NamespaceSyncPeriod
|
||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,NamespaceControllerConfiguration,ConcurrentNamespaceSyncs
|
||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,NodeIPAMControllerConfiguration,ServiceCIDR
|
||||
@ -156,6 +157,7 @@ API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,S
|
||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,SAControllerConfiguration,ConcurrentSATokenSyncs
|
||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,SAControllerConfiguration,RootCAFile
|
||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,ServiceControllerConfiguration,ConcurrentServiceSyncs
|
||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,TTLAfterFinishedControllerConfiguration,ConcurrentTTLSyncs
|
||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,VolumeConfiguration,EnableHostPathProvisioning
|
||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,VolumeConfiguration,EnableDynamicProvisioning
|
||||
API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,VolumeConfiguration,PersistentVolumeRecyclerConfiguration
|
||||
|
5
api/openapi-spec/swagger.json
generated
5
api/openapi-spec/swagger.json
generated
@ -79306,6 +79306,11 @@
|
||||
"template": {
|
||||
"description": "Describes the pod that will be created when executing a job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/",
|
||||
"$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec"
|
||||
},
|
||||
"ttlSecondsAfterFinished": {
|
||||
"description": "ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won't be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes. This field is alpha-level and is only honored by servers that enable the TTLAfterFinished feature.",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
5
api/swagger-spec/batch_v1.json
generated
5
api/swagger-spec/batch_v1.json
generated
@ -1536,6 +1536,11 @@
|
||||
"template": {
|
||||
"$ref": "v1.PodTemplateSpec",
|
||||
"description": "Describes the pod that will be created when executing a job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/"
|
||||
},
|
||||
"ttlSecondsAfterFinished": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won't be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes. This field is alpha-level and is only honored by servers that enable the TTLAfterFinished feature."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
5
api/swagger-spec/batch_v1beta1.json
generated
5
api/swagger-spec/batch_v1beta1.json
generated
@ -1591,6 +1591,11 @@
|
||||
"template": {
|
||||
"$ref": "v1.PodTemplateSpec",
|
||||
"description": "Describes the pod that will be created when executing a job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/"
|
||||
},
|
||||
"ttlSecondsAfterFinished": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won't be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes. This field is alpha-level and is only honored by servers that enable the TTLAfterFinished feature."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
5
api/swagger-spec/batch_v2alpha1.json
generated
5
api/swagger-spec/batch_v2alpha1.json
generated
@ -1591,6 +1591,11 @@
|
||||
"template": {
|
||||
"$ref": "v1.PodTemplateSpec",
|
||||
"description": "Describes the pod that will be created when executing a job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/"
|
||||
},
|
||||
"ttlSecondsAfterFinished": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won't be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes. This field is alpha-level and is only honored by servers that enable the TTLAfterFinished feature."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -68,6 +68,7 @@ go_library(
|
||||
"//pkg/controller/serviceaccount:go_default_library",
|
||||
"//pkg/controller/statefulset:go_default_library",
|
||||
"//pkg/controller/ttl:go_default_library",
|
||||
"//pkg/controller/ttlafterfinished:go_default_library",
|
||||
"//pkg/controller/volume/attachdetach:go_default_library",
|
||||
"//pkg/controller/volume/expand:go_default_library",
|
||||
"//pkg/controller/volume/persistentvolume:go_default_library",
|
||||
|
@ -378,6 +378,7 @@ func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc
|
||||
controllers["clusterrole-aggregation"] = startClusterRoleAggregrationController
|
||||
controllers["pvc-protection"] = startPVCProtectionController
|
||||
controllers["pv-protection"] = startPVProtectionController
|
||||
controllers["ttl-after-finished"] = startTTLAfterFinishedController
|
||||
|
||||
return controllers
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ import (
|
||||
servicecontroller "k8s.io/kubernetes/pkg/controller/service"
|
||||
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
|
||||
ttlcontroller "k8s.io/kubernetes/pkg/controller/ttl"
|
||||
"k8s.io/kubernetes/pkg/controller/ttlafterfinished"
|
||||
"k8s.io/kubernetes/pkg/controller/volume/attachdetach"
|
||||
"k8s.io/kubernetes/pkg/controller/volume/expand"
|
||||
persistentvolumecontroller "k8s.io/kubernetes/pkg/controller/volume/persistentvolume"
|
||||
@ -417,3 +418,14 @@ func startPVProtectionController(ctx ControllerContext) (http.Handler, bool, err
|
||||
).Run(1, ctx.Stop)
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
func startTTLAfterFinishedController(ctx ControllerContext) (http.Handler, bool, error) {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) {
|
||||
return nil, false, nil
|
||||
}
|
||||
go ttlafterfinished.New(
|
||||
ctx.InformerFactory.Batch().V1().Jobs(),
|
||||
ctx.ClientBuilder.ClientOrDie("ttl-after-finished-controller"),
|
||||
).Run(int(ctx.ComponentConfig.TTLAfterFinishedController.ConcurrentTTLSyncs), ctx.Stop)
|
||||
return nil, true, nil
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ go_library(
|
||||
"replicationcontroller.go",
|
||||
"resourcequotacontroller.go",
|
||||
"serviceaccountcontroller.go",
|
||||
"ttlafterfinishedcontroller.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/cmd/kube-controller-manager/app/options",
|
||||
deps = [
|
||||
|
@ -77,6 +77,7 @@ type KubeControllerManagerOptions struct {
|
||||
ReplicationController *ReplicationControllerOptions
|
||||
ResourceQuotaController *ResourceQuotaControllerOptions
|
||||
SAController *SAControllerOptions
|
||||
TTLAfterFinishedController *TTLAfterFinishedControllerOptions
|
||||
|
||||
SecureServing *apiserveroptions.SecureServingOptionsWithLoopback
|
||||
// TODO: remove insecure serving mode
|
||||
@ -172,6 +173,9 @@ func NewKubeControllerManagerOptions() (*KubeControllerManagerOptions, error) {
|
||||
ServiceController: &cmoptions.ServiceControllerOptions{
|
||||
ConcurrentServiceSyncs: componentConfig.ServiceController.ConcurrentServiceSyncs,
|
||||
},
|
||||
TTLAfterFinishedController: &TTLAfterFinishedControllerOptions{
|
||||
ConcurrentTTLSyncs: componentConfig.TTLAfterFinishedController.ConcurrentTTLSyncs,
|
||||
},
|
||||
SecureServing: apiserveroptions.NewSecureServingOptions().WithLoopback(),
|
||||
InsecureServing: (&apiserveroptions.DeprecatedInsecureServingOptions{
|
||||
BindAddress: net.ParseIP(componentConfig.Generic.Address),
|
||||
@ -251,6 +255,7 @@ func (s *KubeControllerManagerOptions) Flags(allControllers []string, disabledBy
|
||||
s.ReplicationController.AddFlags(fss.FlagSet("replicationcontroller"))
|
||||
s.ResourceQuotaController.AddFlags(fss.FlagSet("resourcequota controller"))
|
||||
s.SAController.AddFlags(fss.FlagSet("serviceaccount controller"))
|
||||
s.TTLAfterFinishedController.AddFlags(fss.FlagSet("ttl-after-finished controller"))
|
||||
|
||||
fs := fss.FlagSet("misc")
|
||||
fs.StringVar(&s.Master, "master", s.Master, "The address of the Kubernetes API server (overrides any value in kubeconfig).")
|
||||
@ -328,6 +333,9 @@ func (s *KubeControllerManagerOptions) ApplyTo(c *kubecontrollerconfig.Config) e
|
||||
if err := s.ServiceController.ApplyTo(&c.ComponentConfig.ServiceController); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.TTLAfterFinishedController.ApplyTo(&c.ComponentConfig.TTLAfterFinishedController); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.InsecureServing.ApplyTo(&c.InsecureServing, &c.LoopbackClientConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -376,6 +384,7 @@ func (s *KubeControllerManagerOptions) Validate(allControllers []string, disable
|
||||
errs = append(errs, s.ResourceQuotaController.Validate()...)
|
||||
errs = append(errs, s.SAController.Validate()...)
|
||||
errs = append(errs, s.ServiceController.Validate()...)
|
||||
errs = append(errs, s.TTLAfterFinishedController.Validate()...)
|
||||
errs = append(errs, s.SecureServing.Validate()...)
|
||||
errs = append(errs, s.InsecureServing.Validate()...)
|
||||
errs = append(errs, s.Authentication.Validate()...)
|
||||
|
@ -116,6 +116,7 @@ func TestAddFlags(t *testing.T) {
|
||||
"--cert-dir=/a/b/c",
|
||||
"--bind-address=192.168.4.21",
|
||||
"--secure-port=10001",
|
||||
"--concurrent-ttl-after-finished-syncs=8",
|
||||
}
|
||||
fs.Parse(args)
|
||||
// Sort GCIgnoredResources because it's built from a map, which means the
|
||||
@ -255,6 +256,9 @@ func TestAddFlags(t *testing.T) {
|
||||
ServiceController: &cmoptions.ServiceControllerOptions{
|
||||
ConcurrentServiceSyncs: 2,
|
||||
},
|
||||
TTLAfterFinishedController: &TTLAfterFinishedControllerOptions{
|
||||
ConcurrentTTLSyncs: 8,
|
||||
},
|
||||
SecureServing: (&apiserveroptions.SecureServingOptions{
|
||||
BindPort: 10001,
|
||||
BindAddress: net.ParseIP("192.168.4.21"),
|
||||
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
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 options
|
||||
|
||||
import (
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
kubectrlmgrconfig "k8s.io/kubernetes/pkg/controller/apis/config"
|
||||
)
|
||||
|
||||
// TTLAfterFinishedControllerOptions holds the TTLAfterFinishedController options.
|
||||
type TTLAfterFinishedControllerOptions struct {
|
||||
ConcurrentTTLSyncs int32
|
||||
}
|
||||
|
||||
// AddFlags adds flags related to TTLAfterFinishedController for controller manager to the specified FlagSet.
|
||||
func (o *TTLAfterFinishedControllerOptions) AddFlags(fs *pflag.FlagSet) {
|
||||
if o == nil {
|
||||
return
|
||||
}
|
||||
|
||||
fs.Int32Var(&o.ConcurrentTTLSyncs, "concurrent-ttl-after-finished-syncs", o.ConcurrentTTLSyncs, "The number of TTL-after-finished controller workers that are allowed to sync concurrently.")
|
||||
}
|
||||
|
||||
// ApplyTo fills up TTLAfterFinishedController config with options.
|
||||
func (o *TTLAfterFinishedControllerOptions) ApplyTo(cfg *kubectrlmgrconfig.TTLAfterFinishedControllerConfiguration) error {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg.ConcurrentTTLSyncs = o.ConcurrentTTLSyncs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks validation of TTLAfterFinishedControllerOptions.
|
||||
func (o *TTLAfterFinishedControllerOptions) Validate() []error {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
errs := []error{}
|
||||
return errs
|
||||
}
|
7
docs/api-reference/batch/v1/definitions.html
generated
7
docs/api-reference/batch/v1/definitions.html
generated
@ -2505,6 +2505,13 @@ When an object is created, the system will populate this list with the current s
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_podtemplatespec">v1.PodTemplateSpec</a></p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">ttlSecondsAfterFinished</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won’t be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes. This field is alpha-level and is only honored by servers that enable the TTLAfterFinished feature.</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">integer (int32)</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -2539,6 +2539,13 @@ When an object is created, the system will populate this list with the current s
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_podtemplatespec">v1.PodTemplateSpec</a></p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">ttlSecondsAfterFinished</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won’t be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes. This field is alpha-level and is only honored by servers that enable the TTLAfterFinished feature.</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">integer (int32)</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -2512,6 +2512,13 @@ When an object is created, the system will populate this list with the current s
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_podtemplatespec">v1.PodTemplateSpec</a></p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">ttlSecondsAfterFinished</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won’t be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes. This field is alpha-level and is only honored by servers that enable the TTLAfterFinished feature.</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">integer (int32)</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -138,6 +138,18 @@ type JobSpec struct {
|
||||
|
||||
// Describes the pod that will be created when executing a job.
|
||||
Template api.PodTemplateSpec
|
||||
|
||||
// ttlSecondsAfterFinished limits the lifetime of a Job that has finished
|
||||
// execution (either Complete or Failed). If this field is set,
|
||||
// ttlSecondsAfterFinished after the Job finishes, it is eligible to be
|
||||
// automatically deleted. When the Job is being deleted, its lifecycle
|
||||
// guarantees (e.g. finalizers) will be honored. If this field is unset,
|
||||
// the Job won't be automatically deleted. If this field is set to zero,
|
||||
// the Job becomes eligible to be deleted immediately after it finishes.
|
||||
// This field is alpha-level and is only honored by servers that enable the
|
||||
// TTLAfterFinished feature.
|
||||
// +optional
|
||||
TTLSecondsAfterFinished *int32
|
||||
}
|
||||
|
||||
// JobStatus represents the current state of a Job.
|
||||
|
@ -54,6 +54,7 @@ func Convert_batch_JobSpec_To_v1_JobSpec(in *batch.JobSpec, out *batchv1.JobSpec
|
||||
out.Completions = in.Completions
|
||||
out.ActiveDeadlineSeconds = in.ActiveDeadlineSeconds
|
||||
out.BackoffLimit = in.BackoffLimit
|
||||
out.TTLSecondsAfterFinished = in.TTLSecondsAfterFinished
|
||||
out.Selector = in.Selector
|
||||
if in.ManualSelector != nil {
|
||||
out.ManualSelector = new(bool)
|
||||
@ -73,6 +74,7 @@ func Convert_v1_JobSpec_To_batch_JobSpec(in *batchv1.JobSpec, out *batch.JobSpec
|
||||
out.Completions = in.Completions
|
||||
out.ActiveDeadlineSeconds = in.ActiveDeadlineSeconds
|
||||
out.BackoffLimit = in.BackoffLimit
|
||||
out.TTLSecondsAfterFinished = in.TTLSecondsAfterFinished
|
||||
out.Selector = in.Selector
|
||||
if in.ManualSelector != nil {
|
||||
out.ManualSelector = new(bool)
|
||||
|
2
pkg/apis/batch/v1/zz_generated.conversion.go
generated
2
pkg/apis/batch/v1/zz_generated.conversion.go
generated
@ -217,6 +217,7 @@ func autoConvert_v1_JobSpec_To_batch_JobSpec(in *v1.JobSpec, out *batch.JobSpec,
|
||||
if err := apiscorev1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil {
|
||||
return err
|
||||
}
|
||||
out.TTLSecondsAfterFinished = (*int32)(unsafe.Pointer(in.TTLSecondsAfterFinished))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -230,6 +231,7 @@ func autoConvert_batch_JobSpec_To_v1_JobSpec(in *batch.JobSpec, out *v1.JobSpec,
|
||||
if err := apiscorev1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil {
|
||||
return err
|
||||
}
|
||||
out.TTLSecondsAfterFinished = (*int32)(unsafe.Pointer(in.TTLSecondsAfterFinished))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -14,11 +14,13 @@ go_library(
|
||||
"//pkg/apis/batch:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/core/validation:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
"//vendor/github.com/robfig/cron:go_default_library",
|
||||
],
|
||||
)
|
||||
@ -30,8 +32,10 @@ go_test(
|
||||
deps = [
|
||||
"//pkg/apis/batch:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -24,9 +24,11 @@ import (
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
apimachineryvalidation "k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/apis/batch"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
// TODO: generalize for other controller objects that will follow the same pattern, such as ReplicaSet and DaemonSet, and
|
||||
@ -117,6 +119,14 @@ func validateJobSpec(spec *batch.JobSpec, fldPath *field.Path) field.ErrorList {
|
||||
if spec.BackoffLimit != nil {
|
||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.BackoffLimit), fldPath.Child("backoffLimit"))...)
|
||||
}
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) {
|
||||
// normal validation for TTLSecondsAfterFinished
|
||||
if spec.TTLSecondsAfterFinished != nil {
|
||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.TTLSecondsAfterFinished), fldPath.Child("ttlSecondsAfterFinished"))...)
|
||||
}
|
||||
} else if spec.TTLSecondsAfterFinished != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("ttlSecondsAfterFinished"), "disabled by feature-gate"))
|
||||
}
|
||||
|
||||
allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template, fldPath.Child("template"))...)
|
||||
if spec.Template.Spec.RestartPolicy != api.RestartPolicyOnFailure &&
|
||||
|
@ -17,13 +17,16 @@ limitations under the License.
|
||||
package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/apis/batch"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
func getValidManualSelector() *metav1.LabelSelector {
|
||||
@ -64,7 +67,21 @@ func getValidPodTemplateSpecForGenerated(selector *metav1.LabelSelector) api.Pod
|
||||
}
|
||||
}
|
||||
|
||||
func featureToggle(feature utilfeature.Feature) []string {
|
||||
enabled := fmt.Sprintf("%s=%t", feature, true)
|
||||
disabled := fmt.Sprintf("%s=%t", feature, false)
|
||||
return []string{enabled, disabled}
|
||||
}
|
||||
|
||||
func TestValidateJob(t *testing.T) {
|
||||
ttlEnabled := utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished)
|
||||
defer func() {
|
||||
err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=%t", features.TTLAfterFinished, ttlEnabled))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to set feature gate for %s: %v", features.TTLAfterFinished, err)
|
||||
}
|
||||
}()
|
||||
|
||||
validManualSelector := getValidManualSelector()
|
||||
validPodTemplateSpecForManual := getValidPodTemplateSpecForManual(validManualSelector)
|
||||
validGeneratedSelector := getValidGeneratedSelector()
|
||||
@ -214,15 +231,39 @@ func TestValidateJob(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range errorCases {
|
||||
errs := ValidateJob(&v)
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("expected failure for %s", k)
|
||||
for _, setFeature := range featureToggle(features.TTLAfterFinished) {
|
||||
// Set error cases based on if TTLAfterFinished feature is enabled or not
|
||||
if err := utilfeature.DefaultFeatureGate.Set(setFeature); err != nil {
|
||||
t.Fatalf("Failed to set feature gate for %s: %v", features.TTLAfterFinished, err)
|
||||
}
|
||||
ttlCase := "spec.ttlSecondsAfterFinished:must be greater than or equal to 0"
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) {
|
||||
errorCases[ttlCase] = batch.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "myjob",
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
UID: types.UID("1a2b3c"),
|
||||
},
|
||||
Spec: batch.JobSpec{
|
||||
TTLSecondsAfterFinished: &negative,
|
||||
Selector: validGeneratedSelector,
|
||||
Template: validPodTemplateSpecForGenerated,
|
||||
},
|
||||
}
|
||||
} 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)
|
||||
delete(errorCases, ttlCase)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -584,6 +625,25 @@ func TestValidateCronJob(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) {
|
||||
errorCases["spec.jobTemplate.spec.ttlSecondsAfterFinished:must be greater than or equal to 0"] = batch.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mycronjob",
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
UID: types.UID("1a2b3c"),
|
||||
},
|
||||
Spec: batch.CronJobSpec{
|
||||
Schedule: "* * * * ?",
|
||||
ConcurrencyPolicy: batch.AllowConcurrent,
|
||||
JobTemplate: batch.JobTemplateSpec{
|
||||
Spec: batch.JobSpec{
|
||||
TTLSecondsAfterFinished: &negative,
|
||||
Template: validPodTemplateSpec,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range errorCases {
|
||||
errs := ValidateCronJob(&v)
|
||||
|
5
pkg/apis/batch/zz_generated.deepcopy.go
generated
5
pkg/apis/batch/zz_generated.deepcopy.go
generated
@ -262,6 +262,11 @@ func (in *JobSpec) DeepCopyInto(out *JobSpec) {
|
||||
**out = **in
|
||||
}
|
||||
in.Template.DeepCopyInto(&out.Template)
|
||||
if in.TTLSecondsAfterFinished != nil {
|
||||
in, out := &in.TTLSecondsAfterFinished, &out.TTLSecondsAfterFinished
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -131,6 +131,7 @@ filegroup(
|
||||
"//pkg/controller/statefulset:all-srcs",
|
||||
"//pkg/controller/testutil:all-srcs",
|
||||
"//pkg/controller/ttl:all-srcs",
|
||||
"//pkg/controller/ttlafterfinished:all-srcs",
|
||||
"//pkg/controller/util/node:all-srcs",
|
||||
"//pkg/controller/volume/attachdetach:all-srcs",
|
||||
"//pkg/controller/volume/events:all-srcs",
|
||||
|
@ -96,6 +96,9 @@ type KubeControllerManagerConfiguration struct {
|
||||
// ServiceControllerConfiguration holds configuration for ServiceController
|
||||
// related features.
|
||||
ServiceController ServiceControllerConfiguration
|
||||
// TTLAfterFinishedControllerConfiguration holds configuration for
|
||||
// TTLAfterFinishedController related features.
|
||||
TTLAfterFinishedController TTLAfterFinishedControllerConfiguration
|
||||
}
|
||||
|
||||
// GenericControllerManagerConfiguration holds configuration for a generic controller-manager
|
||||
@ -438,3 +441,10 @@ type PersistentVolumeRecyclerConfiguration struct {
|
||||
// in a multi-node cluster.
|
||||
IncrementTimeoutHostPath int32
|
||||
}
|
||||
|
||||
// TTLAfterFinishedControllerConfiguration contains elements describing TTLAfterFinishedController.
|
||||
type TTLAfterFinishedControllerConfiguration struct {
|
||||
// concurrentTTLSyncs is the number of TTL-after-finished collector workers that are
|
||||
// allowed to sync concurrently.
|
||||
ConcurrentTTLSyncs int32
|
||||
}
|
||||
|
@ -48,6 +48,9 @@ func SetDefaults_KubeControllerManagerConfiguration(obj *kubectrlmgrconfigv1alph
|
||||
if obj.SAController.ConcurrentSATokenSyncs == 0 {
|
||||
obj.SAController.ConcurrentSATokenSyncs = 5
|
||||
}
|
||||
if obj.TTLAfterFinishedController.ConcurrentTTLSyncs <= 0 {
|
||||
obj.TTLAfterFinishedController.ConcurrentTTLSyncs = 5
|
||||
}
|
||||
|
||||
// These defaults override the recommended defaults from the apimachineryconfigv1alpha1 package that are applied automatically
|
||||
// These client-connection defaults are specific to the kube-controller-manager
|
||||
|
@ -289,6 +289,16 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*v1alpha1.TTLAfterFinishedControllerConfiguration)(nil), (*config.TTLAfterFinishedControllerConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_TTLAfterFinishedControllerConfiguration_To_config_TTLAfterFinishedControllerConfiguration(a.(*v1alpha1.TTLAfterFinishedControllerConfiguration), b.(*config.TTLAfterFinishedControllerConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.TTLAfterFinishedControllerConfiguration)(nil), (*v1alpha1.TTLAfterFinishedControllerConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_TTLAfterFinishedControllerConfiguration_To_v1alpha1_TTLAfterFinishedControllerConfiguration(a.(*config.TTLAfterFinishedControllerConfiguration), b.(*v1alpha1.TTLAfterFinishedControllerConfiguration), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*v1alpha1.VolumeConfiguration)(nil), (*config.VolumeConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_VolumeConfiguration_To_config_VolumeConfiguration(a.(*v1alpha1.VolumeConfiguration), b.(*config.VolumeConfiguration), scope)
|
||||
}); err != nil {
|
||||
@ -734,6 +744,9 @@ func autoConvert_v1alpha1_KubeControllerManagerConfiguration_To_config_KubeContr
|
||||
if err := Convert_v1alpha1_ServiceControllerConfiguration_To_config_ServiceControllerConfiguration(&in.ServiceController, &out.ServiceController, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Convert_v1alpha1_TTLAfterFinishedControllerConfiguration_To_config_TTLAfterFinishedControllerConfiguration(&in.TTLAfterFinishedController, &out.TTLAfterFinishedController, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -806,6 +819,9 @@ func autoConvert_config_KubeControllerManagerConfiguration_To_v1alpha1_KubeContr
|
||||
if err := Convert_config_ServiceControllerConfiguration_To_v1alpha1_ServiceControllerConfiguration(&in.ServiceController, &out.ServiceController, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Convert_config_TTLAfterFinishedControllerConfiguration_To_v1alpha1_TTLAfterFinishedControllerConfiguration(&in.TTLAfterFinishedController, &out.TTLAfterFinishedController, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1070,6 +1086,26 @@ func autoConvert_config_ServiceControllerConfiguration_To_v1alpha1_ServiceContro
|
||||
return nil
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_TTLAfterFinishedControllerConfiguration_To_config_TTLAfterFinishedControllerConfiguration(in *v1alpha1.TTLAfterFinishedControllerConfiguration, out *config.TTLAfterFinishedControllerConfiguration, s conversion.Scope) error {
|
||||
out.ConcurrentTTLSyncs = in.ConcurrentTTLSyncs
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_TTLAfterFinishedControllerConfiguration_To_config_TTLAfterFinishedControllerConfiguration is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_TTLAfterFinishedControllerConfiguration_To_config_TTLAfterFinishedControllerConfiguration(in *v1alpha1.TTLAfterFinishedControllerConfiguration, out *config.TTLAfterFinishedControllerConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_TTLAfterFinishedControllerConfiguration_To_config_TTLAfterFinishedControllerConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_TTLAfterFinishedControllerConfiguration_To_v1alpha1_TTLAfterFinishedControllerConfiguration(in *config.TTLAfterFinishedControllerConfiguration, out *v1alpha1.TTLAfterFinishedControllerConfiguration, s conversion.Scope) error {
|
||||
out.ConcurrentTTLSyncs = in.ConcurrentTTLSyncs
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_TTLAfterFinishedControllerConfiguration_To_v1alpha1_TTLAfterFinishedControllerConfiguration is an autogenerated conversion function.
|
||||
func Convert_config_TTLAfterFinishedControllerConfiguration_To_v1alpha1_TTLAfterFinishedControllerConfiguration(in *config.TTLAfterFinishedControllerConfiguration, out *v1alpha1.TTLAfterFinishedControllerConfiguration, s conversion.Scope) error {
|
||||
return autoConvert_config_TTLAfterFinishedControllerConfiguration_To_v1alpha1_TTLAfterFinishedControllerConfiguration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_VolumeConfiguration_To_config_VolumeConfiguration(in *v1alpha1.VolumeConfiguration, out *config.VolumeConfiguration, s conversion.Scope) error {
|
||||
if err := v1.Convert_Pointer_bool_To_bool(&in.EnableHostPathProvisioning, &out.EnableHostPathProvisioning, s); err != nil {
|
||||
return err
|
||||
|
17
pkg/controller/apis/config/zz_generated.deepcopy.go
generated
17
pkg/controller/apis/config/zz_generated.deepcopy.go
generated
@ -285,6 +285,7 @@ func (in *KubeControllerManagerConfiguration) DeepCopyInto(out *KubeControllerMa
|
||||
out.ResourceQuotaController = in.ResourceQuotaController
|
||||
out.SAController = in.SAController
|
||||
out.ServiceController = in.ServiceController
|
||||
out.TTLAfterFinishedController = in.TTLAfterFinishedController
|
||||
return
|
||||
}
|
||||
|
||||
@ -489,6 +490,22 @@ func (in *ServiceControllerConfiguration) DeepCopy() *ServiceControllerConfigura
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TTLAfterFinishedControllerConfiguration) DeepCopyInto(out *TTLAfterFinishedControllerConfiguration) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TTLAfterFinishedControllerConfiguration.
|
||||
func (in *TTLAfterFinishedControllerConfiguration) DeepCopy() *TTLAfterFinishedControllerConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TTLAfterFinishedControllerConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *VolumeConfiguration) DeepCopyInto(out *VolumeConfiguration) {
|
||||
*out = *in
|
||||
|
54
pkg/controller/ttlafterfinished/BUILD
Normal file
54
pkg/controller/ttlafterfinished/BUILD
Normal file
@ -0,0 +1,54 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["ttlafterfinished_controller.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/controller/ttlafterfinished",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/controller/job:go_default_library",
|
||||
"//pkg/kubectl/scheme:go_default_library",
|
||||
"//pkg/util/metrics:go_default_library",
|
||||
"//staging/src/k8s.io/api/batch/v1:go_default_library",
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/clock:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/informers/batch/v1:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/listers/batch/v1:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/tools/record:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/util/workqueue:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["ttlafterfinished_controller_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//staging/src/k8s.io/api/batch/v1:go_default_library",
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
308
pkg/controller/ttlafterfinished/ttlafterfinished_controller.go
Normal file
308
pkg/controller/ttlafterfinished/ttlafterfinished_controller.go
Normal file
@ -0,0 +1,308 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
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 ttlafterfinished
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
batch "k8s.io/api/batch/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
batchinformers "k8s.io/client-go/informers/batch/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
batchlisters "k8s.io/client-go/listers/batch/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
jobutil "k8s.io/kubernetes/pkg/controller/job"
|
||||
"k8s.io/kubernetes/pkg/kubectl/scheme"
|
||||
"k8s.io/kubernetes/pkg/util/metrics"
|
||||
)
|
||||
|
||||
// Controller watches for changes of Jobs API objects. Triggered by Job creation
|
||||
// and updates, it enqueues Jobs that have non-nil `.spec.ttlSecondsAfterFinished`
|
||||
// to the `queue`. The Controller has workers who consume `queue`, check whether
|
||||
// the Job TTL has expired or not; if the Job TTL hasn't expired, it will add the
|
||||
// Job to the queue after the TTL is expected to expire; if the TTL has expired, the
|
||||
// worker will send requests to the API server to delete the Jobs accordingly.
|
||||
// This is implemented outside of Job controller for separation of concerns, and
|
||||
// because it will be extended to handle other finishable resource types.
|
||||
type Controller struct {
|
||||
client clientset.Interface
|
||||
recorder record.EventRecorder
|
||||
|
||||
// jLister can list/get Jobs from the shared informer's store
|
||||
jLister batchlisters.JobLister
|
||||
|
||||
// jStoreSynced returns true if the Job store has been synced at least once.
|
||||
// Added as a member to the struct to allow injection for testing.
|
||||
jListerSynced cache.InformerSynced
|
||||
|
||||
// Jobs that the controller will check its TTL and attempt to delete when the TTL expires.
|
||||
queue workqueue.RateLimitingInterface
|
||||
|
||||
// The clock for tracking time
|
||||
clock clock.Clock
|
||||
}
|
||||
|
||||
// New creates an instance of Controller
|
||||
func New(jobInformer batchinformers.JobInformer, client clientset.Interface) *Controller {
|
||||
eventBroadcaster := record.NewBroadcaster()
|
||||
eventBroadcaster.StartLogging(glog.Infof)
|
||||
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: client.CoreV1().Events("")})
|
||||
|
||||
if client != nil && client.CoreV1().RESTClient().GetRateLimiter() != nil {
|
||||
metrics.RegisterMetricAndTrackRateLimiterUsage("ttl_after_finished_controller", client.CoreV1().RESTClient().GetRateLimiter())
|
||||
}
|
||||
|
||||
tc := &Controller{
|
||||
client: client,
|
||||
recorder: eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "ttl-after-finished-controller"}),
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "ttl_jobs_to_delete"),
|
||||
}
|
||||
|
||||
jobInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: tc.addJob,
|
||||
UpdateFunc: tc.updateJob,
|
||||
})
|
||||
|
||||
tc.jLister = jobInformer.Lister()
|
||||
tc.jListerSynced = jobInformer.Informer().HasSynced
|
||||
|
||||
tc.clock = clock.RealClock{}
|
||||
|
||||
return tc
|
||||
}
|
||||
|
||||
// Run starts the workers to clean up Jobs.
|
||||
func (tc *Controller) Run(workers int, stopCh <-chan struct{}) {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer tc.queue.ShutDown()
|
||||
|
||||
glog.Infof("Starting TTL after finished controller")
|
||||
defer glog.Infof("Shutting down TTL after finished controller")
|
||||
|
||||
if !controller.WaitForCacheSync("TTL after finished", stopCh, tc.jListerSynced) {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < workers; i++ {
|
||||
go wait.Until(tc.worker, time.Second, stopCh)
|
||||
}
|
||||
|
||||
<-stopCh
|
||||
}
|
||||
|
||||
func (tc *Controller) addJob(obj interface{}) {
|
||||
job := obj.(*batch.Job)
|
||||
glog.V(4).Infof("Adding job %s/%s", job.Namespace, job.Name)
|
||||
|
||||
if job.DeletionTimestamp == nil && needsCleanup(job) {
|
||||
tc.enqueue(job)
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *Controller) updateJob(old, cur interface{}) {
|
||||
job := cur.(*batch.Job)
|
||||
glog.V(4).Infof("Updating job %s/%s", job.Namespace, job.Name)
|
||||
|
||||
if job.DeletionTimestamp == nil && needsCleanup(job) {
|
||||
tc.enqueue(job)
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *Controller) enqueue(job *batch.Job) {
|
||||
glog.V(4).Infof("Add job %s/%s to cleanup", job.Namespace, job.Name)
|
||||
key, err := controller.KeyFunc(job)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("couldn't get key for object %#v: %v", job, err))
|
||||
return
|
||||
}
|
||||
|
||||
tc.queue.Add(key)
|
||||
}
|
||||
|
||||
func (tc *Controller) enqueueAfter(job *batch.Job, after time.Duration) {
|
||||
key, err := controller.KeyFunc(job)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("couldn't get key for object %#v: %v", job, err))
|
||||
return
|
||||
}
|
||||
|
||||
tc.queue.AddAfter(key, after)
|
||||
}
|
||||
|
||||
func (tc *Controller) worker() {
|
||||
for tc.processNextWorkItem() {
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *Controller) processNextWorkItem() bool {
|
||||
key, quit := tc.queue.Get()
|
||||
if quit {
|
||||
return false
|
||||
}
|
||||
defer tc.queue.Done(key)
|
||||
|
||||
err := tc.processJob(key.(string))
|
||||
tc.handleErr(err, key)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (tc *Controller) handleErr(err error, key interface{}) {
|
||||
if err == nil {
|
||||
tc.queue.Forget(key)
|
||||
return
|
||||
}
|
||||
|
||||
utilruntime.HandleError(fmt.Errorf("error cleaning up Job %v, will retry: %v", key, err))
|
||||
tc.queue.AddRateLimited(key)
|
||||
}
|
||||
|
||||
// processJob will check the Job's state and TTL and delete the Job when it
|
||||
// finishes and its TTL after finished has expired. If the Job hasn't finished or
|
||||
// its TTL hasn't expired, it will be added to the queue after the TTL is expected
|
||||
// to expire.
|
||||
// This function is not meant to be invoked concurrently with the same key.
|
||||
func (tc *Controller) processJob(key string) error {
|
||||
namespace, name, err := cache.SplitMetaNamespaceKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
glog.V(4).Infof("Checking if Job %s/%s is ready for cleanup", namespace, name)
|
||||
// Ignore the Jobs that are already deleted or being deleted, or the ones that don't need clean up.
|
||||
job, err := tc.jLister.Jobs(namespace).Get(name)
|
||||
if errors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if expired, err := tc.processTTL(job); err != nil {
|
||||
return err
|
||||
} else if !expired {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The Job's TTL is assumed to have expired, but the Job TTL might be stale.
|
||||
// Before deleting the Job, do a final sanity check.
|
||||
// If TTL is modified before we do this check, we cannot be sure if the TTL truly expires.
|
||||
// The latest Job may have a different UID, but it's fine because the checks will be run again.
|
||||
fresh, err := tc.client.BatchV1().Jobs(namespace).Get(name, metav1.GetOptions{})
|
||||
if errors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Use the latest Job TTL to see if the TTL truly expires.
|
||||
if expired, err := tc.processTTL(fresh); err != nil {
|
||||
return err
|
||||
} else if !expired {
|
||||
return nil
|
||||
}
|
||||
// Cascade deletes the Jobs if TTL truly expires.
|
||||
policy := metav1.DeletePropagationForeground
|
||||
options := &metav1.DeleteOptions{
|
||||
PropagationPolicy: &policy,
|
||||
Preconditions: &metav1.Preconditions{UID: &fresh.UID},
|
||||
}
|
||||
glog.V(4).Infof("Cleaning up Job %s/%s", namespace, name)
|
||||
return tc.client.BatchV1().Jobs(fresh.Namespace).Delete(fresh.Name, options)
|
||||
}
|
||||
|
||||
// processTTL checks whether a given Job's TTL has expired, and add it to the queue after the TTL is expected to expire
|
||||
// if the TTL will expire later.
|
||||
func (tc *Controller) processTTL(job *batch.Job) (expired bool, err error) {
|
||||
// We don't care about the Jobs that are going to be deleted, or the ones that don't need clean up.
|
||||
if job.DeletionTimestamp != nil || !needsCleanup(job) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
now := tc.clock.Now()
|
||||
t, err := timeLeft(job, &now)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// TTL has expired
|
||||
if *t <= 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
tc.enqueueAfter(job, *t)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// needsCleanup checks whether a Job has finished and has a TTL set.
|
||||
func needsCleanup(j *batch.Job) bool {
|
||||
return j.Spec.TTLSecondsAfterFinished != nil && jobutil.IsJobFinished(j)
|
||||
}
|
||||
|
||||
func getFinishAndExpireTime(j *batch.Job) (*time.Time, *time.Time, error) {
|
||||
if !needsCleanup(j) {
|
||||
return nil, nil, fmt.Errorf("Job %s/%s should not be cleaned up", j.Namespace, j.Name)
|
||||
}
|
||||
finishAt, err := jobFinishTime(j)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
finishAtUTC := finishAt.UTC()
|
||||
expireAtUTC := finishAtUTC.Add(time.Duration(*j.Spec.TTLSecondsAfterFinished) * time.Second)
|
||||
return &finishAtUTC, &expireAtUTC, nil
|
||||
}
|
||||
|
||||
func timeLeft(j *batch.Job, since *time.Time) (*time.Duration, error) {
|
||||
finishAt, expireAt, err := getFinishAndExpireTime(j)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if finishAt.UTC().After(since.UTC()) {
|
||||
glog.Warningf("Warning: Found Job %s/%s finished in the future. This is likely due to time skew in the cluster. Job cleanup will be deferred.", j.Namespace, j.Name)
|
||||
}
|
||||
remaining := expireAt.UTC().Sub(since.UTC())
|
||||
glog.V(4).Infof("Found Job %s/%s finished at %v, remaining TTL %v since %v, TTL will expire at %v", j.Namespace, j.Name, finishAt.UTC(), remaining, since.UTC(), expireAt.UTC())
|
||||
return &remaining, nil
|
||||
}
|
||||
|
||||
// jobFinishTime takes an already finished Job and returns the time it finishes.
|
||||
func jobFinishTime(finishedJob *batch.Job) (metav1.Time, error) {
|
||||
for _, c := range finishedJob.Status.Conditions {
|
||||
if (c.Type == batch.JobComplete || c.Type == batch.JobFailed) && c.Status == v1.ConditionTrue {
|
||||
finishAt := c.LastTransitionTime
|
||||
if finishAt.IsZero() {
|
||||
return metav1.Time{}, fmt.Errorf("unable to find the time when the Job %s/%s finished", finishedJob.Namespace, finishedJob.Name)
|
||||
}
|
||||
return c.LastTransitionTime, nil
|
||||
}
|
||||
}
|
||||
|
||||
// This should never happen if the Jobs has finished
|
||||
return metav1.Time{}, fmt.Errorf("unable to find the status of the finished Job %s/%s", finishedJob.Namespace, finishedJob.Name)
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
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 ttlafterfinished
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
batch "k8s.io/api/batch/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func newJob(completionTime, failedTime metav1.Time, ttl *int32) *batch.Job {
|
||||
j := &batch.Job{
|
||||
TypeMeta: metav1.TypeMeta{Kind: "Job"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foobar",
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
},
|
||||
Spec: batch.JobSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"foo": "bar"},
|
||||
},
|
||||
Template: v1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{Image: "foo/bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if !completionTime.IsZero() {
|
||||
c := batch.JobCondition{Type: batch.JobComplete, Status: v1.ConditionTrue, LastTransitionTime: completionTime}
|
||||
j.Status.Conditions = append(j.Status.Conditions, c)
|
||||
}
|
||||
|
||||
if !failedTime.IsZero() {
|
||||
c := batch.JobCondition{Type: batch.JobFailed, Status: v1.ConditionTrue, LastTransitionTime: failedTime}
|
||||
j.Status.Conditions = append(j.Status.Conditions, c)
|
||||
}
|
||||
|
||||
if ttl != nil {
|
||||
j.Spec.TTLSecondsAfterFinished = ttl
|
||||
}
|
||||
|
||||
return j
|
||||
}
|
||||
|
||||
func durationPointer(n int) *time.Duration {
|
||||
s := time.Duration(n) * time.Second
|
||||
return &s
|
||||
}
|
||||
|
||||
func int32Ptr(n int32) *int32 {
|
||||
return &n
|
||||
}
|
||||
|
||||
func TestTimeLeft(t *testing.T) {
|
||||
now := metav1.Now()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
completionTime metav1.Time
|
||||
failedTime metav1.Time
|
||||
ttl *int32
|
||||
since *time.Time
|
||||
expectErr bool
|
||||
expectErrStr string
|
||||
expectedTimeLeft *time.Duration
|
||||
}{
|
||||
{
|
||||
name: "Error case: Job unfinished",
|
||||
ttl: int32Ptr(100),
|
||||
since: &now.Time,
|
||||
expectErr: true,
|
||||
expectErrStr: "should not be cleaned up",
|
||||
},
|
||||
{
|
||||
name: "Error case: Job completed now, no TTL",
|
||||
completionTime: now,
|
||||
since: &now.Time,
|
||||
expectErr: true,
|
||||
expectErrStr: "should not be cleaned up",
|
||||
},
|
||||
{
|
||||
name: "Job completed now, 0s TTL",
|
||||
completionTime: now,
|
||||
ttl: int32Ptr(0),
|
||||
since: &now.Time,
|
||||
expectedTimeLeft: durationPointer(0),
|
||||
},
|
||||
{
|
||||
name: "Job completed now, 10s TTL",
|
||||
completionTime: now,
|
||||
ttl: int32Ptr(10),
|
||||
since: &now.Time,
|
||||
expectedTimeLeft: durationPointer(10),
|
||||
},
|
||||
{
|
||||
name: "Job completed 10s ago, 15s TTL",
|
||||
completionTime: metav1.NewTime(now.Add(-10 * time.Second)),
|
||||
ttl: int32Ptr(15),
|
||||
since: &now.Time,
|
||||
expectedTimeLeft: durationPointer(5),
|
||||
},
|
||||
{
|
||||
name: "Error case: Job failed now, no TTL",
|
||||
failedTime: now,
|
||||
since: &now.Time,
|
||||
expectErr: true,
|
||||
expectErrStr: "should not be cleaned up",
|
||||
},
|
||||
{
|
||||
name: "Job failed now, 0s TTL",
|
||||
failedTime: now,
|
||||
ttl: int32Ptr(0),
|
||||
since: &now.Time,
|
||||
expectedTimeLeft: durationPointer(0),
|
||||
},
|
||||
{
|
||||
name: "Job failed now, 10s TTL",
|
||||
failedTime: now,
|
||||
ttl: int32Ptr(10),
|
||||
since: &now.Time,
|
||||
expectedTimeLeft: durationPointer(10),
|
||||
},
|
||||
{
|
||||
name: "Job failed 10s ago, 15s TTL",
|
||||
failedTime: metav1.NewTime(now.Add(-10 * time.Second)),
|
||||
ttl: int32Ptr(15),
|
||||
since: &now.Time,
|
||||
expectedTimeLeft: durationPointer(5),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
job := newJob(tc.completionTime, tc.failedTime, tc.ttl)
|
||||
gotTimeLeft, gotErr := timeLeft(job, tc.since)
|
||||
if tc.expectErr != (gotErr != nil) {
|
||||
t.Errorf("%s: expected error is %t, got %t, error: %v", tc.name, tc.expectErr, gotErr != nil, gotErr)
|
||||
}
|
||||
if tc.expectErr && len(tc.expectErrStr) == 0 {
|
||||
t.Errorf("%s: invalid test setup; error message must not be empty for error cases", tc.name)
|
||||
}
|
||||
if tc.expectErr && !strings.Contains(gotErr.Error(), tc.expectErrStr) {
|
||||
t.Errorf("%s: expected error message contains %q, got %v", tc.name, tc.expectErrStr, gotErr)
|
||||
}
|
||||
if !tc.expectErr {
|
||||
if *gotTimeLeft != *tc.expectedTimeLeft {
|
||||
t.Errorf("%s: expected time left %v, got %v", tc.name, tc.expectedTimeLeft, gotTimeLeft)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -381,6 +381,12 @@ const (
|
||||
//
|
||||
// Enables control over ProcMountType for containers.
|
||||
ProcMountType utilfeature.Feature = "ProcMountType"
|
||||
|
||||
// owner: @janetkuo
|
||||
// alpha: v1.12
|
||||
//
|
||||
// Allow TTL controller to clean up Pods and Jobs after they finish.
|
||||
TTLAfterFinished utilfeature.Feature = "TTLAfterFinished"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -445,6 +451,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
|
||||
SCTPSupport: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
VolumeSnapshotDataSource: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
ProcMountType: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
TTLAfterFinished: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
|
||||
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
||||
// unintentionally on either side:
|
||||
|
@ -18,6 +18,7 @@ go_library(
|
||||
"//pkg/api/pod:go_default_library",
|
||||
"//pkg/apis/batch:go_default_library",
|
||||
"//pkg/apis/batch/validation:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
@ -27,6 +28,7 @@ go_library(
|
||||
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage/names:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@ -39,10 +41,12 @@ go_test(
|
||||
"//pkg/api/testing:go_default_library",
|
||||
"//pkg/apis/batch:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -30,10 +30,12 @@ import (
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/api/pod"
|
||||
"k8s.io/kubernetes/pkg/apis/batch"
|
||||
"k8s.io/kubernetes/pkg/apis/batch/validation"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
// jobStrategy implements verification logic for Replication Controllers.
|
||||
@ -61,6 +63,10 @@ func (jobStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
||||
job := obj.(*batch.Job)
|
||||
job.Status = batch.JobStatus{}
|
||||
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) {
|
||||
job.Spec.TTLSecondsAfterFinished = nil
|
||||
}
|
||||
|
||||
pod.DropDisabledAlphaFields(&job.Spec.Template.Spec)
|
||||
}
|
||||
|
||||
@ -70,6 +76,11 @@ func (jobStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object
|
||||
oldJob := old.(*batch.Job)
|
||||
newJob.Status = oldJob.Status
|
||||
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) {
|
||||
newJob.Spec.TTLSecondsAfterFinished = nil
|
||||
oldJob.Spec.TTLSecondsAfterFinished = nil
|
||||
}
|
||||
|
||||
pod.DropDisabledAlphaFields(&newJob.Spec.Template.Spec)
|
||||
pod.DropDisabledAlphaFields(&oldJob.Spec.Template.Spec)
|
||||
}
|
||||
|
@ -24,19 +24,24 @@ import (
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
||||
"k8s.io/kubernetes/pkg/apis/batch"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
func newBool(a bool) *bool {
|
||||
r := new(bool)
|
||||
*r = a
|
||||
return r
|
||||
return &a
|
||||
}
|
||||
|
||||
func newInt32(i int32) *int32 {
|
||||
return &i
|
||||
}
|
||||
|
||||
func TestJobStrategy(t *testing.T) {
|
||||
ttlEnabled := utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished)
|
||||
ctx := genericapirequest.NewDefaultContext()
|
||||
if !Strategy.NamespaceScoped() {
|
||||
t.Errorf("Job must be namespace scoped")
|
||||
@ -64,9 +69,10 @@ func TestJobStrategy(t *testing.T) {
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
},
|
||||
Spec: batch.JobSpec{
|
||||
Selector: validSelector,
|
||||
Template: validPodTemplateSpec,
|
||||
ManualSelector: newBool(true),
|
||||
Selector: validSelector,
|
||||
Template: validPodTemplateSpec,
|
||||
TTLSecondsAfterFinished: newInt32(0), // Set TTL
|
||||
ManualSelector: newBool(true),
|
||||
},
|
||||
Status: batch.JobStatus{
|
||||
Active: 11,
|
||||
@ -81,11 +87,21 @@ func TestJobStrategy(t *testing.T) {
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("Unexpected error validating %v", errs)
|
||||
}
|
||||
if ttlEnabled && job.Spec.TTLSecondsAfterFinished == nil {
|
||||
// When the TTL feature is enabled, the TTL field can be set
|
||||
t.Errorf("Job should allow setting .spec.ttlSecondsAfterFinished when %v feature is enabled", features.TTLAfterFinished)
|
||||
}
|
||||
if !ttlEnabled && job.Spec.TTLSecondsAfterFinished != nil {
|
||||
// When the TTL feature is disabled, the TTL field cannot be set
|
||||
t.Errorf("Job should not allow setting .spec.ttlSecondsAfterFinished when %v feature is disabled", features.TTLAfterFinished)
|
||||
}
|
||||
|
||||
parallelism := int32(10)
|
||||
updatedJob := &batch.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "bar", ResourceVersion: "4"},
|
||||
Spec: batch.JobSpec{
|
||||
Parallelism: ¶llelism,
|
||||
Parallelism: ¶llelism,
|
||||
TTLSecondsAfterFinished: newInt32(1), // Update TTL
|
||||
},
|
||||
Status: batch.JobStatus{
|
||||
Active: 11,
|
||||
@ -101,6 +117,9 @@ func TestJobStrategy(t *testing.T) {
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("Expected a validation error")
|
||||
}
|
||||
if ttlEnabled != (job.Spec.TTLSecondsAfterFinished != nil || updatedJob.Spec.TTLSecondsAfterFinished != nil) {
|
||||
t.Errorf("Job should only allow updating .spec.ttlSecondsAfterFinished when %v feature is enabled", features.TTLAfterFinished)
|
||||
}
|
||||
|
||||
// Make sure we correctly implement the interface.
|
||||
// Otherwise a typo could silently change the default.
|
||||
|
@ -340,6 +340,15 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding)
|
||||
eventsRule(),
|
||||
},
|
||||
})
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) {
|
||||
addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "ttl-after-finished-controller"},
|
||||
Rules: []rbacv1.PolicyRule{
|
||||
rbacv1helpers.NewRule("get", "list", "watch", "delete").Groups(batchGroup).Resources("jobs").RuleOrDie(),
|
||||
eventsRule(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return controllerRoles, controllerRoleBindings
|
||||
}
|
||||
|
146
staging/src/k8s.io/api/batch/v1/generated.pb.go
generated
146
staging/src/k8s.io/api/batch/v1/generated.pb.go
generated
@ -276,6 +276,11 @@ func (m *JobSpec) MarshalTo(dAtA []byte) (int, error) {
|
||||
i++
|
||||
i = encodeVarintGenerated(dAtA, i, uint64(*m.BackoffLimit))
|
||||
}
|
||||
if m.TTLSecondsAfterFinished != nil {
|
||||
dAtA[i] = 0x40
|
||||
i++
|
||||
i = encodeVarintGenerated(dAtA, i, uint64(*m.TTLSecondsAfterFinished))
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
@ -433,6 +438,9 @@ func (m *JobSpec) Size() (n int) {
|
||||
if m.BackoffLimit != nil {
|
||||
n += 1 + sovGenerated(uint64(*m.BackoffLimit))
|
||||
}
|
||||
if m.TTLSecondsAfterFinished != nil {
|
||||
n += 1 + sovGenerated(uint64(*m.TTLSecondsAfterFinished))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@ -522,6 +530,7 @@ func (this *JobSpec) String() string {
|
||||
`ManualSelector:` + valueToStringGenerated(this.ManualSelector) + `,`,
|
||||
`Template:` + strings.Replace(strings.Replace(this.Template.String(), "PodTemplateSpec", "k8s_io_api_core_v1.PodTemplateSpec", 1), `&`, ``, 1) + `,`,
|
||||
`BackoffLimit:` + valueToStringGenerated(this.BackoffLimit) + `,`,
|
||||
`TTLSecondsAfterFinished:` + valueToStringGenerated(this.TTLSecondsAfterFinished) + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
return s
|
||||
@ -1219,6 +1228,26 @@ func (m *JobSpec) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
}
|
||||
m.BackoffLimit = &v
|
||||
case 8:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field TTLSecondsAfterFinished", wireType)
|
||||
}
|
||||
var v int32
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
v |= (int32(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.TTLSecondsAfterFinished = &v
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipGenerated(dAtA[iNdEx:])
|
||||
@ -1554,61 +1583,64 @@ func init() {
|
||||
}
|
||||
|
||||
var fileDescriptorGenerated = []byte{
|
||||
// 893 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x54, 0x41, 0x6f, 0xe3, 0x44,
|
||||
0x18, 0x8d, 0x9b, 0xa6, 0x4d, 0x26, 0x69, 0xb7, 0x0c, 0xaa, 0x14, 0x2a, 0xe4, 0x2c, 0x41, 0x42,
|
||||
0x05, 0x09, 0x9b, 0x94, 0x0a, 0x21, 0x04, 0x48, 0xb8, 0x68, 0x25, 0xaa, 0x54, 0x5b, 0x26, 0x45,
|
||||
0x48, 0x08, 0x24, 0xc6, 0xf6, 0x97, 0xd4, 0xc4, 0xf6, 0x58, 0x9e, 0x49, 0xa4, 0xde, 0xf8, 0x09,
|
||||
0xfc, 0x08, 0xc4, 0x4f, 0x41, 0x3d, 0xee, 0x71, 0x4f, 0x11, 0x35, 0xdc, 0xb9, 0xef, 0x09, 0xcd,
|
||||
0x78, 0x62, 0x3b, 0x6d, 0x2a, 0xda, 0xbd, 0x79, 0xde, 0xbc, 0xf7, 0xbe, 0xf1, 0x37, 0x6f, 0x3e,
|
||||
0xf4, 0xf9, 0xf4, 0x53, 0x6e, 0x05, 0xcc, 0x9e, 0xce, 0x5c, 0x48, 0x63, 0x10, 0xc0, 0xed, 0x39,
|
||||
0xc4, 0x3e, 0x4b, 0x6d, 0xbd, 0x41, 0x93, 0xc0, 0x76, 0xa9, 0xf0, 0x2e, 0xed, 0xf9, 0xc0, 0x9e,
|
||||
0x40, 0x0c, 0x29, 0x15, 0xe0, 0x5b, 0x49, 0xca, 0x04, 0xc3, 0x6f, 0xe6, 0x24, 0x8b, 0x26, 0x81,
|
||||
0xa5, 0x48, 0xd6, 0x7c, 0x70, 0xf0, 0xe1, 0x24, 0x10, 0x97, 0x33, 0xd7, 0xf2, 0x58, 0x64, 0x4f,
|
||||
0xd8, 0x84, 0xd9, 0x8a, 0xeb, 0xce, 0xc6, 0x6a, 0xa5, 0x16, 0xea, 0x2b, 0xf7, 0x38, 0xe8, 0x57,
|
||||
0x0a, 0x79, 0x2c, 0x85, 0x35, 0x75, 0x0e, 0x8e, 0x4b, 0x4e, 0x44, 0xbd, 0xcb, 0x20, 0x86, 0xf4,
|
||||
0xca, 0x4e, 0xa6, 0x13, 0x09, 0x70, 0x3b, 0x02, 0x41, 0xd7, 0xa9, 0xec, 0xfb, 0x54, 0xe9, 0x2c,
|
||||
0x16, 0x41, 0x04, 0x77, 0x04, 0x9f, 0xfc, 0x9f, 0x80, 0x7b, 0x97, 0x10, 0xd1, 0xdb, 0xba, 0xfe,
|
||||
0xbf, 0x06, 0xaa, 0x9f, 0x32, 0x17, 0xff, 0x8c, 0x9a, 0xf2, 0x2c, 0x3e, 0x15, 0xb4, 0x6b, 0x3c,
|
||||
0x35, 0x0e, 0xdb, 0x47, 0x1f, 0x59, 0x65, 0x87, 0x0a, 0x4b, 0x2b, 0x99, 0x4e, 0x24, 0xc0, 0x2d,
|
||||
0xc9, 0xb6, 0xe6, 0x03, 0xeb, 0xb9, 0xfb, 0x0b, 0x78, 0xe2, 0x0c, 0x04, 0x75, 0xf0, 0xf5, 0xa2,
|
||||
0x57, 0xcb, 0x16, 0x3d, 0x54, 0x62, 0xa4, 0x70, 0xc5, 0x5f, 0xa2, 0x4d, 0x9e, 0x80, 0xd7, 0xdd,
|
||||
0x50, 0xee, 0x6f, 0x5b, 0x6b, 0xfa, 0x6f, 0x9d, 0x32, 0x77, 0x94, 0x80, 0xe7, 0x74, 0xb4, 0xd3,
|
||||
0xa6, 0x5c, 0x11, 0xa5, 0xc3, 0xcf, 0xd0, 0x16, 0x17, 0x54, 0xcc, 0x78, 0xb7, 0xae, 0x1c, 0xcc,
|
||||
0x7b, 0x1d, 0x14, 0xcb, 0xd9, 0xd5, 0x1e, 0x5b, 0xf9, 0x9a, 0x68, 0x75, 0xff, 0xcf, 0x3a, 0xea,
|
||||
0x9c, 0x32, 0xf7, 0x84, 0xc5, 0x7e, 0x20, 0x02, 0x16, 0xe3, 0x63, 0xb4, 0x29, 0xae, 0x12, 0x50,
|
||||
0xbf, 0xdd, 0x72, 0x9e, 0x2e, 0x4b, 0x5f, 0x5c, 0x25, 0xf0, 0x6a, 0xd1, 0xdb, 0xab, 0x72, 0x25,
|
||||
0x46, 0x14, 0x1b, 0x0f, 0x8b, 0xe3, 0x6c, 0x28, 0xdd, 0xf1, 0x6a, 0xb9, 0x57, 0x8b, 0xde, 0x9a,
|
||||
0x74, 0x58, 0x85, 0xd3, 0xea, 0xa1, 0xf0, 0x04, 0xed, 0x84, 0x94, 0x8b, 0xf3, 0x94, 0xb9, 0x70,
|
||||
0x11, 0x44, 0xa0, 0xff, 0xf1, 0x83, 0x87, 0xdd, 0x81, 0x54, 0x38, 0xfb, 0xfa, 0x00, 0x3b, 0xc3,
|
||||
0xaa, 0x11, 0x59, 0xf5, 0xc5, 0x73, 0x84, 0x25, 0x70, 0x91, 0xd2, 0x98, 0xe7, 0xbf, 0x24, 0xab,
|
||||
0x6d, 0x3e, 0xba, 0xda, 0x81, 0xae, 0x86, 0x87, 0x77, 0xdc, 0xc8, 0x9a, 0x0a, 0xf8, 0x3d, 0xb4,
|
||||
0x95, 0x02, 0xe5, 0x2c, 0xee, 0x36, 0x54, 0xbb, 0x8a, 0xdb, 0x21, 0x0a, 0x25, 0x7a, 0x17, 0xbf,
|
||||
0x8f, 0xb6, 0x23, 0xe0, 0x9c, 0x4e, 0xa0, 0xbb, 0xa5, 0x88, 0x4f, 0x34, 0x71, 0xfb, 0x2c, 0x87,
|
||||
0xc9, 0x72, 0xbf, 0xff, 0x87, 0x81, 0xb6, 0x4f, 0x99, 0x3b, 0x0c, 0xb8, 0xc0, 0x3f, 0xde, 0x89,
|
||||
0xaf, 0xf5, 0xb0, 0x9f, 0x91, 0x6a, 0x15, 0xde, 0x3d, 0x5d, 0xa7, 0xb9, 0x44, 0x2a, 0xd1, 0xfd,
|
||||
0x02, 0x35, 0x02, 0x01, 0x91, 0xbc, 0xea, 0xfa, 0x61, 0xfb, 0xa8, 0x7b, 0x5f, 0xf2, 0x9c, 0x1d,
|
||||
0x6d, 0xd2, 0xf8, 0x46, 0xd2, 0x49, 0xae, 0xea, 0xff, 0x53, 0x57, 0x07, 0x95, 0x59, 0xc6, 0x03,
|
||||
0xd4, 0x4e, 0x68, 0x4a, 0xc3, 0x10, 0xc2, 0x80, 0x47, 0xea, 0xac, 0x0d, 0xe7, 0x49, 0xb6, 0xe8,
|
||||
0xb5, 0xcf, 0x4b, 0x98, 0x54, 0x39, 0x52, 0xe2, 0xb1, 0x28, 0x09, 0x41, 0x36, 0x33, 0x8f, 0x9b,
|
||||
0x96, 0x9c, 0x94, 0x30, 0xa9, 0x72, 0xf0, 0x73, 0xb4, 0x4f, 0x3d, 0x11, 0xcc, 0xe1, 0x6b, 0xa0,
|
||||
0x7e, 0x18, 0xc4, 0x30, 0x02, 0x8f, 0xc5, 0x7e, 0xfe, 0x74, 0xea, 0xce, 0x5b, 0xd9, 0xa2, 0xb7,
|
||||
0xff, 0xd5, 0x3a, 0x02, 0x59, 0xaf, 0xc3, 0x3f, 0xa1, 0x26, 0x87, 0x10, 0x3c, 0xc1, 0x52, 0x1d,
|
||||
0x96, 0x8f, 0x1f, 0xd8, 0x5f, 0xea, 0x42, 0x38, 0xd2, 0x52, 0xa7, 0x23, 0x1b, 0xbc, 0x5c, 0x91,
|
||||
0xc2, 0x12, 0x7f, 0x86, 0x76, 0x23, 0x1a, 0xcf, 0x68, 0xc1, 0x54, 0x29, 0x69, 0x3a, 0x38, 0x5b,
|
||||
0xf4, 0x76, 0xcf, 0x56, 0x76, 0xc8, 0x2d, 0x26, 0xfe, 0x16, 0x35, 0x05, 0x44, 0x49, 0x48, 0x45,
|
||||
0x1e, 0x99, 0xf6, 0xd1, 0xbb, 0xd5, 0xfb, 0x91, 0x2f, 0x4f, 0x1e, 0xe4, 0x9c, 0xf9, 0x17, 0x9a,
|
||||
0xa6, 0x46, 0x4c, 0x71, 0xdf, 0x4b, 0x94, 0x14, 0x36, 0xf8, 0x18, 0x75, 0x5c, 0xea, 0x4d, 0xd9,
|
||||
0x78, 0x3c, 0x0c, 0xa2, 0x40, 0x74, 0xb7, 0x55, 0xcb, 0xf7, 0xb2, 0x45, 0xaf, 0xe3, 0x54, 0x70,
|
||||
0xb2, 0xc2, 0xea, 0xff, 0x5e, 0x47, 0xad, 0x62, 0xfc, 0xe0, 0xef, 0x10, 0xf2, 0x96, 0x8f, 0x9d,
|
||||
0x77, 0x0d, 0x15, 0x9c, 0x77, 0xee, 0x0b, 0x4e, 0x31, 0x16, 0xca, 0x19, 0x5a, 0x40, 0x9c, 0x54,
|
||||
0x8c, 0xf0, 0xf7, 0xa8, 0xc5, 0x05, 0x4d, 0x85, 0x7a, 0xb6, 0x1b, 0x8f, 0x7e, 0xb6, 0x3b, 0xd9,
|
||||
0xa2, 0xd7, 0x1a, 0x2d, 0x0d, 0x48, 0xe9, 0x85, 0xc7, 0x68, 0xb7, 0x4c, 0xd0, 0x6b, 0x8e, 0x20,
|
||||
0x75, 0x5d, 0x27, 0x2b, 0x2e, 0xe4, 0x96, 0xab, 0x1c, 0x04, 0x79, 0xc4, 0x54, 0x8e, 0x1a, 0xe5,
|
||||
0x20, 0xc8, 0xf3, 0x48, 0xf4, 0x2e, 0xb6, 0x51, 0x8b, 0xcf, 0x3c, 0x0f, 0xc0, 0x07, 0x5f, 0xa5,
|
||||
0xa1, 0xe1, 0xbc, 0xa1, 0xa9, 0xad, 0xd1, 0x72, 0x83, 0x94, 0x1c, 0x69, 0x3c, 0xa6, 0x41, 0x08,
|
||||
0xbe, 0x4a, 0x41, 0xc5, 0xf8, 0x99, 0x42, 0x89, 0xde, 0x75, 0x0e, 0xaf, 0x6f, 0xcc, 0xda, 0x8b,
|
||||
0x1b, 0xb3, 0xf6, 0xf2, 0xc6, 0xac, 0xfd, 0x9a, 0x99, 0xc6, 0x75, 0x66, 0x1a, 0x2f, 0x32, 0xd3,
|
||||
0x78, 0x99, 0x99, 0xc6, 0x5f, 0x99, 0x69, 0xfc, 0xf6, 0xb7, 0x59, 0xfb, 0x61, 0x63, 0x3e, 0xf8,
|
||||
0x2f, 0x00, 0x00, 0xff, 0xff, 0xdd, 0xcc, 0x84, 0xd1, 0x61, 0x08, 0x00, 0x00,
|
||||
// 929 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x54, 0x5d, 0x6f, 0xe3, 0x44,
|
||||
0x14, 0xad, 0x9b, 0xa6, 0x4d, 0xa6, 0x1f, 0x5b, 0x06, 0x55, 0x1b, 0x0a, 0xb2, 0x97, 0x20, 0xa1,
|
||||
0x82, 0x84, 0x4d, 0x4b, 0x85, 0x10, 0x02, 0xa4, 0x75, 0x51, 0x25, 0xaa, 0x54, 0x5b, 0x26, 0x59,
|
||||
0x21, 0x21, 0x90, 0x18, 0xdb, 0x37, 0x89, 0x89, 0xed, 0xb1, 0x3c, 0x93, 0x48, 0x7d, 0xe3, 0x27,
|
||||
0xf0, 0x23, 0x10, 0x7f, 0x82, 0x77, 0xd4, 0xc7, 0x7d, 0xdc, 0x27, 0x8b, 0x9a, 0x1f, 0xc0, 0xfb,
|
||||
0x3e, 0xa1, 0x19, 0x3b, 0xb6, 0xd3, 0x26, 0xa2, 0xcb, 0x5b, 0xe6, 0xcc, 0x39, 0xe7, 0x5e, 0xdf,
|
||||
0x39, 0xb9, 0xe8, 0x8b, 0xc9, 0x67, 0xdc, 0xf4, 0x99, 0x35, 0x99, 0x3a, 0x90, 0x44, 0x20, 0x80,
|
||||
0x5b, 0x33, 0x88, 0x3c, 0x96, 0x58, 0xc5, 0x05, 0x8d, 0x7d, 0xcb, 0xa1, 0xc2, 0x1d, 0x5b, 0xb3,
|
||||
0x63, 0x6b, 0x04, 0x11, 0x24, 0x54, 0x80, 0x67, 0xc6, 0x09, 0x13, 0x0c, 0xbf, 0x99, 0x93, 0x4c,
|
||||
0x1a, 0xfb, 0xa6, 0x22, 0x99, 0xb3, 0xe3, 0xc3, 0x8f, 0x46, 0xbe, 0x18, 0x4f, 0x1d, 0xd3, 0x65,
|
||||
0xa1, 0x35, 0x62, 0x23, 0x66, 0x29, 0xae, 0x33, 0x1d, 0xaa, 0x93, 0x3a, 0xa8, 0x5f, 0xb9, 0xc7,
|
||||
0x61, 0xb7, 0x56, 0xc8, 0x65, 0x09, 0x2c, 0xa9, 0x73, 0x78, 0x5a, 0x71, 0x42, 0xea, 0x8e, 0xfd,
|
||||
0x08, 0x92, 0x6b, 0x2b, 0x9e, 0x8c, 0x24, 0xc0, 0xad, 0x10, 0x04, 0x5d, 0xa6, 0xb2, 0x56, 0xa9,
|
||||
0x92, 0x69, 0x24, 0xfc, 0x10, 0xee, 0x09, 0x3e, 0xfd, 0x2f, 0x01, 0x77, 0xc7, 0x10, 0xd2, 0xbb,
|
||||
0xba, 0xee, 0x3f, 0x1a, 0x6a, 0x5c, 0x30, 0x07, 0xff, 0x84, 0x5a, 0xb2, 0x17, 0x8f, 0x0a, 0xda,
|
||||
0xd1, 0x9e, 0x68, 0x47, 0xdb, 0x27, 0x1f, 0x9b, 0xd5, 0x84, 0x4a, 0x4b, 0x33, 0x9e, 0x8c, 0x24,
|
||||
0xc0, 0x4d, 0xc9, 0x36, 0x67, 0xc7, 0xe6, 0x33, 0xe7, 0x67, 0x70, 0xc5, 0x25, 0x08, 0x6a, 0xe3,
|
||||
0x9b, 0xd4, 0x58, 0xcb, 0x52, 0x03, 0x55, 0x18, 0x29, 0x5d, 0xf1, 0x57, 0x68, 0x83, 0xc7, 0xe0,
|
||||
0x76, 0xd6, 0x95, 0xfb, 0x3b, 0xe6, 0x92, 0xf9, 0x9b, 0x17, 0xcc, 0xe9, 0xc7, 0xe0, 0xda, 0x3b,
|
||||
0x85, 0xd3, 0x86, 0x3c, 0x11, 0xa5, 0xc3, 0xe7, 0x68, 0x93, 0x0b, 0x2a, 0xa6, 0xbc, 0xd3, 0x50,
|
||||
0x0e, 0xfa, 0x4a, 0x07, 0xc5, 0xb2, 0xf7, 0x0a, 0x8f, 0xcd, 0xfc, 0x4c, 0x0a, 0x75, 0xf7, 0xcf,
|
||||
0x06, 0xda, 0xb9, 0x60, 0xce, 0x19, 0x8b, 0x3c, 0x5f, 0xf8, 0x2c, 0xc2, 0xa7, 0x68, 0x43, 0x5c,
|
||||
0xc7, 0xa0, 0x3e, 0xbb, 0x6d, 0x3f, 0x99, 0x97, 0x1e, 0x5c, 0xc7, 0xf0, 0x2a, 0x35, 0xf6, 0xeb,
|
||||
0x5c, 0x89, 0x11, 0xc5, 0xc6, 0xbd, 0xb2, 0x9d, 0x75, 0xa5, 0x3b, 0x5d, 0x2c, 0xf7, 0x2a, 0x35,
|
||||
0x96, 0xa4, 0xc3, 0x2c, 0x9d, 0x16, 0x9b, 0xc2, 0x23, 0xb4, 0x1b, 0x50, 0x2e, 0xae, 0x12, 0xe6,
|
||||
0xc0, 0xc0, 0x0f, 0xa1, 0xf8, 0xc6, 0x0f, 0x1f, 0xf6, 0x06, 0x52, 0x61, 0x1f, 0x14, 0x0d, 0xec,
|
||||
0xf6, 0xea, 0x46, 0x64, 0xd1, 0x17, 0xcf, 0x10, 0x96, 0xc0, 0x20, 0xa1, 0x11, 0xcf, 0x3f, 0x49,
|
||||
0x56, 0xdb, 0x78, 0xed, 0x6a, 0x87, 0x45, 0x35, 0xdc, 0xbb, 0xe7, 0x46, 0x96, 0x54, 0xc0, 0xef,
|
||||
0xa3, 0xcd, 0x04, 0x28, 0x67, 0x51, 0xa7, 0xa9, 0xc6, 0x55, 0xbe, 0x0e, 0x51, 0x28, 0x29, 0x6e,
|
||||
0xf1, 0x07, 0x68, 0x2b, 0x04, 0xce, 0xe9, 0x08, 0x3a, 0x9b, 0x8a, 0xf8, 0xa8, 0x20, 0x6e, 0x5d,
|
||||
0xe6, 0x30, 0x99, 0xdf, 0x77, 0x7f, 0xd7, 0xd0, 0xd6, 0x05, 0x73, 0x7a, 0x3e, 0x17, 0xf8, 0x87,
|
||||
0x7b, 0xf1, 0x35, 0x1f, 0xf6, 0x31, 0x52, 0xad, 0xc2, 0xbb, 0x5f, 0xd4, 0x69, 0xcd, 0x91, 0x5a,
|
||||
0x74, 0xbf, 0x44, 0x4d, 0x5f, 0x40, 0x28, 0x9f, 0xba, 0x71, 0xb4, 0x7d, 0xd2, 0x59, 0x95, 0x3c,
|
||||
0x7b, 0xb7, 0x30, 0x69, 0x7e, 0x23, 0xe9, 0x24, 0x57, 0x75, 0xff, 0xd8, 0x50, 0x8d, 0xca, 0x2c,
|
||||
0xe3, 0x63, 0xb4, 0x1d, 0xd3, 0x84, 0x06, 0x01, 0x04, 0x3e, 0x0f, 0x55, 0xaf, 0x4d, 0xfb, 0x51,
|
||||
0x96, 0x1a, 0xdb, 0x57, 0x15, 0x4c, 0xea, 0x1c, 0x29, 0x71, 0x59, 0x18, 0x07, 0x20, 0x87, 0x99,
|
||||
0xc7, 0xad, 0x90, 0x9c, 0x55, 0x30, 0xa9, 0x73, 0xf0, 0x33, 0x74, 0x40, 0x5d, 0xe1, 0xcf, 0xe0,
|
||||
0x6b, 0xa0, 0x5e, 0xe0, 0x47, 0xd0, 0x07, 0x97, 0x45, 0x5e, 0xfe, 0xd7, 0x69, 0xd8, 0x6f, 0x65,
|
||||
0xa9, 0x71, 0xf0, 0x74, 0x19, 0x81, 0x2c, 0xd7, 0xe1, 0x1f, 0x51, 0x8b, 0x43, 0x00, 0xae, 0x60,
|
||||
0x49, 0x11, 0x96, 0x4f, 0x1e, 0x38, 0x5f, 0xea, 0x40, 0xd0, 0x2f, 0xa4, 0xf6, 0x8e, 0x1c, 0xf0,
|
||||
0xfc, 0x44, 0x4a, 0x4b, 0xfc, 0x39, 0xda, 0x0b, 0x69, 0x34, 0xa5, 0x25, 0x53, 0xa5, 0xa4, 0x65,
|
||||
0xe3, 0x2c, 0x35, 0xf6, 0x2e, 0x17, 0x6e, 0xc8, 0x1d, 0x26, 0xfe, 0x16, 0xb5, 0x04, 0x84, 0x71,
|
||||
0x40, 0x45, 0x1e, 0x99, 0xed, 0x93, 0xf7, 0xea, 0xef, 0x23, 0xff, 0x79, 0xb2, 0x91, 0x2b, 0xe6,
|
||||
0x0d, 0x0a, 0x9a, 0x5a, 0x31, 0xe5, 0x7b, 0xcf, 0x51, 0x52, 0xda, 0xe0, 0x53, 0xb4, 0xe3, 0x50,
|
||||
0x77, 0xc2, 0x86, 0xc3, 0x9e, 0x1f, 0xfa, 0xa2, 0xb3, 0xa5, 0x46, 0xbe, 0x9f, 0xa5, 0xc6, 0x8e,
|
||||
0x5d, 0xc3, 0xc9, 0x02, 0x0b, 0x3f, 0x47, 0x8f, 0x85, 0x08, 0x8a, 0x89, 0x3d, 0x1d, 0x0a, 0x48,
|
||||
0xce, 0xfd, 0xc8, 0xe7, 0x63, 0xf0, 0x3a, 0x2d, 0x65, 0xf0, 0x76, 0x96, 0x1a, 0x8f, 0x07, 0x83,
|
||||
0xde, 0x32, 0x0a, 0x59, 0xa5, 0xed, 0xfe, 0xd6, 0x40, 0xed, 0x72, 0xab, 0xe1, 0xe7, 0x08, 0xb9,
|
||||
0xf3, 0x1d, 0xc2, 0x3b, 0x9a, 0xca, 0xe3, 0xbb, 0xab, 0xf2, 0x58, 0x6e, 0x9b, 0x6a, 0x35, 0x97,
|
||||
0x10, 0x27, 0x35, 0x23, 0xfc, 0x1d, 0x6a, 0x73, 0x41, 0x13, 0xa1, 0xb6, 0xc1, 0xfa, 0x6b, 0x6f,
|
||||
0x83, 0xdd, 0x2c, 0x35, 0xda, 0xfd, 0xb9, 0x01, 0xa9, 0xbc, 0xf0, 0x10, 0xed, 0x55, 0xc1, 0xfc,
|
||||
0x9f, 0x9b, 0x4d, 0xa5, 0xe0, 0x6c, 0xc1, 0x85, 0xdc, 0x71, 0x95, 0xfb, 0x25, 0x4f, 0xae, 0x8a,
|
||||
0x67, 0xb3, 0xda, 0x2f, 0x79, 0xcc, 0x49, 0x71, 0x8b, 0x2d, 0xd4, 0xe6, 0x53, 0xd7, 0x05, 0xf0,
|
||||
0xc0, 0x53, 0x21, 0x6b, 0xda, 0x6f, 0x14, 0xd4, 0x76, 0x7f, 0x7e, 0x41, 0x2a, 0x8e, 0x34, 0x1e,
|
||||
0x52, 0x3f, 0x00, 0x4f, 0x85, 0xab, 0x66, 0x7c, 0xae, 0x50, 0x52, 0xdc, 0xda, 0x47, 0x37, 0xb7,
|
||||
0xfa, 0xda, 0x8b, 0x5b, 0x7d, 0xed, 0xe5, 0xad, 0xbe, 0xf6, 0x4b, 0xa6, 0x6b, 0x37, 0x99, 0xae,
|
||||
0xbd, 0xc8, 0x74, 0xed, 0x65, 0xa6, 0x6b, 0x7f, 0x65, 0xba, 0xf6, 0xeb, 0xdf, 0xfa, 0xda, 0xf7,
|
||||
0xeb, 0xb3, 0xe3, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x13, 0xdb, 0x98, 0xf9, 0xb8, 0x08, 0x00,
|
||||
0x00,
|
||||
}
|
||||
|
@ -134,6 +134,18 @@ message JobSpec {
|
||||
// Describes the pod that will be created when executing a job.
|
||||
// More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/
|
||||
optional k8s.io.api.core.v1.PodTemplateSpec template = 6;
|
||||
|
||||
// ttlSecondsAfterFinished limits the lifetime of a Job that has finished
|
||||
// execution (either Complete or Failed). If this field is set,
|
||||
// ttlSecondsAfterFinished after the Job finishes, it is eligible to be
|
||||
// automatically deleted. When the Job is being deleted, its lifecycle
|
||||
// guarantees (e.g. finalizers) will be honored. If this field is unset,
|
||||
// the Job won't be automatically deleted. If this field is set to zero,
|
||||
// the Job becomes eligible to be deleted immediately after it finishes.
|
||||
// This field is alpha-level and is only honored by servers that enable the
|
||||
// TTLAfterFinished feature.
|
||||
// +optional
|
||||
optional int32 ttlSecondsAfterFinished = 8;
|
||||
}
|
||||
|
||||
// JobStatus represents the current state of a Job.
|
||||
|
@ -114,6 +114,18 @@ type JobSpec struct {
|
||||
// Describes the pod that will be created when executing a job.
|
||||
// More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/
|
||||
Template v1.PodTemplateSpec `json:"template" protobuf:"bytes,6,opt,name=template"`
|
||||
|
||||
// ttlSecondsAfterFinished limits the lifetime of a Job that has finished
|
||||
// execution (either Complete or Failed). If this field is set,
|
||||
// ttlSecondsAfterFinished after the Job finishes, it is eligible to be
|
||||
// automatically deleted. When the Job is being deleted, its lifecycle
|
||||
// guarantees (e.g. finalizers) will be honored. If this field is unset,
|
||||
// the Job won't be automatically deleted. If this field is set to zero,
|
||||
// the Job becomes eligible to be deleted immediately after it finishes.
|
||||
// This field is alpha-level and is only honored by servers that enable the
|
||||
// TTLAfterFinished feature.
|
||||
// +optional
|
||||
TTLSecondsAfterFinished *int32 `json:"ttlSecondsAfterFinished,omitempty" protobuf:"varint,8,opt,name=ttlSecondsAfterFinished"`
|
||||
}
|
||||
|
||||
// JobStatus represents the current state of a Job.
|
||||
|
@ -63,14 +63,15 @@ func (JobList) SwaggerDoc() map[string]string {
|
||||
}
|
||||
|
||||
var map_JobSpec = map[string]string{
|
||||
"": "JobSpec describes how the job execution will look like.",
|
||||
"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. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/",
|
||||
"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. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/",
|
||||
"activeDeadlineSeconds": "Specifies the 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",
|
||||
"backoffLimit": "Specifies the number of retries before marking this job failed. Defaults to 6",
|
||||
"selector": "A label query over pods that should match the pod count. Normally, the system sets this field for you. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors",
|
||||
"manualSelector": "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. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector",
|
||||
"template": "Describes the pod that will be created when executing a job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/",
|
||||
"": "JobSpec describes how the job execution will look like.",
|
||||
"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. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/",
|
||||
"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. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/",
|
||||
"activeDeadlineSeconds": "Specifies the 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",
|
||||
"backoffLimit": "Specifies the number of retries before marking this job failed. Defaults to 6",
|
||||
"selector": "A label query over pods that should match the pod count. Normally, the system sets this field for you. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors",
|
||||
"manualSelector": "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. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector",
|
||||
"template": "Describes the pod that will be created when executing a job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/",
|
||||
"ttlSecondsAfterFinished": "ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won't be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes. This field is alpha-level and is only honored by servers that enable the TTLAfterFinished feature.",
|
||||
}
|
||||
|
||||
func (JobSpec) SwaggerDoc() map[string]string {
|
||||
|
@ -138,6 +138,11 @@ func (in *JobSpec) DeepCopyInto(out *JobSpec) {
|
||||
**out = **in
|
||||
}
|
||||
in.Template.DeepCopyInto(&out.Template)
|
||||
if in.TTLSecondsAfterFinished != nil {
|
||||
in, out := &in.TTLSecondsAfterFinished, &out.TTLSecondsAfterFinished
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -145,6 +145,9 @@ type KubeControllerManagerConfiguration struct {
|
||||
// ServiceControllerConfiguration holds configuration for ServiceController
|
||||
// related features.
|
||||
ServiceController ServiceControllerConfiguration
|
||||
// TTLAfterFinishedControllerConfiguration holds configuration for
|
||||
// TTLAfterFinishedController related features.
|
||||
TTLAfterFinishedController TTLAfterFinishedControllerConfiguration
|
||||
}
|
||||
|
||||
// GenericControllerManagerConfiguration holds configuration for a generic controller-manager.
|
||||
@ -438,3 +441,10 @@ type ServiceControllerConfiguration struct {
|
||||
// management, but more CPU (and network) load.
|
||||
ConcurrentServiceSyncs int32
|
||||
}
|
||||
|
||||
// TTLAfterFinishedControllerConfiguration contains elements describing TTLAfterFinishedController.
|
||||
type TTLAfterFinishedControllerConfiguration struct {
|
||||
// concurrentTTLSyncs is the number of TTL-after-finished collector workers that are
|
||||
// allowed to sync concurrently.
|
||||
ConcurrentTTLSyncs int32
|
||||
}
|
||||
|
@ -300,6 +300,7 @@ func (in *KubeControllerManagerConfiguration) DeepCopyInto(out *KubeControllerMa
|
||||
out.ResourceQuotaController = in.ResourceQuotaController
|
||||
out.SAController = in.SAController
|
||||
out.ServiceController = in.ServiceController
|
||||
out.TTLAfterFinishedController = in.TTLAfterFinishedController
|
||||
return
|
||||
}
|
||||
|
||||
@ -509,6 +510,22 @@ func (in *ServiceControllerConfiguration) DeepCopy() *ServiceControllerConfigura
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TTLAfterFinishedControllerConfiguration) DeepCopyInto(out *TTLAfterFinishedControllerConfiguration) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TTLAfterFinishedControllerConfiguration.
|
||||
func (in *TTLAfterFinishedControllerConfiguration) DeepCopy() *TTLAfterFinishedControllerConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TTLAfterFinishedControllerConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *VolumeConfiguration) DeepCopyInto(out *VolumeConfiguration) {
|
||||
*out = *in
|
||||
|
@ -44,7 +44,7 @@ var _ = SIGDescribe("Job", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Ensuring job reaches completions")
|
||||
err = framework.WaitForJobFinish(f.ClientSet, f.Namespace.Name, job.Name, completions)
|
||||
err = framework.WaitForJobComplete(f.ClientSet, f.Namespace.Name, job.Name, completions)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
@ -63,7 +63,7 @@ var _ = SIGDescribe("Job", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Ensuring job reaches completions")
|
||||
err = framework.WaitForJobFinish(f.ClientSet, f.Namespace.Name, job.Name, completions)
|
||||
err = framework.WaitForJobComplete(f.ClientSet, f.Namespace.Name, job.Name, completions)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
@ -84,7 +84,7 @@ var _ = SIGDescribe("Job", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Ensuring job reaches completions")
|
||||
err = framework.WaitForJobFinish(f.ClientSet, f.Namespace.Name, job.Name, *job.Spec.Completions)
|
||||
err = framework.WaitForJobComplete(f.ClientSet, f.Namespace.Name, job.Name, *job.Spec.Completions)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
|
@ -58,7 +58,7 @@ var _ = SIGDescribe("Metadata Concealment", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Ensuring job reaches completions")
|
||||
err = framework.WaitForJobFinish(f.ClientSet, f.Namespace.Name, job.Name, int32(1))
|
||||
err = framework.WaitForJobComplete(f.ClientSet, f.Namespace.Name, job.Name, int32(1))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
@ -34,6 +34,7 @@ go_library(
|
||||
"secrets_volume.go",
|
||||
"security_context.go",
|
||||
"sysctl.go",
|
||||
"ttlafterfinished.go",
|
||||
"util.go",
|
||||
"volumes.go",
|
||||
],
|
||||
@ -43,13 +44,16 @@ go_library(
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/conditions:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//pkg/kubelet:go_default_library",
|
||||
"//pkg/kubelet/apis:go_default_library",
|
||||
"//pkg/kubelet/images:go_default_library",
|
||||
"//pkg/kubelet/sysctl:go_default_library",
|
||||
"//pkg/security/apparmor:go_default_library",
|
||||
"//pkg/util/slice:go_default_library",
|
||||
"//pkg/util/version:go_default_library",
|
||||
"//staging/src/k8s.io/api/autoscaling/v1:go_default_library",
|
||||
"//staging/src/k8s.io/api/batch/v1:go_default_library",
|
||||
"//staging/src/k8s.io/api/coordination/v1beta1:go_default_library",
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
@ -65,6 +69,7 @@ go_library(
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/scale:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
|
||||
|
101
test/e2e/common/ttlafterfinished.go
Normal file
101
test/e2e/common/ttlafterfinished.go
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
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 common
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
batch "k8s.io/api/batch/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/util/slice"
|
||||
"k8s.io/kubernetes/test/e2e/framework"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const dummyFinalizer = "k8s.io/dummy-finalizer"
|
||||
|
||||
var _ = framework.KubeDescribe("TTLAfterFinished", func() {
|
||||
f := framework.NewDefaultFramework("ttlafterfinished")
|
||||
|
||||
It("Job should be deleted once it finishes after TTL seconds [Feature:TTLAfterFinished]", func() {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) {
|
||||
framework.Skipf("Skip because %s feature is not enabled; run test with --feature-gates=%s=true", features.TTLAfterFinished, features.TTLAfterFinished)
|
||||
}
|
||||
testFinishedJob(f)
|
||||
})
|
||||
})
|
||||
|
||||
func cleanupJob(f *framework.Framework, job *batch.Job) {
|
||||
ns := f.Namespace.Name
|
||||
c := f.ClientSet
|
||||
|
||||
framework.Logf("Remove the Job's dummy finalizer; the Job should be deleted cascadingly")
|
||||
removeFinalizerFunc := func(j *batch.Job) {
|
||||
j.ObjectMeta.Finalizers = slice.RemoveString(j.ObjectMeta.Finalizers, dummyFinalizer, nil)
|
||||
}
|
||||
_, err := framework.UpdateJobWithRetries(c, ns, job.Name, removeFinalizerFunc)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
framework.WaitForJobGone(c, ns, job.Name, wait.ForeverTestTimeout)
|
||||
|
||||
err = framework.WaitForAllJobPodsGone(c, ns, job.Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
func testFinishedJob(f *framework.Framework) {
|
||||
ns := f.Namespace.Name
|
||||
c := f.ClientSet
|
||||
|
||||
parallelism := int32(1)
|
||||
completions := int32(1)
|
||||
backoffLimit := int32(2)
|
||||
ttl := int32(10)
|
||||
|
||||
job := framework.NewTestJob("randomlySucceedOrFail", "rand-non-local", v1.RestartPolicyNever, parallelism, completions, nil, backoffLimit)
|
||||
job.Spec.TTLSecondsAfterFinished = &ttl
|
||||
job.ObjectMeta.Finalizers = []string{dummyFinalizer}
|
||||
defer cleanupJob(f, job)
|
||||
|
||||
framework.Logf("Create a Job %s/%s with TTL", job.Namespace, job.Name)
|
||||
job, err := framework.CreateJob(c, ns, job)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
framework.Logf("Wait for the Job to finish")
|
||||
err = framework.WaitForJobFinish(c, ns, job.Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
framework.Logf("Wait for TTL after finished controller to delete the Job")
|
||||
err = framework.WaitForJobDeleting(c, ns, job.Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
framework.Logf("Check Job's deletionTimestamp and compare with the time when the Job finished")
|
||||
job, err = framework.GetJob(c, ns, job.Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
finishTime := framework.JobFinishTime(job)
|
||||
finishTimeUTC := finishTime.UTC()
|
||||
Expect(finishTime.IsZero()).NotTo(BeTrue())
|
||||
|
||||
deleteAtUTC := job.ObjectMeta.DeletionTimestamp.UTC()
|
||||
Expect(deleteAtUTC).NotTo(BeNil())
|
||||
|
||||
expireAtUTC := finishTimeUTC.Add(time.Duration(ttl) * time.Second)
|
||||
Expect(deleteAtUTC.Before(expireAtUTC)).To(BeFalse())
|
||||
}
|
@ -59,6 +59,7 @@ go_library(
|
||||
"//pkg/cloudprovider/providers/gce:go_default_library",
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/controller/deployment/util:go_default_library",
|
||||
"//pkg/controller/job:go_default_library",
|
||||
"//pkg/controller/nodelifecycle:go_default_library",
|
||||
"//pkg/controller/service:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
jobutil "k8s.io/kubernetes/pkg/controller/job"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -181,8 +182,8 @@ func WaitForAllJobPodsRunning(c clientset.Interface, ns, jobName string, paralle
|
||||
})
|
||||
}
|
||||
|
||||
// WaitForJobFinish uses c to wait for compeletions to complete for the Job jobName in namespace ns.
|
||||
func WaitForJobFinish(c clientset.Interface, ns, jobName string, completions int32) error {
|
||||
// WaitForJobComplete uses c to wait for compeletions to complete for the Job jobName in namespace ns.
|
||||
func WaitForJobComplete(c clientset.Interface, ns, jobName string, completions int32) error {
|
||||
return wait.Poll(Poll, JobTimeout, func() (bool, error) {
|
||||
curr, err := c.BatchV1().Jobs(ns).Get(jobName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
@ -192,6 +193,17 @@ func WaitForJobFinish(c clientset.Interface, ns, jobName string, completions int
|
||||
})
|
||||
}
|
||||
|
||||
// WaitForJobFinish uses c to wait for the Job jobName in namespace ns to finish (either Failed or Complete).
|
||||
func WaitForJobFinish(c clientset.Interface, ns, jobName string) error {
|
||||
return wait.PollImmediate(Poll, JobTimeout, func() (bool, error) {
|
||||
curr, err := c.BatchV1().Jobs(ns).Get(jobName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return jobutil.IsJobFinished(curr), nil
|
||||
})
|
||||
}
|
||||
|
||||
// WaitForJobFailure uses c to wait for up to timeout for the Job named jobName in namespace ns to fail.
|
||||
func WaitForJobFailure(c clientset.Interface, ns, jobName string, timeout time.Duration, reason string) error {
|
||||
return wait.Poll(Poll, timeout, func() (bool, error) {
|
||||
@ -239,6 +251,18 @@ func CheckForAllJobPodsRunning(c clientset.Interface, ns, jobName string, parall
|
||||
return count == parallelism, nil
|
||||
}
|
||||
|
||||
// WaitForAllJobPodsRunning wait for all pods for the Job named jobName in namespace ns
|
||||
// to be deleted.
|
||||
func WaitForAllJobPodsGone(c clientset.Interface, ns, jobName string) error {
|
||||
return wait.PollImmediate(Poll, JobTimeout, func() (bool, error) {
|
||||
pods, err := GetJobPods(c, ns, jobName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return len(pods.Items) == 0, nil
|
||||
})
|
||||
}
|
||||
|
||||
func newBool(val bool) *bool {
|
||||
p := new(bool)
|
||||
*p = val
|
||||
@ -250,7 +274,7 @@ type updateJobFunc func(*batch.Job)
|
||||
func UpdateJobWithRetries(c clientset.Interface, namespace, name string, applyUpdate updateJobFunc) (job *batch.Job, err error) {
|
||||
jobs := c.BatchV1().Jobs(namespace)
|
||||
var updateErr error
|
||||
pollErr := wait.PollImmediate(10*time.Millisecond, 1*time.Minute, func() (bool, error) {
|
||||
pollErr := wait.PollImmediate(Poll, JobTimeout, func() (bool, error) {
|
||||
if job, err = jobs.Get(name, metav1.GetOptions{}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -268,3 +292,25 @@ func UpdateJobWithRetries(c clientset.Interface, namespace, name string, applyUp
|
||||
}
|
||||
return job, pollErr
|
||||
}
|
||||
|
||||
// WaitForJobDeleting uses c to wait for the Job jobName in namespace ns to have
|
||||
// a non-nil deletionTimestamp (i.e. being deleted).
|
||||
func WaitForJobDeleting(c clientset.Interface, ns, jobName string) error {
|
||||
return wait.PollImmediate(Poll, JobTimeout, func() (bool, error) {
|
||||
curr, err := c.BatchV1().Jobs(ns).Get(jobName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return curr.ObjectMeta.DeletionTimestamp != nil, nil
|
||||
})
|
||||
}
|
||||
|
||||
func JobFinishTime(finishedJob *batch.Job) metav1.Time {
|
||||
var finishTime metav1.Time
|
||||
for _, c := range finishedJob.Status.Conditions {
|
||||
if (c.Type == batch.JobComplete || c.Type == batch.JobFailed) && c.Status == v1.ConditionTrue {
|
||||
return c.LastTransitionTime
|
||||
}
|
||||
}
|
||||
return finishTime
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user