pvc_protection_controller_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  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. "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. type reaction struct {
  34. verb string
  35. resource string
  36. reactorfn clienttesting.ReactionFunc
  37. }
  38. const (
  39. defaultNS = "default"
  40. defaultPVCName = "pvc1"
  41. defaultPodName = "pod1"
  42. defaultNodeName = "node1"
  43. )
  44. func pod() *v1.Pod {
  45. return &v1.Pod{
  46. ObjectMeta: metav1.ObjectMeta{
  47. Name: defaultPodName,
  48. Namespace: defaultNS,
  49. },
  50. Spec: v1.PodSpec{
  51. NodeName: defaultNodeName,
  52. },
  53. Status: v1.PodStatus{
  54. Phase: v1.PodPending,
  55. },
  56. }
  57. }
  58. func unscheduled(pod *v1.Pod) *v1.Pod {
  59. pod.Spec.NodeName = ""
  60. return pod
  61. }
  62. func withPVC(pvcName string, pod *v1.Pod) *v1.Pod {
  63. volume := v1.Volume{
  64. Name: pvcName,
  65. VolumeSource: v1.VolumeSource{
  66. PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
  67. ClaimName: pvcName,
  68. },
  69. },
  70. }
  71. pod.Spec.Volumes = append(pod.Spec.Volumes, volume)
  72. return pod
  73. }
  74. func withEmptyDir(pod *v1.Pod) *v1.Pod {
  75. volume := v1.Volume{
  76. Name: "emptyDir",
  77. VolumeSource: v1.VolumeSource{
  78. EmptyDir: &v1.EmptyDirVolumeSource{},
  79. },
  80. }
  81. pod.Spec.Volumes = append(pod.Spec.Volumes, volume)
  82. return pod
  83. }
  84. func withStatus(phase v1.PodPhase, pod *v1.Pod) *v1.Pod {
  85. pod.Status.Phase = phase
  86. return pod
  87. }
  88. func pvc() *v1.PersistentVolumeClaim {
  89. return &v1.PersistentVolumeClaim{
  90. ObjectMeta: metav1.ObjectMeta{
  91. Name: defaultPVCName,
  92. Namespace: defaultNS,
  93. },
  94. }
  95. }
  96. func withProtectionFinalizer(pvc *v1.PersistentVolumeClaim) *v1.PersistentVolumeClaim {
  97. pvc.Finalizers = append(pvc.Finalizers, volumeutil.PVCProtectionFinalizer)
  98. return pvc
  99. }
  100. func deleted(pvc *v1.PersistentVolumeClaim) *v1.PersistentVolumeClaim {
  101. pvc.DeletionTimestamp = &metav1.Time{}
  102. return pvc
  103. }
  104. func generateUpdateErrorFunc(t *testing.T, failures int) clienttesting.ReactionFunc {
  105. i := 0
  106. return func(action clienttesting.Action) (bool, runtime.Object, error) {
  107. i++
  108. if i <= failures {
  109. // Update fails
  110. update, ok := action.(clienttesting.UpdateAction)
  111. if !ok {
  112. t.Fatalf("Reactor got non-update action: %+v", action)
  113. }
  114. acc, _ := meta.Accessor(update.GetObject())
  115. return true, nil, apierrors.NewForbidden(update.GetResource().GroupResource(), acc.GetName(), errors.New("Mock error"))
  116. }
  117. // Update succeeds
  118. return false, nil, nil
  119. }
  120. }
  121. func TestPVCProtectionController(t *testing.T) {
  122. pvcVer := schema.GroupVersionResource{
  123. Group: v1.GroupName,
  124. Version: "v1",
  125. Resource: "persistentvolumeclaims",
  126. }
  127. tests := []struct {
  128. name string
  129. // Object to insert into fake kubeclient before the test starts.
  130. initialObjects []runtime.Object
  131. // Optional client reactors.
  132. reactors []reaction
  133. // PVC event to simulate. This PVC will be automatically added to
  134. // initalObjects.
  135. updatedPVC *v1.PersistentVolumeClaim
  136. // Pod event to simulate. This Pod will be automatically added to
  137. // initalObjects.
  138. updatedPod *v1.Pod
  139. // Pod event to similate. This Pod is *not* added to
  140. // initalObjects.
  141. deletedPod *v1.Pod
  142. // List of expected kubeclient actions that should happen during the
  143. // test.
  144. expectedActions []clienttesting.Action
  145. storageObjectInUseProtectionEnabled bool
  146. }{
  147. //
  148. // PVC events
  149. //
  150. {
  151. name: "StorageObjectInUseProtection Enabled, PVC without finalizer -> finalizer is added",
  152. updatedPVC: pvc(),
  153. expectedActions: []clienttesting.Action{
  154. clienttesting.NewUpdateAction(pvcVer, defaultNS, withProtectionFinalizer(pvc())),
  155. },
  156. storageObjectInUseProtectionEnabled: true,
  157. },
  158. {
  159. name: "StorageObjectInUseProtection Disabled, PVC without finalizer -> finalizer is added",
  160. updatedPVC: pvc(),
  161. expectedActions: []clienttesting.Action{},
  162. storageObjectInUseProtectionEnabled: false,
  163. },
  164. {
  165. name: "PVC with finalizer -> no action",
  166. updatedPVC: withProtectionFinalizer(pvc()),
  167. expectedActions: []clienttesting.Action{},
  168. storageObjectInUseProtectionEnabled: true,
  169. },
  170. {
  171. name: "saving PVC finalizer fails -> controller retries",
  172. updatedPVC: pvc(),
  173. reactors: []reaction{
  174. {
  175. verb: "update",
  176. resource: "persistentvolumeclaims",
  177. reactorfn: generateUpdateErrorFunc(t, 2 /* update fails twice*/),
  178. },
  179. },
  180. expectedActions: []clienttesting.Action{
  181. // This fails
  182. clienttesting.NewUpdateAction(pvcVer, defaultNS, withProtectionFinalizer(pvc())),
  183. // This fails too
  184. clienttesting.NewUpdateAction(pvcVer, defaultNS, withProtectionFinalizer(pvc())),
  185. // This succeeds
  186. clienttesting.NewUpdateAction(pvcVer, defaultNS, withProtectionFinalizer(pvc())),
  187. },
  188. storageObjectInUseProtectionEnabled: true,
  189. },
  190. {
  191. name: "StorageObjectInUseProtection Enabled, deleted PVC with finalizer -> finalizer is removed",
  192. updatedPVC: deleted(withProtectionFinalizer(pvc())),
  193. expectedActions: []clienttesting.Action{
  194. clienttesting.NewUpdateAction(pvcVer, defaultNS, deleted(pvc())),
  195. },
  196. storageObjectInUseProtectionEnabled: true,
  197. },
  198. {
  199. name: "StorageObjectInUseProtection Disabled, deleted PVC with finalizer -> finalizer is removed",
  200. updatedPVC: deleted(withProtectionFinalizer(pvc())),
  201. expectedActions: []clienttesting.Action{
  202. clienttesting.NewUpdateAction(pvcVer, defaultNS, deleted(pvc())),
  203. },
  204. storageObjectInUseProtectionEnabled: false,
  205. },
  206. {
  207. name: "finalizer removal fails -> controller retries",
  208. updatedPVC: deleted(withProtectionFinalizer(pvc())),
  209. reactors: []reaction{
  210. {
  211. verb: "update",
  212. resource: "persistentvolumeclaims",
  213. reactorfn: generateUpdateErrorFunc(t, 2 /* update fails twice*/),
  214. },
  215. },
  216. expectedActions: []clienttesting.Action{
  217. // Fails
  218. clienttesting.NewUpdateAction(pvcVer, defaultNS, deleted(pvc())),
  219. // Fails too
  220. clienttesting.NewUpdateAction(pvcVer, defaultNS, deleted(pvc())),
  221. // Succeeds
  222. clienttesting.NewUpdateAction(pvcVer, defaultNS, deleted(pvc())),
  223. },
  224. storageObjectInUseProtectionEnabled: true,
  225. },
  226. {
  227. name: "deleted PVC with finalizer + pods with the PVC exists -> finalizer is not removed",
  228. initialObjects: []runtime.Object{
  229. withPVC(defaultPVCName, pod()),
  230. },
  231. updatedPVC: deleted(withProtectionFinalizer(pvc())),
  232. expectedActions: []clienttesting.Action{},
  233. },
  234. {
  235. name: "deleted PVC with finalizer + pods with unrelated PVC and EmptyDir exists -> finalizer is removed",
  236. initialObjects: []runtime.Object{
  237. withEmptyDir(withPVC("unrelatedPVC", pod())),
  238. },
  239. updatedPVC: deleted(withProtectionFinalizer(pvc())),
  240. expectedActions: []clienttesting.Action{
  241. clienttesting.NewUpdateAction(pvcVer, defaultNS, deleted(pvc())),
  242. },
  243. storageObjectInUseProtectionEnabled: true,
  244. },
  245. {
  246. name: "deleted PVC with finalizer + pods with the PVC finished but is not deleted -> finalizer is not removed",
  247. initialObjects: []runtime.Object{
  248. withStatus(v1.PodFailed, withPVC(defaultPVCName, pod())),
  249. },
  250. updatedPVC: deleted(withProtectionFinalizer(pvc())),
  251. expectedActions: []clienttesting.Action{},
  252. storageObjectInUseProtectionEnabled: true,
  253. },
  254. //
  255. // Pod events
  256. //
  257. {
  258. name: "updated running Pod -> no action",
  259. initialObjects: []runtime.Object{
  260. deleted(withProtectionFinalizer(pvc())),
  261. },
  262. updatedPod: withStatus(v1.PodRunning, withPVC(defaultPVCName, pod())),
  263. expectedActions: []clienttesting.Action{},
  264. storageObjectInUseProtectionEnabled: true,
  265. },
  266. {
  267. name: "updated finished Pod -> finalizer is not removed",
  268. initialObjects: []runtime.Object{
  269. deleted(withProtectionFinalizer(pvc())),
  270. },
  271. updatedPod: withStatus(v1.PodSucceeded, withPVC(defaultPVCName, pod())),
  272. expectedActions: []clienttesting.Action{},
  273. storageObjectInUseProtectionEnabled: true,
  274. },
  275. {
  276. name: "updated unscheduled Pod -> finalizer is removed",
  277. initialObjects: []runtime.Object{
  278. deleted(withProtectionFinalizer(pvc())),
  279. },
  280. updatedPod: unscheduled(withPVC(defaultPVCName, pod())),
  281. expectedActions: []clienttesting.Action{
  282. clienttesting.NewUpdateAction(pvcVer, defaultNS, deleted(pvc())),
  283. },
  284. storageObjectInUseProtectionEnabled: true,
  285. },
  286. {
  287. name: "deleted running Pod -> finalizer is removed",
  288. initialObjects: []runtime.Object{
  289. deleted(withProtectionFinalizer(pvc())),
  290. },
  291. deletedPod: withStatus(v1.PodRunning, withPVC(defaultPVCName, pod())),
  292. expectedActions: []clienttesting.Action{
  293. clienttesting.NewUpdateAction(pvcVer, defaultNS, deleted(pvc())),
  294. },
  295. storageObjectInUseProtectionEnabled: true,
  296. },
  297. }
  298. for _, test := range tests {
  299. // Create client with initial data
  300. objs := test.initialObjects
  301. if test.updatedPVC != nil {
  302. objs = append(objs, test.updatedPVC)
  303. }
  304. if test.updatedPod != nil {
  305. objs = append(objs, test.updatedPod)
  306. }
  307. client := fake.NewSimpleClientset(objs...)
  308. // Create informers
  309. informers := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc())
  310. pvcInformer := informers.Core().V1().PersistentVolumeClaims()
  311. podInformer := informers.Core().V1().Pods()
  312. // Populate the informers with initial objects so the controller can
  313. // Get() and List() it.
  314. for _, obj := range objs {
  315. switch obj.(type) {
  316. case *v1.PersistentVolumeClaim:
  317. pvcInformer.Informer().GetStore().Add(obj)
  318. case *v1.Pod:
  319. podInformer.Informer().GetStore().Add(obj)
  320. default:
  321. t.Fatalf("Unknown initalObject type: %+v", obj)
  322. }
  323. }
  324. // Add reactor to inject test errors.
  325. for _, reactor := range test.reactors {
  326. client.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactorfn)
  327. }
  328. // Create the controller
  329. ctrl := NewPVCProtectionController(pvcInformer, podInformer, client, test.storageObjectInUseProtectionEnabled)
  330. // Start the test by simulating an event
  331. if test.updatedPVC != nil {
  332. ctrl.pvcAddedUpdated(test.updatedPVC)
  333. }
  334. if test.updatedPod != nil {
  335. ctrl.podAddedDeletedUpdated(test.updatedPod, false)
  336. }
  337. if test.deletedPod != nil {
  338. ctrl.podAddedDeletedUpdated(test.deletedPod, true)
  339. }
  340. // Process the controller queue until we get expected results
  341. timeout := time.Now().Add(10 * time.Second)
  342. lastReportedActionCount := 0
  343. for {
  344. if time.Now().After(timeout) {
  345. t.Errorf("Test %q: timed out", test.name)
  346. break
  347. }
  348. if ctrl.queue.Len() > 0 {
  349. klog.V(5).Infof("Test %q: %d events queue, processing one", test.name, ctrl.queue.Len())
  350. ctrl.processNextWorkItem()
  351. }
  352. if ctrl.queue.Len() > 0 {
  353. // There is still some work in the queue, process it now
  354. continue
  355. }
  356. currentActionCount := len(client.Actions())
  357. if currentActionCount < len(test.expectedActions) {
  358. // Do not log evey wait, only when the action count changes.
  359. if lastReportedActionCount < currentActionCount {
  360. klog.V(5).Infof("Test %q: got %d actions out of %d, waiting for the rest", test.name, currentActionCount, len(test.expectedActions))
  361. lastReportedActionCount = currentActionCount
  362. }
  363. // The test expected more to happen, wait for the actions.
  364. // Most probably it's exponential backoff
  365. time.Sleep(10 * time.Millisecond)
  366. continue
  367. }
  368. break
  369. }
  370. actions := client.Actions()
  371. for i, action := range actions {
  372. if len(test.expectedActions) < i+1 {
  373. t.Errorf("Test %q: %d unexpected actions: %+v", test.name, len(actions)-len(test.expectedActions), spew.Sdump(actions[i:]))
  374. break
  375. }
  376. expectedAction := test.expectedActions[i]
  377. if !reflect.DeepEqual(expectedAction, action) {
  378. t.Errorf("Test %q: action %d\nExpected:\n%s\ngot:\n%s", test.name, i, spew.Sdump(expectedAction), spew.Sdump(action))
  379. }
  380. }
  381. if len(test.expectedActions) > len(actions) {
  382. t.Errorf("Test %q: %d additional expected actions", test.name, len(test.expectedActions)-len(actions))
  383. for _, a := range test.expectedActions[len(actions):] {
  384. t.Logf(" %+v", a)
  385. }
  386. }
  387. }
  388. }