expand_controller_test.go 9.2 KB

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