pvc_protection_controller_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. /*
  2. Copyright 2017 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 pvcprotection
  14. import (
  15. "errors"
  16. "reflect"
  17. "testing"
  18. "time"
  19. "github.com/davecgh/go-spew/spew"
  20. v1 "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/apimachinery/pkg/types"
  27. "k8s.io/client-go/informers"
  28. "k8s.io/client-go/kubernetes/fake"
  29. clienttesting "k8s.io/client-go/testing"
  30. "k8s.io/klog"
  31. "k8s.io/kubernetes/pkg/controller"
  32. volumeutil "k8s.io/kubernetes/pkg/volume/util"
  33. )
  34. type reaction struct {
  35. verb string
  36. resource string
  37. reactorfn clienttesting.ReactionFunc
  38. }
  39. const (
  40. defaultNS = "default"
  41. defaultPVCName = "pvc1"
  42. defaultPodName = "pod1"
  43. defaultNodeName = "node1"
  44. defaultUID = "uid1"
  45. )
  46. func pod() *v1.Pod {
  47. return &v1.Pod{
  48. ObjectMeta: metav1.ObjectMeta{
  49. Name: defaultPodName,
  50. Namespace: defaultNS,
  51. UID: defaultUID,
  52. },
  53. Spec: v1.PodSpec{
  54. NodeName: defaultNodeName,
  55. },
  56. Status: v1.PodStatus{
  57. Phase: v1.PodPending,
  58. },
  59. }
  60. }
  61. func unscheduled(pod *v1.Pod) *v1.Pod {
  62. pod.Spec.NodeName = ""
  63. return pod
  64. }
  65. func withPVC(pvcName string, pod *v1.Pod) *v1.Pod {
  66. volume := v1.Volume{
  67. Name: pvcName,
  68. VolumeSource: v1.VolumeSource{
  69. PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
  70. ClaimName: pvcName,
  71. },
  72. },
  73. }
  74. pod.Spec.Volumes = append(pod.Spec.Volumes, volume)
  75. return pod
  76. }
  77. func withEmptyDir(pod *v1.Pod) *v1.Pod {
  78. volume := v1.Volume{
  79. Name: "emptyDir",
  80. VolumeSource: v1.VolumeSource{
  81. EmptyDir: &v1.EmptyDirVolumeSource{},
  82. },
  83. }
  84. pod.Spec.Volumes = append(pod.Spec.Volumes, volume)
  85. return pod
  86. }
  87. func withStatus(phase v1.PodPhase, pod *v1.Pod) *v1.Pod {
  88. pod.Status.Phase = phase
  89. return pod
  90. }
  91. func withUID(uid types.UID, pod *v1.Pod) *v1.Pod {
  92. pod.ObjectMeta.UID = uid
  93. return pod
  94. }
  95. func pvc() *v1.PersistentVolumeClaim {
  96. return &v1.PersistentVolumeClaim{
  97. ObjectMeta: metav1.ObjectMeta{
  98. Name: defaultPVCName,
  99. Namespace: defaultNS,
  100. },
  101. }
  102. }
  103. func withProtectionFinalizer(pvc *v1.PersistentVolumeClaim) *v1.PersistentVolumeClaim {
  104. pvc.Finalizers = append(pvc.Finalizers, volumeutil.PVCProtectionFinalizer)
  105. return pvc
  106. }
  107. func deleted(pvc *v1.PersistentVolumeClaim) *v1.PersistentVolumeClaim {
  108. pvc.DeletionTimestamp = &metav1.Time{}
  109. return pvc
  110. }
  111. func generateUpdateErrorFunc(t *testing.T, failures int) clienttesting.ReactionFunc {
  112. i := 0
  113. return func(action clienttesting.Action) (bool, runtime.Object, error) {
  114. i++
  115. if i <= failures {
  116. // Update fails
  117. update, ok := action.(clienttesting.UpdateAction)
  118. if !ok {
  119. t.Fatalf("Reactor got non-update action: %+v", action)
  120. }
  121. acc, _ := meta.Accessor(update.GetObject())
  122. return true, nil, apierrors.NewForbidden(update.GetResource().GroupResource(), acc.GetName(), errors.New("Mock error"))
  123. }
  124. // Update succeeds
  125. return false, nil, nil
  126. }
  127. }
  128. func TestPVCProtectionController(t *testing.T) {
  129. pvcGVR := schema.GroupVersionResource{
  130. Group: v1.GroupName,
  131. Version: "v1",
  132. Resource: "persistentvolumeclaims",
  133. }
  134. podGVR := schema.GroupVersionResource{
  135. Group: v1.GroupName,
  136. Version: "v1",
  137. Resource: "pods",
  138. }
  139. podGVK := schema.GroupVersionKind{
  140. Group: v1.GroupName,
  141. Version: "v1",
  142. Kind: "Pod",
  143. }
  144. tests := []struct {
  145. name string
  146. // Object to insert into fake kubeclient before the test starts.
  147. initialObjects []runtime.Object
  148. // Whether not to insert the content of initialObjects into the
  149. // informers before the test starts. Set it to true to simulate the case
  150. // where informers have not been notified yet of certain API objects.
  151. informersAreLate bool
  152. // Optional client reactors.
  153. reactors []reaction
  154. // PVC event to simulate. This PVC will be automatically added to
  155. // initialObjects.
  156. updatedPVC *v1.PersistentVolumeClaim
  157. // Pod event to simulate. This Pod will be automatically added to
  158. // initialObjects.
  159. updatedPod *v1.Pod
  160. // Pod event to simulate. This Pod is *not* added to
  161. // initialObjects.
  162. deletedPod *v1.Pod
  163. // List of expected kubeclient actions that should happen during the
  164. // test.
  165. expectedActions []clienttesting.Action
  166. storageObjectInUseProtectionEnabled bool
  167. }{
  168. //
  169. // PVC events
  170. //
  171. {
  172. name: "StorageObjectInUseProtection Enabled, PVC without finalizer -> finalizer is added",
  173. updatedPVC: pvc(),
  174. expectedActions: []clienttesting.Action{
  175. clienttesting.NewUpdateAction(pvcGVR, defaultNS, withProtectionFinalizer(pvc())),
  176. },
  177. storageObjectInUseProtectionEnabled: true,
  178. },
  179. {
  180. name: "StorageObjectInUseProtection Disabled, PVC without finalizer -> finalizer is not added",
  181. updatedPVC: pvc(),
  182. expectedActions: []clienttesting.Action{},
  183. storageObjectInUseProtectionEnabled: false,
  184. },
  185. {
  186. name: "PVC with finalizer -> no action",
  187. updatedPVC: withProtectionFinalizer(pvc()),
  188. expectedActions: []clienttesting.Action{},
  189. storageObjectInUseProtectionEnabled: true,
  190. },
  191. {
  192. name: "saving PVC finalizer fails -> controller retries",
  193. updatedPVC: pvc(),
  194. reactors: []reaction{
  195. {
  196. verb: "update",
  197. resource: "persistentvolumeclaims",
  198. reactorfn: generateUpdateErrorFunc(t, 2 /* update fails twice*/),
  199. },
  200. },
  201. expectedActions: []clienttesting.Action{
  202. // This fails
  203. clienttesting.NewUpdateAction(pvcGVR, defaultNS, withProtectionFinalizer(pvc())),
  204. // This fails too
  205. clienttesting.NewUpdateAction(pvcGVR, defaultNS, withProtectionFinalizer(pvc())),
  206. // This succeeds
  207. clienttesting.NewUpdateAction(pvcGVR, defaultNS, withProtectionFinalizer(pvc())),
  208. },
  209. storageObjectInUseProtectionEnabled: true,
  210. },
  211. {
  212. name: "StorageObjectInUseProtection Enabled, deleted PVC with finalizer -> finalizer is removed",
  213. updatedPVC: deleted(withProtectionFinalizer(pvc())),
  214. expectedActions: []clienttesting.Action{
  215. clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}),
  216. clienttesting.NewUpdateAction(pvcGVR, defaultNS, deleted(pvc())),
  217. },
  218. storageObjectInUseProtectionEnabled: true,
  219. },
  220. {
  221. name: "StorageObjectInUseProtection Disabled, deleted PVC with finalizer -> finalizer is removed",
  222. updatedPVC: deleted(withProtectionFinalizer(pvc())),
  223. expectedActions: []clienttesting.Action{
  224. clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}),
  225. clienttesting.NewUpdateAction(pvcGVR, defaultNS, deleted(pvc())),
  226. },
  227. storageObjectInUseProtectionEnabled: false,
  228. },
  229. {
  230. name: "finalizer removal fails -> controller retries",
  231. updatedPVC: deleted(withProtectionFinalizer(pvc())),
  232. reactors: []reaction{
  233. {
  234. verb: "update",
  235. resource: "persistentvolumeclaims",
  236. reactorfn: generateUpdateErrorFunc(t, 2 /* update fails twice*/),
  237. },
  238. },
  239. expectedActions: []clienttesting.Action{
  240. clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}),
  241. // Fails
  242. clienttesting.NewUpdateAction(pvcGVR, defaultNS, deleted(pvc())),
  243. clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}),
  244. // Fails too
  245. clienttesting.NewUpdateAction(pvcGVR, defaultNS, deleted(pvc())),
  246. clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}),
  247. // Succeeds
  248. clienttesting.NewUpdateAction(pvcGVR, defaultNS, deleted(pvc())),
  249. },
  250. storageObjectInUseProtectionEnabled: true,
  251. },
  252. {
  253. name: "deleted PVC with finalizer + pod with the PVC exists -> finalizer is not removed",
  254. initialObjects: []runtime.Object{
  255. withPVC(defaultPVCName, pod()),
  256. },
  257. updatedPVC: deleted(withProtectionFinalizer(pvc())),
  258. expectedActions: []clienttesting.Action{},
  259. },
  260. {
  261. name: "deleted PVC with finalizer + pod with unrelated PVC and EmptyDir exists -> finalizer is removed",
  262. initialObjects: []runtime.Object{
  263. withEmptyDir(withPVC("unrelatedPVC", pod())),
  264. },
  265. updatedPVC: deleted(withProtectionFinalizer(pvc())),
  266. expectedActions: []clienttesting.Action{
  267. clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}),
  268. clienttesting.NewUpdateAction(pvcGVR, defaultNS, deleted(pvc())),
  269. },
  270. storageObjectInUseProtectionEnabled: true,
  271. },
  272. {
  273. name: "deleted PVC with finalizer + pod with the PVC finished but is not deleted -> finalizer is not removed",
  274. initialObjects: []runtime.Object{
  275. withStatus(v1.PodFailed, withPVC(defaultPVCName, pod())),
  276. },
  277. updatedPVC: deleted(withProtectionFinalizer(pvc())),
  278. expectedActions: []clienttesting.Action{},
  279. storageObjectInUseProtectionEnabled: true,
  280. },
  281. {
  282. name: "deleted PVC with finalizer + pod with the PVC exists but is not in the Informer's cache yet -> finalizer is not removed",
  283. initialObjects: []runtime.Object{
  284. withPVC(defaultPVCName, pod()),
  285. },
  286. informersAreLate: true,
  287. updatedPVC: deleted(withProtectionFinalizer(pvc())),
  288. expectedActions: []clienttesting.Action{
  289. clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}),
  290. },
  291. storageObjectInUseProtectionEnabled: true,
  292. },
  293. //
  294. // Pod events
  295. //
  296. {
  297. name: "updated running Pod -> no action",
  298. initialObjects: []runtime.Object{
  299. deleted(withProtectionFinalizer(pvc())),
  300. },
  301. updatedPod: withStatus(v1.PodRunning, withPVC(defaultPVCName, pod())),
  302. expectedActions: []clienttesting.Action{},
  303. storageObjectInUseProtectionEnabled: true,
  304. },
  305. {
  306. name: "updated finished Pod -> finalizer is not removed",
  307. initialObjects: []runtime.Object{
  308. deleted(withProtectionFinalizer(pvc())),
  309. },
  310. updatedPod: withStatus(v1.PodSucceeded, withPVC(defaultPVCName, pod())),
  311. expectedActions: []clienttesting.Action{},
  312. storageObjectInUseProtectionEnabled: true,
  313. },
  314. {
  315. name: "updated unscheduled Pod -> finalizer is removed",
  316. initialObjects: []runtime.Object{
  317. deleted(withProtectionFinalizer(pvc())),
  318. },
  319. updatedPod: unscheduled(withPVC(defaultPVCName, pod())),
  320. expectedActions: []clienttesting.Action{
  321. clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}),
  322. clienttesting.NewUpdateAction(pvcGVR, defaultNS, deleted(pvc())),
  323. },
  324. storageObjectInUseProtectionEnabled: true,
  325. },
  326. {
  327. name: "deleted running Pod -> finalizer is removed",
  328. initialObjects: []runtime.Object{
  329. deleted(withProtectionFinalizer(pvc())),
  330. },
  331. deletedPod: withStatus(v1.PodRunning, withPVC(defaultPVCName, pod())),
  332. expectedActions: []clienttesting.Action{
  333. clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}),
  334. clienttesting.NewUpdateAction(pvcGVR, defaultNS, deleted(pvc())),
  335. },
  336. storageObjectInUseProtectionEnabled: true,
  337. },
  338. {
  339. name: "pod delete and create with same namespaced name seen as an update, old pod used deleted PVC -> finalizer is removed",
  340. initialObjects: []runtime.Object{
  341. deleted(withProtectionFinalizer(pvc())),
  342. },
  343. deletedPod: withPVC(defaultPVCName, pod()),
  344. updatedPod: withUID("uid2", pod()),
  345. expectedActions: []clienttesting.Action{
  346. clienttesting.NewListAction(podGVR, podGVK, defaultNS, metav1.ListOptions{}),
  347. clienttesting.NewUpdateAction(pvcGVR, defaultNS, deleted(pvc())),
  348. },
  349. storageObjectInUseProtectionEnabled: true,
  350. },
  351. {
  352. name: "pod delete and create with same namespaced name seen as an update, old pod used non-deleted PVC -> finalizer is not removed",
  353. initialObjects: []runtime.Object{
  354. withProtectionFinalizer(pvc()),
  355. },
  356. deletedPod: withPVC(defaultPVCName, pod()),
  357. updatedPod: withUID("uid2", pod()),
  358. expectedActions: []clienttesting.Action{},
  359. storageObjectInUseProtectionEnabled: true,
  360. },
  361. {
  362. name: "pod delete and create with same namespaced name seen as an update, both pods reference deleted PVC -> finalizer is not removed",
  363. initialObjects: []runtime.Object{
  364. deleted(withProtectionFinalizer(pvc())),
  365. },
  366. deletedPod: withPVC(defaultPVCName, pod()),
  367. updatedPod: withUID("uid2", withPVC(defaultPVCName, pod())),
  368. expectedActions: []clienttesting.Action{},
  369. storageObjectInUseProtectionEnabled: true,
  370. },
  371. {
  372. name: "pod update from unscheduled to scheduled, deleted PVC is referenced -> finalizer is not removed",
  373. initialObjects: []runtime.Object{
  374. deleted(withProtectionFinalizer(pvc())),
  375. },
  376. deletedPod: unscheduled(withPVC(defaultPVCName, pod())),
  377. updatedPod: withPVC(defaultPVCName, pod()),
  378. expectedActions: []clienttesting.Action{},
  379. storageObjectInUseProtectionEnabled: true,
  380. },
  381. }
  382. for _, test := range tests {
  383. // Create initial data for client and informers.
  384. var (
  385. clientObjs []runtime.Object
  386. informersObjs []runtime.Object
  387. )
  388. if test.updatedPVC != nil {
  389. clientObjs = append(clientObjs, test.updatedPVC)
  390. informersObjs = append(informersObjs, test.updatedPVC)
  391. }
  392. if test.updatedPod != nil {
  393. clientObjs = append(clientObjs, test.updatedPod)
  394. informersObjs = append(informersObjs, test.updatedPod)
  395. }
  396. clientObjs = append(clientObjs, test.initialObjects...)
  397. if !test.informersAreLate {
  398. informersObjs = append(informersObjs, test.initialObjects...)
  399. }
  400. // Create client with initial data
  401. client := fake.NewSimpleClientset(clientObjs...)
  402. // Create informers
  403. informers := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc())
  404. pvcInformer := informers.Core().V1().PersistentVolumeClaims()
  405. podInformer := informers.Core().V1().Pods()
  406. // Populate the informers with initial objects so the controller can
  407. // Get() and List() it.
  408. for _, obj := range informersObjs {
  409. switch obj.(type) {
  410. case *v1.PersistentVolumeClaim:
  411. pvcInformer.Informer().GetStore().Add(obj)
  412. case *v1.Pod:
  413. podInformer.Informer().GetStore().Add(obj)
  414. default:
  415. t.Fatalf("Unknown initalObject type: %+v", obj)
  416. }
  417. }
  418. // Add reactor to inject test errors.
  419. for _, reactor := range test.reactors {
  420. client.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactorfn)
  421. }
  422. // Create the controller
  423. ctrl := NewPVCProtectionController(pvcInformer, podInformer, client, test.storageObjectInUseProtectionEnabled)
  424. // Start the test by simulating an event
  425. if test.updatedPVC != nil {
  426. ctrl.pvcAddedUpdated(test.updatedPVC)
  427. }
  428. switch {
  429. case test.deletedPod != nil && test.updatedPod != nil && test.deletedPod.Namespace == test.updatedPod.Namespace && test.deletedPod.Name == test.updatedPod.Name:
  430. ctrl.podAddedDeletedUpdated(test.deletedPod, test.updatedPod, false)
  431. case test.updatedPod != nil:
  432. ctrl.podAddedDeletedUpdated(nil, test.updatedPod, false)
  433. case test.deletedPod != nil:
  434. ctrl.podAddedDeletedUpdated(nil, test.deletedPod, true)
  435. }
  436. // Process the controller queue until we get expected results
  437. timeout := time.Now().Add(10 * time.Second)
  438. lastReportedActionCount := 0
  439. for {
  440. if time.Now().After(timeout) {
  441. t.Errorf("Test %q: timed out", test.name)
  442. break
  443. }
  444. if ctrl.queue.Len() > 0 {
  445. klog.V(5).Infof("Test %q: %d events queue, processing one", test.name, ctrl.queue.Len())
  446. ctrl.processNextWorkItem()
  447. }
  448. if ctrl.queue.Len() > 0 {
  449. // There is still some work in the queue, process it now
  450. continue
  451. }
  452. currentActionCount := len(client.Actions())
  453. if currentActionCount < len(test.expectedActions) {
  454. // Do not log every wait, only when the action count changes.
  455. if lastReportedActionCount < currentActionCount {
  456. klog.V(5).Infof("Test %q: got %d actions out of %d, waiting for the rest", test.name, currentActionCount, len(test.expectedActions))
  457. lastReportedActionCount = currentActionCount
  458. }
  459. // The test expected more to happen, wait for the actions.
  460. // Most probably it's exponential backoff
  461. time.Sleep(10 * time.Millisecond)
  462. continue
  463. }
  464. break
  465. }
  466. actions := client.Actions()
  467. for i, action := range actions {
  468. if len(test.expectedActions) < i+1 {
  469. t.Errorf("Test %q: %d unexpected actions: %+v", test.name, len(actions)-len(test.expectedActions), spew.Sdump(actions[i:]))
  470. break
  471. }
  472. expectedAction := test.expectedActions[i]
  473. if !reflect.DeepEqual(expectedAction, action) {
  474. t.Errorf("Test %q: action %d\nExpected:\n%s\ngot:\n%s", test.name, i, spew.Sdump(expectedAction), spew.Sdump(action))
  475. }
  476. }
  477. if len(test.expectedActions) > len(actions) {
  478. t.Errorf("Test %q: %d additional expected actions", test.name, len(test.expectedActions)-len(actions))
  479. for _, a := range test.expectedActions[len(actions):] {
  480. t.Logf(" %+v", a)
  481. }
  482. }
  483. }
  484. }