Skip to content

Commit 6b248e2

Browse files
committed
add test for vsphere driver snapshot configuration
1 parent f1a21f4 commit 6b248e2

File tree

3 files changed

+337
-1
lines changed

3 files changed

+337
-1
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ require (
4747
golang.org/x/net v0.19.0
4848
golang.org/x/oauth2 v0.10.0
4949
google.golang.org/grpc v1.58.3
50+
gopkg.in/ini.v1 v1.62.0
5051
gopkg.in/src-d/go-git.v4 v4.13.1
5152
gopkg.in/yaml.v2 v2.4.0
5253
k8s.io/api v0.29.0
@@ -266,7 +267,6 @@ require (
266267
google.golang.org/protobuf v1.31.0 // indirect
267268
gopkg.in/gcfg.v1 v1.2.3 // indirect
268269
gopkg.in/inf.v0 v0.9.1 // indirect
269-
gopkg.in/ini.v1 v1.62.0 // indirect
270270
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
271271
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
272272
gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect
Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
package storage
2+
3+
import (
4+
"context"
5+
"fmt"
6+
g "github.com/onsi/ginkgo/v2"
7+
o "github.com/onsi/gomega"
8+
operatorv1 "github.com/openshift/api/operator/v1"
9+
exutil "github.com/openshift/origin/test/extended/util"
10+
"gopkg.in/ini.v1"
11+
v1 "k8s.io/api/core/v1"
12+
"k8s.io/apimachinery/pkg/api/resource"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
"k8s.io/apimachinery/pkg/types"
15+
"k8s.io/kubernetes/test/e2e/framework"
16+
e2e "k8s.io/kubernetes/test/e2e/framework"
17+
k8simage "k8s.io/kubernetes/test/utils/image"
18+
"strings"
19+
"time"
20+
)
21+
22+
const (
23+
projectName = "csi-driver-configuration"
24+
providerName = "csi.vsphere.vmware.com"
25+
)
26+
27+
// This is [Serial] because it modifies clustercsidriver.
28+
var _ = g.Describe("[sig-storage][Feature:VSphereDriverConfiguration][Serial] vSphere CSI Driver Configuration", func() {
29+
defer g.GinkgoRecover()
30+
var oc = exutil.NewCLI(projectName)
31+
32+
var (
33+
originalDriverConfigSpec *operatorv1.CSIDriverConfigSpec
34+
snapshotOptions = map[string]map[string]string{
35+
"global": {
36+
"clusterCSIDriver": "globalMaxSnapshotsPerBlockVolume",
37+
"cloudConf": "global-max-snapshots-per-block-volume",
38+
},
39+
"vsan": {
40+
"clusterCSIDriver": "granularMaxSnapshotsPerBlockVolumeInVSAN",
41+
"cloudConf": "granular-max-snapshots-per-block-volume-vsan",
42+
},
43+
"vvol": {
44+
"clusterCSIDriver": "granularMaxSnapshotsPerBlockVolumeInVVOL",
45+
"cloudConf": "granular-max-snapshots-per-block-volume-vvol",
46+
},
47+
}
48+
)
49+
50+
g.BeforeEach(func() {
51+
//TODO: remove when GA
52+
if !exutil.IsTechPreviewNoUpgrade(oc) {
53+
g.Skip("this test is only expected to work with TechPreviewNoUpgrade clusters")
54+
}
55+
56+
if !framework.ProviderIs("vsphere") {
57+
g.Skip("this test is only expected to work with vSphere clusters")
58+
}
59+
60+
originalClusterCSIDriver, err := oc.AdminOperatorClient().OperatorV1().ClusterCSIDrivers().Get(context.Background(), providerName, metav1.GetOptions{})
61+
o.Expect(err).NotTo(o.HaveOccurred())
62+
63+
originalDriverConfigSpec = originalClusterCSIDriver.Spec.DriverConfig.DeepCopy()
64+
e2e.Logf("storing original driver config: %+v", originalDriverConfigSpec)
65+
})
66+
67+
g.AfterEach(func() {
68+
if originalDriverConfigSpec == nil {
69+
return
70+
}
71+
72+
clusterCSIDriver, err := oc.AdminOperatorClient().OperatorV1().ClusterCSIDrivers().Get(context.Background(), providerName, metav1.GetOptions{})
73+
o.Expect(err).NotTo(o.HaveOccurred())
74+
75+
e2e.Logf("restoring original driver config: %+v", originalDriverConfigSpec)
76+
clusterCSIDriver.Spec.DriverConfig = *originalDriverConfigSpec
77+
_, err = oc.AdminOperatorClient().OperatorV1().ClusterCSIDrivers().Update(context.Background(), clusterCSIDriver, metav1.UpdateOptions{})
78+
if err != nil {
79+
e2e.Failf("failed to update ClusterCSIDriver: %v", err)
80+
}
81+
o.Expect(err).NotTo(o.HaveOccurred())
82+
})
83+
84+
g.Context("snapshot options in clusterCSIDriver", func() {
85+
var tests = []struct {
86+
name string
87+
clusterCSIDriverOptions map[string]int
88+
cloudConfigOptions map[string]int
89+
successfulSnapshotsCreated int // Number of snapshots that should be created successfully, 0 to skip.
90+
}{
91+
{
92+
name: "global snapshot option",
93+
clusterCSIDriverOptions: map[string]int{
94+
snapshotOptions["global"]["clusterCSIDriver"]: 4,
95+
},
96+
cloudConfigOptions: map[string]int{
97+
snapshotOptions["global"]["cloudConf"]: 4,
98+
},
99+
successfulSnapshotsCreated: 4,
100+
},
101+
{
102+
name: "default snapshot limit",
103+
clusterCSIDriverOptions: map[string]int{},
104+
cloudConfigOptions: map[string]int{},
105+
successfulSnapshotsCreated: 3,
106+
},
107+
{
108+
name: "vsan snapshot option",
109+
clusterCSIDriverOptions: map[string]int{
110+
snapshotOptions["vsan"]["clusterCSIDriver"]: 4,
111+
},
112+
cloudConfigOptions: map[string]int{
113+
snapshotOptions["vsan"]["cloudConf"]: 4,
114+
},
115+
successfulSnapshotsCreated: 0,
116+
},
117+
{
118+
name: "vvol snapshot option",
119+
clusterCSIDriverOptions: map[string]int{
120+
snapshotOptions["vvol"]["clusterCSIDriver"]: 4,
121+
},
122+
cloudConfigOptions: map[string]int{
123+
snapshotOptions["vvol"]["cloudConf"]: 4,
124+
},
125+
successfulSnapshotsCreated: 0,
126+
},
127+
{
128+
name: "all snapshot options",
129+
clusterCSIDriverOptions: map[string]int{
130+
snapshotOptions["global"]["clusterCSIDriver"]: 5,
131+
snapshotOptions["vsan"]["clusterCSIDriver"]: 10,
132+
snapshotOptions["vvol"]["clusterCSIDriver"]: 15,
133+
},
134+
cloudConfigOptions: map[string]int{
135+
snapshotOptions["global"]["cloudConf"]: 5,
136+
snapshotOptions["vsan"]["cloudConf"]: 10,
137+
snapshotOptions["vvol"]["cloudConf"]: 15,
138+
},
139+
successfulSnapshotsCreated: 0,
140+
},
141+
}
142+
143+
for _, t := range tests {
144+
g.It(fmt.Sprintf("%s", t.name), func() {
145+
for option, value := range t.clusterCSIDriverOptions {
146+
e2e.Logf("updating %s to %d in clustercsidriver", option, value)
147+
setClusterCSIDriverSnapshotOptions(oc, option, value)
148+
}
149+
150+
for option, value := range t.cloudConfigOptions {
151+
o.Eventually(func() error {
152+
return loadAndCheckCloudConf(oc, "Snapshot", option, value)
153+
}, time.Minute, time.Second).Should(o.Succeed())
154+
}
155+
156+
if t.successfulSnapshotsCreated > 0 {
157+
pvc, err := createTestPVC(oc, oc.Namespace(), "test-pvc", "1Gi")
158+
o.Expect(err).NotTo(o.HaveOccurred())
159+
160+
_, err = createTestPod(oc, pvc.Name, oc.Namespace())
161+
o.Expect(err).NotTo(o.HaveOccurred())
162+
163+
// Wait for pvc to be bound.
164+
o.Eventually(func() error {
165+
pvc, err := oc.AdminKubeClient().CoreV1().PersistentVolumeClaims(oc.Namespace()).Get(context.Background(), "test-pvc", metav1.GetOptions{})
166+
if err != nil {
167+
return err
168+
}
169+
if pvc.Status.Phase != v1.ClaimBound {
170+
return fmt.Errorf("PVC not bound")
171+
}
172+
return nil
173+
})
174+
175+
for i := 0; i < t.successfulSnapshotsCreated; i++ {
176+
err := createSnapshot(oc, oc.Namespace(), fmt.Sprintf("test-snapshot-%d", i), "test-pvc")
177+
o.Expect(err).NotTo(o.HaveOccurred())
178+
}
179+
180+
// Next snapshot creation should be over the set limit and fail.
181+
err = createSnapshot(oc, oc.Namespace(), "test-snapshot-failed", "test-pvc")
182+
o.Expect(err).NotTo(o.HaveOccurred())
183+
184+
readyToUse, err := oc.Run("get").Args("volumesnapshot/test-snapshot-failed", "-o", "jsonpath={.status.readyToUse}").Output()
185+
o.Expect(err).NotTo(o.HaveOccurred())
186+
187+
errMsg, err := oc.Run("get").Args("volumesnapshot/test-snapshot-failed", "-o", "jsonpath={.status.error.message}").Output()
188+
o.Expect(err).NotTo(o.HaveOccurred())
189+
190+
e2e.Logf("VolumeSnapshot error message: %s readyToUse %s", errMsg, readyToUse)
191+
if !strings.Contains(errMsg, "failed to take snapshot of the volume") && readyToUse != "false" {
192+
e2e.Failf("VolumeSnapshot \"test-snapshot-failed\" should have failed and should not be ready to use")
193+
}
194+
}
195+
})
196+
197+
}
198+
})
199+
})
200+
201+
func setClusterCSIDriverSnapshotOptions(oc *exutil.CLI, snapshotOptions string, value int) {
202+
patch := []byte(fmt.Sprintf("{\"spec\":{\"driverConfig\":{\"vSphere\":{\"%s\": %d}}}}", snapshotOptions, value))
203+
_, err := oc.AdminOperatorClient().OperatorV1().ClusterCSIDrivers().Patch(context.Background(), providerName, types.MergePatchType, patch, metav1.PatchOptions{})
204+
if err != nil {
205+
e2e.Failf("failed to patch ClusterCSIDriver: %v", err)
206+
}
207+
}
208+
209+
func loadAndCheckCloudConf(oc *exutil.CLI, sectionName string, keyName string, expectedValue int) error {
210+
cm, err := oc.AdminKubeClient().CoreV1().ConfigMaps("openshift-cluster-csi-drivers").Get(context.Background(), "vsphere-csi-config", metav1.GetOptions{})
211+
if err != nil {
212+
e2e.Failf("failed to get ConfigMap: %v", err)
213+
return err
214+
}
215+
216+
cloudConfData, ok := cm.Data["cloud.conf"]
217+
if !ok {
218+
return fmt.Errorf("cloud.conf key not found in ConfigMap")
219+
}
220+
221+
cfg, err := ini.Load([]byte(cloudConfData))
222+
if err != nil {
223+
e2e.Failf("failed to load cloud.conf: %v", err)
224+
return err
225+
}
226+
227+
section, err := cfg.GetSection(sectionName)
228+
if err != nil {
229+
return fmt.Errorf("section %s not found in cloud.conf: %v", sectionName, err)
230+
}
231+
232+
key, err := section.GetKey(keyName)
233+
if err != nil {
234+
return fmt.Errorf("key %s not found in section %s: %v", keyName, sectionName, err)
235+
}
236+
237+
o.Expect(key.String()).To(o.Equal(fmt.Sprintf("%d", expectedValue)))
238+
239+
return nil
240+
}
241+
242+
func createTestPod(oc *exutil.CLI, pvcName string, namespace string) (*v1.Pod, error) {
243+
allowPrivEsc := false
244+
pod := &v1.Pod{
245+
ObjectMeta: metav1.ObjectMeta{
246+
Name: "test-pod-driver-conf",
247+
Namespace: namespace,
248+
},
249+
Spec: v1.PodSpec{
250+
Containers: []v1.Container{
251+
{
252+
Name: "test",
253+
Image: k8simage.GetE2EImage(k8simage.BusyBox),
254+
VolumeMounts: []v1.VolumeMount{
255+
{
256+
Name: "pvc-data",
257+
MountPath: "/mnt",
258+
},
259+
},
260+
SecurityContext: &v1.SecurityContext{
261+
AllowPrivilegeEscalation: &allowPrivEsc,
262+
SeccompProfile: &v1.SeccompProfile{
263+
Type: v1.SeccompProfileTypeRuntimeDefault,
264+
},
265+
Capabilities: &v1.Capabilities{
266+
Drop: []v1.Capability{"ALL"},
267+
},
268+
},
269+
},
270+
},
271+
Volumes: []v1.Volume{
272+
{
273+
Name: "pvc-data",
274+
VolumeSource: v1.VolumeSource{
275+
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
276+
ClaimName: pvcName,
277+
},
278+
},
279+
},
280+
},
281+
},
282+
}
283+
284+
return oc.AdminKubeClient().CoreV1().Pods(namespace).Create(context.Background(), pod, metav1.CreateOptions{})
285+
}
286+
287+
func createTestPVC(oc *exutil.CLI, namespace string, pvcName string, volumeSize string) (*v1.PersistentVolumeClaim, error) {
288+
e2e.Logf("creating PVC %s in namespace %s with size %s", pvcName, namespace, volumeSize)
289+
290+
pvc := &v1.PersistentVolumeClaim{
291+
ObjectMeta: metav1.ObjectMeta{
292+
Name: pvcName,
293+
Namespace: namespace,
294+
},
295+
Spec: v1.PersistentVolumeClaimSpec{
296+
AccessModes: []v1.PersistentVolumeAccessMode{"ReadWriteOnce"},
297+
Resources: v1.VolumeResourceRequirements{
298+
Requests: v1.ResourceList{
299+
v1.ResourceStorage: resource.MustParse(volumeSize),
300+
},
301+
},
302+
},
303+
}
304+
305+
return oc.AdminKubeClient().CoreV1().PersistentVolumeClaims(namespace).Create(context.Background(), pvc, metav1.CreateOptions{})
306+
}
307+
308+
func createSnapshot(oc *exutil.CLI, namespace string, snapshotName string, pvcName string) error {
309+
snapshot := fmt.Sprintf(`
310+
apiVersion: snapshot.storage.k8s.io/v1
311+
kind: VolumeSnapshot
312+
metadata:
313+
name: %s
314+
namespace: %s
315+
spec:
316+
source:
317+
persistentVolumeClaimName: %s
318+
`, snapshotName, namespace, pvcName)
319+
320+
err := oc.AsAdmin().Run("apply").Args("-f", "-").InputString(snapshot).Execute()
321+
if err != nil {
322+
return fmt.Errorf("failed to create snapshot: %v", err)
323+
}
324+
325+
return nil
326+
}

test/extended/util/annotate/generated/zz_generated.annotations.go

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)