pv_protection_controller_test.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. /*
  2. Copyright 2018 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package pvprotection
  14. import (
  15. "errors"
  16. "reflect"
  17. "testing"
  18. "time"
  19. "github.com/davecgh/go-spew/spew"
  20. "k8s.io/api/core/v1"
  21. apierrors "k8s.io/apimachinery/pkg/api/errors"
  22. "k8s.io/apimachinery/pkg/api/meta"
  23. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  24. "k8s.io/apimachinery/pkg/runtime"
  25. "k8s.io/apimachinery/pkg/runtime/schema"
  26. "k8s.io/client-go/informers"
  27. "k8s.io/client-go/kubernetes/fake"
  28. clienttesting "k8s.io/client-go/testing"
  29. "k8s.io/klog"
  30. "k8s.io/kubernetes/pkg/controller"
  31. volumeutil "k8s.io/kubernetes/pkg/volume/util"
  32. )
  33. const defaultPVName = "default-pv"
  34. type reaction struct {
  35. verb string
  36. resource string
  37. reactorfn clienttesting.ReactionFunc
  38. }
  39. func pv() *v1.PersistentVolume {
  40. return &v1.PersistentVolume{
  41. ObjectMeta: metav1.ObjectMeta{
  42. Name: defaultPVName,
  43. },
  44. }
  45. }
  46. func boundPV() *v1.PersistentVolume {
  47. return &v1.PersistentVolume{
  48. ObjectMeta: metav1.ObjectMeta{
  49. Name: defaultPVName,
  50. },
  51. Status: v1.PersistentVolumeStatus{
  52. Phase: v1.VolumeBound,
  53. },
  54. }
  55. }
  56. func withProtectionFinalizer(pv *v1.PersistentVolume) *v1.PersistentVolume {
  57. pv.Finalizers = append(pv.Finalizers, volumeutil.PVProtectionFinalizer)
  58. return pv
  59. }
  60. func generateUpdateErrorFunc(t *testing.T, failures int) clienttesting.ReactionFunc {
  61. i := 0
  62. return func(action clienttesting.Action) (bool, runtime.Object, error) {
  63. i++
  64. if i <= failures {
  65. // Update fails
  66. update, ok := action.(clienttesting.UpdateAction)
  67. if !ok {
  68. t.Fatalf("Reactor got non-update action: %+v", action)
  69. }
  70. acc, _ := meta.Accessor(update.GetObject())
  71. return true, nil, apierrors.NewForbidden(update.GetResource().GroupResource(), acc.GetName(), errors.New("Mock error"))
  72. }
  73. // Update succeeds
  74. return false, nil, nil
  75. }
  76. }
  77. func deleted(pv *v1.PersistentVolume) *v1.PersistentVolume {
  78. pv.DeletionTimestamp = &metav1.Time{}
  79. return pv
  80. }
  81. func TestPVProtectionController(t *testing.T) {
  82. pvVer := schema.GroupVersionResource{
  83. Group: v1.GroupName,
  84. Version: "v1",
  85. Resource: "persistentvolumes",
  86. }
  87. tests := []struct {
  88. name string
  89. // Object to insert into fake kubeclient before the test starts.
  90. initialObjects []runtime.Object
  91. // Optional client reactors.
  92. reactors []reaction
  93. // PV event to simulate. This PV will be automatically added to
  94. // initalObjects.
  95. updatedPV *v1.PersistentVolume
  96. // List of expected kubeclient actions that should happen during the
  97. // test.
  98. expectedActions []clienttesting.Action
  99. storageObjectInUseProtectionEnabled bool
  100. }{
  101. // PV events
  102. //
  103. {
  104. name: "StorageObjectInUseProtection Enabled, PV without finalizer -> finalizer is added",
  105. updatedPV: pv(),
  106. expectedActions: []clienttesting.Action{
  107. clienttesting.NewUpdateAction(pvVer, "", withProtectionFinalizer(pv())),
  108. },
  109. storageObjectInUseProtectionEnabled: true,
  110. },
  111. {
  112. name: "StorageObjectInUseProtection Disabled, PV without finalizer -> finalizer is added",
  113. updatedPV: pv(),
  114. expectedActions: []clienttesting.Action{},
  115. storageObjectInUseProtectionEnabled: false,
  116. },
  117. {
  118. name: "PVC with finalizer -> no action",
  119. updatedPV: withProtectionFinalizer(pv()),
  120. expectedActions: []clienttesting.Action{},
  121. storageObjectInUseProtectionEnabled: true,
  122. },
  123. {
  124. name: "saving PVC finalizer fails -> controller retries",
  125. updatedPV: pv(),
  126. reactors: []reaction{
  127. {
  128. verb: "update",
  129. resource: "persistentvolumes",
  130. reactorfn: generateUpdateErrorFunc(t, 2 /* update fails twice*/),
  131. },
  132. },
  133. expectedActions: []clienttesting.Action{
  134. // This fails
  135. clienttesting.NewUpdateAction(pvVer, "", withProtectionFinalizer(pv())),
  136. // This fails too
  137. clienttesting.NewUpdateAction(pvVer, "", withProtectionFinalizer(pv())),
  138. // This succeeds
  139. clienttesting.NewUpdateAction(pvVer, "", withProtectionFinalizer(pv())),
  140. },
  141. storageObjectInUseProtectionEnabled: true,
  142. },
  143. {
  144. name: "StorageObjectInUseProtection Enabled, deleted PV with finalizer -> finalizer is removed",
  145. updatedPV: deleted(withProtectionFinalizer(pv())),
  146. expectedActions: []clienttesting.Action{
  147. clienttesting.NewUpdateAction(pvVer, "", deleted(pv())),
  148. },
  149. storageObjectInUseProtectionEnabled: true,
  150. },
  151. {
  152. name: "StorageObjectInUseProtection Disabled, deleted PV with finalizer -> finalizer is removed",
  153. updatedPV: deleted(withProtectionFinalizer(pv())),
  154. expectedActions: []clienttesting.Action{
  155. clienttesting.NewUpdateAction(pvVer, "", deleted(pv())),
  156. },
  157. storageObjectInUseProtectionEnabled: false,
  158. },
  159. {
  160. name: "finalizer removal fails -> controller retries",
  161. updatedPV: deleted(withProtectionFinalizer(pv())),
  162. reactors: []reaction{
  163. {
  164. verb: "update",
  165. resource: "persistentvolumes",
  166. reactorfn: generateUpdateErrorFunc(t, 2 /* update fails twice*/),
  167. },
  168. },
  169. expectedActions: []clienttesting.Action{
  170. // Fails
  171. clienttesting.NewUpdateAction(pvVer, "", deleted(pv())),
  172. // Fails too
  173. clienttesting.NewUpdateAction(pvVer, "", deleted(pv())),
  174. // Succeeds
  175. clienttesting.NewUpdateAction(pvVer, "", deleted(pv())),
  176. },
  177. storageObjectInUseProtectionEnabled: true,
  178. },
  179. {
  180. name: "deleted PVC with finalizer + PV is bound -> finalizer is not removed",
  181. updatedPV: deleted(withProtectionFinalizer(boundPV())),
  182. expectedActions: []clienttesting.Action{},
  183. storageObjectInUseProtectionEnabled: true,
  184. },
  185. }
  186. for _, test := range tests {
  187. // Create client with initial data
  188. objs := test.initialObjects
  189. if test.updatedPV != nil {
  190. objs = append(objs, test.updatedPV)
  191. }
  192. client := fake.NewSimpleClientset(objs...)
  193. // Create informers
  194. informers := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc())
  195. pvInformer := informers.Core().V1().PersistentVolumes()
  196. // Populate the informers with initial objects so the controller can
  197. // Get() it.
  198. for _, obj := range objs {
  199. switch obj.(type) {
  200. case *v1.PersistentVolume:
  201. pvInformer.Informer().GetStore().Add(obj)
  202. default:
  203. t.Fatalf("Unknown initalObject type: %+v", obj)
  204. }
  205. }
  206. // Add reactor to inject test errors.
  207. for _, reactor := range test.reactors {
  208. client.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactorfn)
  209. }
  210. // Create the controller
  211. ctrl := NewPVProtectionController(pvInformer, client, test.storageObjectInUseProtectionEnabled)
  212. // Start the test by simulating an event
  213. if test.updatedPV != nil {
  214. ctrl.pvAddedUpdated(test.updatedPV)
  215. }
  216. // Process the controller queue until we get expected results
  217. timeout := time.Now().Add(10 * time.Second)
  218. lastReportedActionCount := 0
  219. for {
  220. if time.Now().After(timeout) {
  221. t.Errorf("Test %q: timed out", test.name)
  222. break
  223. }
  224. if ctrl.queue.Len() > 0 {
  225. klog.V(5).Infof("Test %q: %d events queue, processing one", test.name, ctrl.queue.Len())
  226. ctrl.processNextWorkItem()
  227. }
  228. if ctrl.queue.Len() > 0 {
  229. // There is still some work in the queue, process it now
  230. continue
  231. }
  232. currentActionCount := len(client.Actions())
  233. if currentActionCount < len(test.expectedActions) {
  234. // Do not log evey wait, only when the action count changes.
  235. if lastReportedActionCount < currentActionCount {
  236. klog.V(5).Infof("Test %q: got %d actions out of %d, waiting for the rest", test.name, currentActionCount, len(test.expectedActions))
  237. lastReportedActionCount = currentActionCount
  238. }
  239. // The test expected more to happen, wait for the actions.
  240. // Most probably it's exponential backoff
  241. time.Sleep(10 * time.Millisecond)
  242. continue
  243. }
  244. break
  245. }
  246. actions := client.Actions()
  247. if !reflect.DeepEqual(actions, test.expectedActions) {
  248. t.Errorf("Test %q: action not expected\nExpected:\n%s\ngot:\n%s", test.name, spew.Sdump(test.expectedActions), spew.Sdump(actions))
  249. }
  250. }
  251. }