diff --git a/pkg/controller/BUILD b/pkg/controller/BUILD index 00e7f8efa30..e2902c7c544 100644 --- a/pkg/controller/BUILD +++ b/pkg/controller/BUILD @@ -136,6 +136,7 @@ filegroup( "//pkg/controller/volume/events:all-srcs", "//pkg/controller/volume/expand:all-srcs", "//pkg/controller/volume/persistentvolume:all-srcs", + "//pkg/controller/volume/protectionutil:all-srcs", "//pkg/controller/volume/pvcprotection:all-srcs", "//pkg/controller/volume/pvprotection:all-srcs", ], diff --git a/pkg/controller/volume/protectionutil/BUILD b/pkg/controller/volume/protectionutil/BUILD new file mode 100644 index 00000000000..990e948d427 --- /dev/null +++ b/pkg/controller/volume/protectionutil/BUILD @@ -0,0 +1,37 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["utils.go"], + importpath = "k8s.io/kubernetes/pkg/controller/volume/protectionutil", + visibility = ["//visibility:public"], + deps = [ + "//pkg/util/slice:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["utils_test.go"], + embed = [":go_default_library"], + deps = [ + "//pkg/volume/util: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"], +) diff --git a/pkg/controller/volume/protectionutil/utils.go b/pkg/controller/volume/protectionutil/utils.go new file mode 100644 index 00000000000..c1a8c896248 --- /dev/null +++ b/pkg/controller/volume/protectionutil/utils.go @@ -0,0 +1,34 @@ +/* +Copyright 2019 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 protectionutil + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/util/slice" +) + +// IsDeletionCandidate checks if object is candidate to be deleted +func IsDeletionCandidate(obj metav1.Object, finalizer string) bool { + return obj.GetDeletionTimestamp() != nil && slice.ContainsString(obj.GetFinalizers(), + finalizer, nil) +} + +// NeedToAddFinalizer checks if need to add finalizer to object +func NeedToAddFinalizer(obj metav1.Object, finalizer string) bool { + return obj.GetDeletionTimestamp() == nil && !slice.ContainsString(obj.GetFinalizers(), + finalizer, nil) +} diff --git a/pkg/controller/volume/protectionutil/utils_test.go b/pkg/controller/volume/protectionutil/utils_test.go new file mode 100644 index 00000000000..abc5256de0c --- /dev/null +++ b/pkg/controller/volume/protectionutil/utils_test.go @@ -0,0 +1,267 @@ +/* +Copyright 2019 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 protectionutil + +import ( + "testing" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/volume/util" +) + +const ( + defaultNS = "default" + defaultPVCName = "pvc1" + defaultPVName = "pv1" +) + +type TestCase struct { + name string + obj metav1.Object + finalizer string + result bool +} + +func pvc() *v1.PersistentVolumeClaim { + return &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: defaultPVCName, + Namespace: defaultNS, + }, + } +} + +func pv() *v1.PersistentVolume { + return &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: defaultPVName, + }, + } +} + +func TestIsDeletionCandidateLackDeleteTimeAndFinalizer(t *testing.T) { + + tests := []TestCase{ + { + name: "pv lacks delete time and finalizer", + obj: pv(), + finalizer: util.PVProtectionFinalizer, + result: false, + }, + { + name: "pvc lacks delete time and finalizer", + obj: pvc(), + finalizer: util.PVCProtectionFinalizer, + + result: false, + }, + } + for _, test := range tests { + if test.result != IsDeletionCandidate(test.obj, test.finalizer) { + t.Error(test.name) + } + } +} + +func TestIsDeletionCandidateLackDeleteTime(t *testing.T) { + pv := pv() + pv.SetFinalizers([]string{util.PVProtectionFinalizer}) + pvc := pvc() + pvc.SetFinalizers([]string{util.PVCProtectionFinalizer}) + tests := []TestCase{ + { + name: "pv lacks delete time", + obj: pv, + finalizer: util.PVProtectionFinalizer, + result: false, + }, + { + name: "pvc lacks delete time", + obj: pvc, + finalizer: util.PVCProtectionFinalizer, + + result: false, + }, + } + for _, test := range tests { + if test.result != IsDeletionCandidate(test.obj, test.finalizer) { + t.Error(test.name) + } + } +} + +func TestIsDeletionCandidateLackFinalizer(t *testing.T) { + + pv := pv() + pv.SetDeletionTimestamp(&metav1.Time{}) + pvc := pvc() + pvc.SetDeletionTimestamp(&metav1.Time{}) + tests := []TestCase{ + { + name: "pv lacks finalizer", + obj: pv, + finalizer: util.PVProtectionFinalizer, + result: false, + }, + { + name: "pvc lacks finalizer", + obj: pvc, + finalizer: util.PVCProtectionFinalizer, + result: false, + }, + } + for _, test := range tests { + if test.result != IsDeletionCandidate(test.obj, test.finalizer) { + t.Error(test.name) + } + } +} + +func TestIsDeletionCandidateSuccess(t *testing.T) { + + pv := pv() + pv.SetDeletionTimestamp(&metav1.Time{}) + pv.SetFinalizers([]string{util.PVProtectionFinalizer}) + pvc := pvc() + pvc.SetDeletionTimestamp(&metav1.Time{}) + pvc.SetFinalizers([]string{util.PVCProtectionFinalizer}) + + tests := []TestCase{ + { + name: "pv is to delete", + obj: pv, + finalizer: util.PVProtectionFinalizer, + result: true, + }, + { + name: "pvc is to delete", + obj: pvc, + finalizer: util.PVCProtectionFinalizer, + result: true, + }, + } + for _, test := range tests { + if test.result != IsDeletionCandidate(test.obj, test.finalizer) { + t.Error(test.name) + } + } +} + +func TestNeedToAddFinalizerHasDeleteTimeAndFinalizer(t *testing.T) { + pv := pv() + pv.SetDeletionTimestamp(&metav1.Time{}) + pv.SetFinalizers([]string{util.PVProtectionFinalizer}) + pvc := pvc() + pvc.SetDeletionTimestamp(&metav1.Time{}) + pvc.SetFinalizers([]string{util.PVCProtectionFinalizer}) + + tests := []TestCase{ + { + name: "pv has delete time and finalizer", + obj: pv, + finalizer: util.PVProtectionFinalizer, + result: false, + }, + { + name: "pvc has delete time and finalizer", + obj: pvc, + finalizer: util.PVCProtectionFinalizer, + result: false, + }, + } + for _, test := range tests { + if test.result != NeedToAddFinalizer(test.obj, test.finalizer) { + t.Error(test.name) + } + } +} + +func TestNeedToAddFinalizerHasDeleteTime(t *testing.T) { + pv := pv() + pv.SetDeletionTimestamp(&metav1.Time{}) + pvc := pvc() + pvc.SetDeletionTimestamp(&metav1.Time{}) + tests := []TestCase{ + { + name: "pv has delete", + obj: pv, + finalizer: util.PVProtectionFinalizer, + result: false, + }, + { + name: "pvc has delete", + obj: pvc, + finalizer: util.PVCProtectionFinalizer, + result: false, + }, + } + for _, test := range tests { + if test.result != NeedToAddFinalizer(test.obj, test.finalizer) { + t.Error(test.name) + } + } +} + +func TestNeedToAddFinalizerHasFinalizer(t *testing.T) { + pv := pv() + pv.SetFinalizers([]string{util.PVProtectionFinalizer}) + pvc := pvc() + pvc.SetFinalizers([]string{util.PVCProtectionFinalizer}) + + tests := []TestCase{ + { + name: "pv has finalizer", + obj: pv, + finalizer: util.PVProtectionFinalizer, + result: false, + }, + { + name: "pvc has finalizer", + obj: pvc, + finalizer: util.PVCProtectionFinalizer, + result: false, + }, + } + for _, test := range tests { + if test.result != NeedToAddFinalizer(test.obj, test.finalizer) { + t.Error(test.name) + } + } +} + +func TestNeedToAddFinalizerSuccess(t *testing.T) { + tests := []TestCase{ + { + name: "pv needs add finalizer", + obj: pv(), + finalizer: util.PVProtectionFinalizer, + result: true, + }, + { + name: "pvc needs add finalizer", + obj: pvc(), + finalizer: util.PVCProtectionFinalizer, + result: true, + }, + } + for _, test := range tests { + if test.result != NeedToAddFinalizer(test.obj, test.finalizer) { + t.Error(test.name) + } + } +} diff --git a/pkg/controller/volume/pvcprotection/BUILD b/pkg/controller/volume/pvcprotection/BUILD index 041d1fb4b95..fcf1058f3d9 100644 --- a/pkg/controller/volume/pvcprotection/BUILD +++ b/pkg/controller/volume/pvcprotection/BUILD @@ -7,6 +7,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//pkg/controller:go_default_library", + "//pkg/controller/volume/protectionutil:go_default_library", "//pkg/util/metrics:go_default_library", "//pkg/util/slice:go_default_library", "//pkg/volume/util:go_default_library", diff --git a/pkg/controller/volume/pvcprotection/pvc_protection_controller.go b/pkg/controller/volume/pvcprotection/pvc_protection_controller.go index a832476c552..a79d538611c 100644 --- a/pkg/controller/volume/pvcprotection/pvc_protection_controller.go +++ b/pkg/controller/volume/pvcprotection/pvc_protection_controller.go @@ -32,6 +32,7 @@ import ( "k8s.io/client-go/util/workqueue" "k8s.io/klog" "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/volume/protectionutil" "k8s.io/kubernetes/pkg/util/metrics" "k8s.io/kubernetes/pkg/util/slice" volumeutil "k8s.io/kubernetes/pkg/volume/util" @@ -157,7 +158,7 @@ func (c *Controller) processPVC(pvcNamespace, pvcName string) error { return err } - if isDeletionCandidate(pvc) { + if protectionutil.IsDeletionCandidate(pvc, volumeutil.PVCProtectionFinalizer) { // PVC should be deleted. Check if it's used and remove finalizer if // it's not. isUsed, err := c.isBeingUsed(pvc) @@ -169,7 +170,7 @@ func (c *Controller) processPVC(pvcNamespace, pvcName string) error { } } - if needToAddFinalizer(pvc) { + if protectionutil.NeedToAddFinalizer(pvc, volumeutil.PVCProtectionFinalizer) { // PVC is not being deleted -> it should have the finalizer. The // finalizer should be added by admission plugin, this is just to add // the finalizer to old PVCs that were created before the admission @@ -250,7 +251,7 @@ func (c *Controller) pvcAddedUpdated(obj interface{}) { } klog.V(4).Infof("Got event on PVC %s", key) - if needToAddFinalizer(pvc) || isDeletionCandidate(pvc) { + if protectionutil.NeedToAddFinalizer(pvc, volumeutil.PVCProtectionFinalizer) || protectionutil.IsDeletionCandidate(pvc, volumeutil.PVCProtectionFinalizer) { c.queue.Add(key) } } @@ -285,11 +286,3 @@ func (c *Controller) podAddedDeletedUpdated(obj interface{}, deleted bool) { } } } - -func isDeletionCandidate(pvc *v1.PersistentVolumeClaim) bool { - return pvc.ObjectMeta.DeletionTimestamp != nil && slice.ContainsString(pvc.ObjectMeta.Finalizers, volumeutil.PVCProtectionFinalizer, nil) -} - -func needToAddFinalizer(pvc *v1.PersistentVolumeClaim) bool { - return pvc.ObjectMeta.DeletionTimestamp == nil && !slice.ContainsString(pvc.ObjectMeta.Finalizers, volumeutil.PVCProtectionFinalizer, nil) -} diff --git a/pkg/controller/volume/pvcprotection/pvc_protection_controller_test.go b/pkg/controller/volume/pvcprotection/pvc_protection_controller_test.go index c8eb87bf652..ff164278c65 100644 --- a/pkg/controller/volume/pvcprotection/pvc_protection_controller_test.go +++ b/pkg/controller/volume/pvcprotection/pvc_protection_controller_test.go @@ -23,7 +23,6 @@ import ( "time" "github.com/davecgh/go-spew/spew" - "k8s.io/klog" "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -34,6 +33,7 @@ import ( "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" clienttesting "k8s.io/client-go/testing" + "k8s.io/klog" "k8s.io/kubernetes/pkg/controller" volumeutil "k8s.io/kubernetes/pkg/volume/util" ) diff --git a/pkg/controller/volume/pvprotection/BUILD b/pkg/controller/volume/pvprotection/BUILD index cdea8771770..c1af6ae73e3 100644 --- a/pkg/controller/volume/pvprotection/BUILD +++ b/pkg/controller/volume/pvprotection/BUILD @@ -7,6 +7,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//pkg/controller:go_default_library", + "//pkg/controller/volume/protectionutil:go_default_library", "//pkg/util/metrics:go_default_library", "//pkg/util/slice:go_default_library", "//pkg/volume/util:go_default_library", diff --git a/pkg/controller/volume/pvprotection/pv_protection_controller.go b/pkg/controller/volume/pvprotection/pv_protection_controller.go index d14a7a06d1a..f892408c4f2 100644 --- a/pkg/controller/volume/pvprotection/pv_protection_controller.go +++ b/pkg/controller/volume/pvprotection/pv_protection_controller.go @@ -31,6 +31,7 @@ import ( "k8s.io/client-go/util/workqueue" "k8s.io/klog" "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/volume/protectionutil" "k8s.io/kubernetes/pkg/util/metrics" "k8s.io/kubernetes/pkg/util/slice" volumeutil "k8s.io/kubernetes/pkg/volume/util" @@ -135,7 +136,7 @@ func (c *Controller) processPV(pvName string) error { return err } - if isDeletionCandidate(pv) { + if protectionutil.IsDeletionCandidate(pv, volumeutil.PVProtectionFinalizer) { // PV should be deleted. Check if it's used and remove finalizer if // it's not. isUsed := c.isBeingUsed(pv) @@ -144,7 +145,7 @@ func (c *Controller) processPV(pvName string) error { } } - if needToAddFinalizer(pv) { + if protectionutil.NeedToAddFinalizer(pv, volumeutil.PVProtectionFinalizer) { // PV is not being deleted -> it should have the finalizer. The // finalizer should be added by admission plugin, this is just to add // the finalizer to old PVs that were created before the admission @@ -202,15 +203,7 @@ func (c *Controller) pvAddedUpdated(obj interface{}) { } klog.V(4).Infof("Got event on PV %s", pv.Name) - if needToAddFinalizer(pv) || isDeletionCandidate(pv) { + if protectionutil.NeedToAddFinalizer(pv, volumeutil.PVProtectionFinalizer) || protectionutil.IsDeletionCandidate(pv, volumeutil.PVProtectionFinalizer) { c.queue.Add(pv.Name) } } - -func isDeletionCandidate(pv *v1.PersistentVolume) bool { - return pv.ObjectMeta.DeletionTimestamp != nil && slice.ContainsString(pv.ObjectMeta.Finalizers, volumeutil.PVProtectionFinalizer, nil) -} - -func needToAddFinalizer(pv *v1.PersistentVolume) bool { - return pv.ObjectMeta.DeletionTimestamp == nil && !slice.ContainsString(pv.ObjectMeta.Finalizers, volumeutil.PVProtectionFinalizer, nil) -} diff --git a/pkg/controller/volume/pvprotection/pv_protection_controller_test.go b/pkg/controller/volume/pvprotection/pv_protection_controller_test.go index 75caba4dab1..db4ac859377 100644 --- a/pkg/controller/volume/pvprotection/pv_protection_controller_test.go +++ b/pkg/controller/volume/pvprotection/pv_protection_controller_test.go @@ -23,7 +23,6 @@ import ( "time" "github.com/davecgh/go-spew/spew" - "k8s.io/klog" "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -34,6 +33,7 @@ import ( "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" clienttesting "k8s.io/client-go/testing" + "k8s.io/klog" "k8s.io/kubernetes/pkg/controller" volumeutil "k8s.io/kubernetes/pkg/volume/util" )