expand_controller_test.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. /*
  2. Copyright 2019 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 expand
  14. import (
  15. "encoding/json"
  16. "fmt"
  17. "reflect"
  18. "regexp"
  19. "testing"
  20. "k8s.io/api/core/v1"
  21. storagev1 "k8s.io/api/storage/v1"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/runtime"
  24. "k8s.io/apimachinery/pkg/types"
  25. "k8s.io/apimachinery/pkg/util/strategicpatch"
  26. utilfeature "k8s.io/apiserver/pkg/util/feature"
  27. "k8s.io/client-go/informers"
  28. coretesting "k8s.io/client-go/testing"
  29. featuregatetesting "k8s.io/component-base/featuregate/testing"
  30. csitranslationplugins "k8s.io/csi-translation-lib/plugins"
  31. "k8s.io/kubernetes/pkg/controller"
  32. controllervolumetesting "k8s.io/kubernetes/pkg/controller/volume/attachdetach/testing"
  33. "k8s.io/kubernetes/pkg/features"
  34. "k8s.io/kubernetes/pkg/volume"
  35. "k8s.io/kubernetes/pkg/volume/awsebs"
  36. "k8s.io/kubernetes/pkg/volume/util/operationexecutor"
  37. volumetypes "k8s.io/kubernetes/pkg/volume/util/types"
  38. )
  39. func TestSyncHandler(t *testing.T) {
  40. tests := []struct {
  41. name string
  42. csiMigrationEnabled bool
  43. storageClass *storagev1.StorageClass
  44. pvcKey string
  45. pv *v1.PersistentVolume
  46. pvc *v1.PersistentVolumeClaim
  47. expansionCalled bool
  48. hasError bool
  49. expectedAnnotation map[string]string
  50. }{
  51. {
  52. name: "when pvc has no PV binding",
  53. pvc: getFakePersistentVolumeClaim("no-pv-pvc", "", "", ""),
  54. pvcKey: "default/no-pv-pvc",
  55. hasError: true,
  56. },
  57. {
  58. name: "when pvc has no storageclass",
  59. pv: getFakePersistentVolume("vol-1", csitranslationplugins.AWSEBSInTreePluginName, "no-sc-pvc-vol-1"),
  60. pvc: getFakePersistentVolumeClaim("no-sc-pvc", "vol-1", "", "no-sc-pvc-vol-1"),
  61. pvcKey: "default/no-sc-pvc",
  62. },
  63. {
  64. name: "when pvc storageclass is missing",
  65. pv: getFakePersistentVolume("vol-2", csitranslationplugins.AWSEBSInTreePluginName, "missing-sc-pvc-vol-2"),
  66. pvc: getFakePersistentVolumeClaim("missing-sc-pvc", "vol-2", "resizable", "missing-sc-pvc-vol-2"),
  67. pvcKey: "default/missing-sc-pvc",
  68. },
  69. {
  70. name: "when pvc and pv has everything for in-tree plugin",
  71. pv: getFakePersistentVolume("vol-3", csitranslationplugins.AWSEBSInTreePluginName, "good-pvc-vol-3"),
  72. pvc: getFakePersistentVolumeClaim("good-pvc", "vol-3", "resizable2", "good-pvc-vol-3"),
  73. storageClass: getFakeStorageClass("resizable2", csitranslationplugins.AWSEBSInTreePluginName),
  74. pvcKey: "default/good-pvc",
  75. expansionCalled: true,
  76. expectedAnnotation: map[string]string{volumetypes.VolumeResizerKey: csitranslationplugins.AWSEBSInTreePluginName},
  77. },
  78. {
  79. name: "when csi migration is enabled for a in-tree plugin",
  80. csiMigrationEnabled: true,
  81. pv: getFakePersistentVolume("vol-4", csitranslationplugins.AWSEBSInTreePluginName, "csi-pvc-vol-4"),
  82. pvc: getFakePersistentVolumeClaim("csi-pvc", "vol-4", "resizable3", "csi-pvc-vol-4"),
  83. storageClass: getFakeStorageClass("resizable3", csitranslationplugins.AWSEBSInTreePluginName),
  84. pvcKey: "default/csi-pvc",
  85. expectedAnnotation: map[string]string{volumetypes.VolumeResizerKey: csitranslationplugins.AWSEBSDriverName},
  86. },
  87. {
  88. name: "for csi plugin without migration path",
  89. pv: getFakePersistentVolume("vol-5", "com.csi.ceph", "ceph-csi-pvc-vol-5"),
  90. pvc: getFakePersistentVolumeClaim("ceph-csi-pvc", "vol-5", "resizable4", "ceph-csi-pvc-vol-5"),
  91. storageClass: getFakeStorageClass("resizable4", "com.csi.ceph"),
  92. pvcKey: "default/ceph-csi-pvc",
  93. expansionCalled: false,
  94. hasError: false,
  95. },
  96. }
  97. for _, tc := range tests {
  98. test := tc
  99. fakeKubeClient := controllervolumetesting.CreateTestClient()
  100. informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc())
  101. pvcInformer := informerFactory.Core().V1().PersistentVolumeClaims()
  102. pvInformer := informerFactory.Core().V1().PersistentVolumes()
  103. storageClassInformer := informerFactory.Storage().V1().StorageClasses()
  104. pvc := test.pvc
  105. if tc.pv != nil {
  106. informerFactory.Core().V1().PersistentVolumes().Informer().GetIndexer().Add(tc.pv)
  107. }
  108. if tc.pvc != nil {
  109. informerFactory.Core().V1().PersistentVolumeClaims().Informer().GetIndexer().Add(pvc)
  110. }
  111. allPlugins := []volume.VolumePlugin{}
  112. allPlugins = append(allPlugins, awsebs.ProbeVolumePlugins()...)
  113. if tc.storageClass != nil {
  114. informerFactory.Storage().V1().StorageClasses().Informer().GetIndexer().Add(tc.storageClass)
  115. }
  116. expc, err := NewExpandController(fakeKubeClient, pvcInformer, pvInformer, storageClassInformer, nil, allPlugins)
  117. if err != nil {
  118. t.Fatalf("error creating expand controller : %v", err)
  119. }
  120. if test.csiMigrationEnabled {
  121. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIMigration, true)()
  122. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIMigrationAWS, true)()
  123. } else {
  124. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIMigration, false)()
  125. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIMigrationAWS, false)()
  126. }
  127. var expController *expandController
  128. expController, _ = expc.(*expandController)
  129. var expansionCalled bool
  130. expController.operationGenerator = operationexecutor.NewFakeOGCounter(func() (error, error) {
  131. expansionCalled = true
  132. return nil, nil
  133. })
  134. fakeKubeClient.AddReactor("patch", "persistentvolumeclaims", func(action coretesting.Action) (bool, runtime.Object, error) {
  135. if action.GetSubresource() == "status" {
  136. patchActionaction, _ := action.(coretesting.PatchAction)
  137. pvc, err = applyPVCPatch(pvc, patchActionaction.GetPatch())
  138. if err != nil {
  139. return false, nil, err
  140. }
  141. return true, pvc, nil
  142. }
  143. return true, pvc, nil
  144. })
  145. err = expController.syncHandler(test.pvcKey)
  146. if err != nil && !test.hasError {
  147. t.Fatalf("for: %s; unexpected error while running handler : %v", test.name, err)
  148. }
  149. if err == nil && test.hasError {
  150. t.Fatalf("for: %s; unexpected success", test.name)
  151. }
  152. if expansionCalled != test.expansionCalled {
  153. t.Fatalf("for: %s; expected expansionCalled to be %v but was %v", test.name, test.expansionCalled, expansionCalled)
  154. }
  155. if len(test.expectedAnnotation) != 0 && !reflect.DeepEqual(test.expectedAnnotation, pvc.Annotations) {
  156. t.Fatalf("for: %s; expected %v annotations, got %v", test.name, test.expectedAnnotation, pvc.Annotations)
  157. }
  158. }
  159. }
  160. func applyPVCPatch(originalPVC *v1.PersistentVolumeClaim, patch []byte) (*v1.PersistentVolumeClaim, error) {
  161. pvcData, err := json.Marshal(originalPVC)
  162. if err != nil {
  163. return nil, fmt.Errorf("failed to marshal pvc with %v", err)
  164. }
  165. updated, err := strategicpatch.StrategicMergePatch(pvcData, patch, v1.PersistentVolumeClaim{})
  166. if err != nil {
  167. return nil, fmt.Errorf("failed to apply patch on pvc %v", err)
  168. }
  169. updatedPVC := &v1.PersistentVolumeClaim{}
  170. if err := json.Unmarshal(updated, updatedPVC); err != nil {
  171. return nil, fmt.Errorf("failed to unmarshal updated pvc : %v", err)
  172. }
  173. return updatedPVC, nil
  174. }
  175. func getFakePersistentVolume(volumeName, pluginName string, pvcUID types.UID) *v1.PersistentVolume {
  176. pv := &v1.PersistentVolume{
  177. ObjectMeta: metav1.ObjectMeta{Name: volumeName},
  178. Spec: v1.PersistentVolumeSpec{
  179. PersistentVolumeSource: v1.PersistentVolumeSource{},
  180. ClaimRef: &v1.ObjectReference{
  181. Namespace: "default",
  182. },
  183. },
  184. }
  185. if pvcUID != "" {
  186. pv.Spec.ClaimRef.UID = pvcUID
  187. }
  188. if matched, _ := regexp.MatchString(`csi`, pluginName); matched {
  189. pv.Spec.PersistentVolumeSource.CSI = &v1.CSIPersistentVolumeSource{
  190. Driver: pluginName,
  191. VolumeHandle: volumeName,
  192. }
  193. } else {
  194. pv.Spec.PersistentVolumeSource.AWSElasticBlockStore = &v1.AWSElasticBlockStoreVolumeSource{
  195. VolumeID: volumeName,
  196. FSType: "ext4",
  197. }
  198. }
  199. return pv
  200. }
  201. func getFakePersistentVolumeClaim(pvcName, volumeName, scName string, uid types.UID) *v1.PersistentVolumeClaim {
  202. pvc := &v1.PersistentVolumeClaim{
  203. ObjectMeta: metav1.ObjectMeta{Name: pvcName, Namespace: "default", UID: uid},
  204. Spec: v1.PersistentVolumeClaimSpec{},
  205. }
  206. if volumeName != "" {
  207. pvc.Spec.VolumeName = volumeName
  208. }
  209. if scName != "" {
  210. pvc.Spec.StorageClassName = &scName
  211. }
  212. return pvc
  213. }
  214. func getFakeStorageClass(scName, pluginName string) *storagev1.StorageClass {
  215. return &storagev1.StorageClass{
  216. ObjectMeta: metav1.ObjectMeta{Name: scName},
  217. Provisioner: pluginName,
  218. }
  219. }