1
16
17 package storage
18
19 import (
20 "context"
21 "fmt"
22 "path"
23
24 "github.com/onsi/ginkgo/v2"
25 "github.com/onsi/gomega"
26 v1 "k8s.io/api/core/v1"
27 storagev1 "k8s.io/api/storage/v1"
28 "k8s.io/apimachinery/pkg/api/resource"
29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30 utilerrors "k8s.io/apimachinery/pkg/util/errors"
31 clientset "k8s.io/client-go/kubernetes"
32 "k8s.io/kubernetes/test/e2e/feature"
33 "k8s.io/kubernetes/test/e2e/framework"
34 e2enode "k8s.io/kubernetes/test/e2e/framework/node"
35 e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
36 e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
37 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
38 "k8s.io/kubernetes/test/e2e/storage/testsuites"
39 "k8s.io/kubernetes/test/e2e/storage/utils"
40 imageutils "k8s.io/kubernetes/test/utils/image"
41 admissionapi "k8s.io/pod-security-admission/api"
42 )
43
44 var _ = utils.SIGDescribe(feature.Flexvolumes, "Mounted flexvolume volume expand", framework.WithSlow(), func() {
45 var (
46 c clientset.Interface
47 ns string
48 err error
49 pvc *v1.PersistentVolumeClaim
50 resizableSc *storagev1.StorageClass
51 nodeName string
52 nodeKeyValueLabel map[string]string
53 nodeLabelValue string
54 nodeKey string
55 node *v1.Node
56 )
57
58 f := framework.NewDefaultFramework("mounted-flexvolume-expand")
59 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
60 ginkgo.BeforeEach(func(ctx context.Context) {
61 e2eskipper.SkipUnlessProviderIs("aws", "gce", "local")
62 e2eskipper.SkipUnlessMasterOSDistroIs("debian", "ubuntu", "gci", "custom")
63 e2eskipper.SkipUnlessNodeOSDistroIs("debian", "ubuntu", "gci", "custom")
64 e2eskipper.SkipUnlessSSHKeyPresent()
65 c = f.ClientSet
66 ns = f.Namespace.Name
67 framework.ExpectNoError(e2enode.WaitForAllNodesSchedulable(ctx, c, f.Timeouts.NodeSchedulable))
68 var err error
69
70 node, err = e2enode.GetRandomReadySchedulableNode(ctx, f.ClientSet)
71 framework.ExpectNoError(err)
72 nodeName = node.Name
73
74 nodeKey = "mounted_flexvolume_expand_" + ns
75 nodeLabelValue = ns
76 nodeKeyValueLabel = map[string]string{nodeKey: nodeLabelValue}
77 e2enode.AddOrUpdateLabelOnNode(c, nodeName, nodeKey, nodeLabelValue)
78 ginkgo.DeferCleanup(e2enode.RemoveLabelOffNode, c, nodeName, nodeKey)
79
80 test := testsuites.StorageClassTest{
81 Name: "flexvolume-resize",
82 Timeouts: f.Timeouts,
83 ClaimSize: "2Gi",
84 AllowVolumeExpansion: true,
85 Provisioner: "flex-expand",
86 }
87
88 resizableSc, err = c.StorageV1().StorageClasses().Create(ctx, newStorageClass(test, ns, "resizing"), metav1.CreateOptions{})
89 if err != nil {
90 fmt.Printf("storage class creation error: %v\n", err)
91 }
92 framework.ExpectNoError(err, "Error creating resizable storage class: %v", err)
93 if !*resizableSc.AllowVolumeExpansion {
94 framework.Failf("Class %s does not allow volume expansion", resizableSc.Name)
95 }
96
97 pvc = e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{
98 StorageClassName: &(resizableSc.Name),
99 ClaimSize: "2Gi",
100 }, ns)
101 pvc, err = c.CoreV1().PersistentVolumeClaims(pvc.Namespace).Create(ctx, pvc, metav1.CreateOptions{})
102 framework.ExpectNoError(err, "Error creating pvc: %v", err)
103 ginkgo.DeferCleanup(func(ctx context.Context) {
104 framework.Logf("AfterEach: Cleaning up resources for mounted volume resize")
105 if errs := e2epv.PVPVCCleanup(ctx, c, ns, nil, pvc); len(errs) > 0 {
106 framework.Failf("AfterEach: Failed to delete PVC and/or PV. Errors: %v", utilerrors.NewAggregate(errs))
107 }
108 })
109 })
110
111 ginkgo.It("should be resizable when mounted", func(ctx context.Context) {
112 e2eskipper.SkipUnlessSSHKeyPresent()
113
114 driver := "dummy-attachable"
115
116 ginkgo.By(fmt.Sprintf("installing flexvolume %s on node %s as %s", path.Join(driverDir, driver), node.Name, driver))
117 installFlex(ctx, c, node, "k8s", driver, path.Join(driverDir, driver))
118 ginkgo.By(fmt.Sprintf("installing flexvolume %s on (master) node %s as %s", path.Join(driverDir, driver), node.Name, driver))
119 installFlex(ctx, c, nil, "k8s", driver, path.Join(driverDir, driver))
120
121 pv := e2epv.MakePersistentVolume(e2epv.PersistentVolumeConfig{
122 PVSource: v1.PersistentVolumeSource{
123 FlexVolume: &v1.FlexPersistentVolumeSource{
124 Driver: "k8s/" + driver,
125 }},
126 NamePrefix: "pv-",
127 StorageClassName: resizableSc.Name,
128 VolumeMode: pvc.Spec.VolumeMode,
129 })
130
131 _, err = e2epv.CreatePV(ctx, c, f.Timeouts, pv)
132 framework.ExpectNoError(err, "Error creating pv %v", err)
133
134 ginkgo.By("Waiting for PVC to be in bound phase")
135 pvcClaims := []*v1.PersistentVolumeClaim{pvc}
136 var pvs []*v1.PersistentVolume
137
138 pvs, err = e2epv.WaitForPVClaimBoundPhase(ctx, c, pvcClaims, framework.ClaimProvisionTimeout)
139 framework.ExpectNoError(err, "Failed waiting for PVC to be bound %v", err)
140 gomega.Expect(pvs).To(gomega.HaveLen(1))
141
142 var pod *v1.Pod
143 ginkgo.By("Creating pod")
144 pod, err = createNginxPod(ctx, c, ns, nodeKeyValueLabel, pvcClaims)
145 framework.ExpectNoError(err, "Failed to create pod %v", err)
146 ginkgo.DeferCleanup(e2epod.DeletePodWithWait, c, pod)
147
148 ginkgo.By("Waiting for pod to go to 'running' state")
149 err = e2epod.WaitForPodNameRunningInNamespace(ctx, f.ClientSet, pod.ObjectMeta.Name, f.Namespace.Name)
150 framework.ExpectNoError(err, "Pod didn't go to 'running' state %v", err)
151
152 ginkgo.By("Expanding current pvc")
153 newSize := resource.MustParse("6Gi")
154 newPVC, err := testsuites.ExpandPVCSize(ctx, pvc, newSize, c)
155 framework.ExpectNoError(err, "While updating pvc for more size")
156 pvc = newPVC
157 gomega.Expect(pvc).NotTo(gomega.BeNil())
158
159 pvcSize := pvc.Spec.Resources.Requests[v1.ResourceStorage]
160 if pvcSize.Cmp(newSize) != 0 {
161 framework.Failf("error updating pvc size %q", pvc.Name)
162 }
163
164 ginkgo.By("Waiting for cloudprovider resize to finish")
165 err = testsuites.WaitForControllerVolumeResize(ctx, pvc, c, totalResizeWaitPeriod)
166 framework.ExpectNoError(err, "While waiting for pvc resize to finish")
167
168 ginkgo.By("Waiting for file system resize to finish")
169 pvc, err = testsuites.WaitForFSResize(ctx, pvc, c)
170 framework.ExpectNoError(err, "while waiting for fs resize to finish")
171
172 pvcConditions := pvc.Status.Conditions
173 gomega.Expect(pvcConditions).To(gomega.BeEmpty(), "pvc should not have conditions")
174 })
175 })
176
177
178 func createNginxPod(ctx context.Context, client clientset.Interface, namespace string, nodeSelector map[string]string, pvclaims []*v1.PersistentVolumeClaim) (*v1.Pod, error) {
179 pod := makeNginxPod(namespace, nodeSelector, pvclaims)
180 pod, err := client.CoreV1().Pods(namespace).Create(ctx, pod, metav1.CreateOptions{})
181 if err != nil {
182 return nil, fmt.Errorf("pod Create API error: %w", err)
183 }
184
185 err = e2epod.WaitForPodNameRunningInNamespace(ctx, client, pod.Name, namespace)
186 if err != nil {
187 return pod, fmt.Errorf("pod %q is not Running: %w", pod.Name, err)
188 }
189
190 pod, err = client.CoreV1().Pods(namespace).Get(ctx, pod.Name, metav1.GetOptions{})
191 if err != nil {
192 return pod, fmt.Errorf("pod Get API error: %w", err)
193 }
194 return pod, nil
195 }
196
197
198 func makeNginxPod(ns string, nodeSelector map[string]string, pvclaims []*v1.PersistentVolumeClaim) *v1.Pod {
199 podSpec := &v1.Pod{
200 TypeMeta: metav1.TypeMeta{
201 Kind: "Pod",
202 APIVersion: "v1",
203 },
204 ObjectMeta: metav1.ObjectMeta{
205 GenerateName: "pvc-tester-",
206 Namespace: ns,
207 },
208 Spec: v1.PodSpec{
209 Containers: []v1.Container{
210 {
211 Name: "write-pod",
212 Image: imageutils.GetE2EImage(imageutils.Nginx),
213 Ports: []v1.ContainerPort{
214 {
215 Name: "http-server",
216 ContainerPort: 80,
217 },
218 },
219 },
220 },
221 },
222 }
223 var volumeMounts = make([]v1.VolumeMount, len(pvclaims))
224 var volumes = make([]v1.Volume, len(pvclaims))
225 for index, pvclaim := range pvclaims {
226 volumename := fmt.Sprintf("volume%v", index+1)
227 volumeMounts[index] = v1.VolumeMount{Name: volumename, MountPath: "/mnt/" + volumename}
228 volumes[index] = v1.Volume{Name: volumename, VolumeSource: v1.VolumeSource{PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ClaimName: pvclaim.Name, ReadOnly: false}}}
229 }
230 podSpec.Spec.Containers[0].VolumeMounts = volumeMounts
231 podSpec.Spec.Volumes = volumes
232 if nodeSelector != nil {
233 podSpec.Spec.NodeSelector = nodeSelector
234 }
235 return podSpec
236 }
237
View as plain text