1
16
17 package external
18
19 import (
20 "context"
21 "encoding/json"
22 "flag"
23 "fmt"
24 "os"
25 "time"
26
27 storagev1 "k8s.io/api/storage/v1"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30 "k8s.io/apimachinery/pkg/runtime"
31 "k8s.io/apimachinery/pkg/runtime/schema"
32 "k8s.io/apimachinery/pkg/util/sets"
33 "k8s.io/client-go/kubernetes/scheme"
34 klog "k8s.io/klog/v2"
35 "k8s.io/kubernetes/test/e2e/framework"
36 e2econfig "k8s.io/kubernetes/test/e2e/framework/config"
37 e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
38 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
39 e2evolume "k8s.io/kubernetes/test/e2e/framework/volume"
40 storageframework "k8s.io/kubernetes/test/e2e/storage/framework"
41 "k8s.io/kubernetes/test/e2e/storage/testsuites"
42 "k8s.io/kubernetes/test/e2e/storage/utils"
43
44 "github.com/onsi/gomega"
45 )
46
47
48
49
50 type driverDefinition struct {
51
52
53
54
55
56 DriverInfo storageframework.DriverInfo
57
58
59
60 StorageClass struct {
61
62
63
64 FromName bool
65
66
67
68
69
70
71
72
73
74
75 FromFile string
76
77
78
79 FromExistingClassName string
80 }
81
82
83
84 SnapshotClass struct {
85
86
87 FromName bool
88
89
90
91
92
93
94
95
96
97
98 FromFile string
99
100
101
102 FromExistingClassName string
103 }
104
105
106
107
108
109
110
111
112 InlineVolumes []struct {
113
114
115 Attributes map[string]string
116
117
118
119 Shared bool
120
121
122 ReadOnly bool
123 }
124
125
126
127
128 ClientNodeName string
129
130
131
132
133 Timeouts map[string]string
134 }
135
136 func init() {
137 e2econfig.Flags.Var(testDriverParameter{}, "storage.testdriver", "name of a .yaml or .json file that defines a driver for storage testing, can be used more than once")
138 }
139
140
141
142
143
144
145
146 type testDriverParameter struct {
147 }
148
149 var _ flag.Value = testDriverParameter{}
150
151 func (t testDriverParameter) String() string {
152 return "<.yaml or .json file>"
153 }
154
155 func (t testDriverParameter) Set(filename string) error {
156 return AddDriverDefinition(filename)
157 }
158
159
160
161
162 func AddDriverDefinition(filename string) error {
163 driver, err := loadDriverDefinition(filename)
164 framework.Logf("Driver loaded from path [%s]: %+v", filename, driver)
165 if err != nil {
166 return err
167 }
168 if driver.DriverInfo.Name == "" {
169 return fmt.Errorf("%q: DriverInfo.Name not set", filename)
170 }
171
172 args := []interface{}{"External Storage"}
173 args = append(args, storageframework.GetDriverNameWithFeatureTags(driver)...)
174 args = append(args, func() {
175 storageframework.DefineTestSuites(driver, testsuites.CSISuites)
176 })
177 framework.Describe(args...)
178
179 return nil
180 }
181
182 func loadDriverDefinition(filename string) (*driverDefinition, error) {
183 if filename == "" {
184 return nil, fmt.Errorf("missing file name")
185 }
186 data, err := os.ReadFile(filename)
187 if err != nil {
188 return nil, err
189 }
190
191 driver := &driverDefinition{
192 DriverInfo: storageframework.DriverInfo{
193 SupportedFsType: sets.NewString(
194 "",
195 ),
196 SupportedSizeRange: e2evolume.SizeRange{
197 Min: "5Gi",
198 },
199 },
200 }
201
202
203 if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), data, driver); err != nil {
204 return nil, fmt.Errorf("%s: %w", filename, err)
205 }
206
207
208
209 if _, ok := driver.GetDriverInfo().Capabilities[storageframework.CapOnlineExpansion]; !ok &&
210 driver.GetDriverInfo().Capabilities[storageframework.CapControllerExpansion] {
211 caps := driver.DriverInfo.Capabilities
212 caps[storageframework.CapOnlineExpansion] = true
213 driver.DriverInfo.Capabilities = caps
214 }
215 if _, ok := driver.GetDriverInfo().Capabilities[storageframework.CapOfflineExpansion]; !ok &&
216 driver.GetDriverInfo().Capabilities[storageframework.CapControllerExpansion] {
217 caps := driver.DriverInfo.Capabilities
218 caps[storageframework.CapOfflineExpansion] = true
219 driver.DriverInfo.Capabilities = caps
220 }
221 return driver, nil
222 }
223
224 var _ storageframework.TestDriver = &driverDefinition{}
225
226
227
228
229 var _ storageframework.DynamicPVTestDriver = &driverDefinition{}
230
231
232 var _ storageframework.SnapshottableTestDriver = &driverDefinition{}
233
234
235 var _ storageframework.EphemeralTestDriver = &driverDefinition{}
236
237 var _ storageframework.CustomTimeoutsTestDriver = &driverDefinition{}
238
239
240
241
242 var _ runtime.Object = &driverDefinition{}
243
244 func (d *driverDefinition) DeepCopyObject() runtime.Object {
245 return nil
246 }
247
248 func (d *driverDefinition) GetObjectKind() schema.ObjectKind {
249 return nil
250 }
251
252 func (d *driverDefinition) GetDriverInfo() *storageframework.DriverInfo {
253 return &d.DriverInfo
254 }
255
256 func (d *driverDefinition) SkipUnsupportedTest(pattern storageframework.TestPattern) {
257 supported := false
258
259 switch pattern.VolType {
260 case "":
261 supported = true
262 case storageframework.DynamicPV, storageframework.GenericEphemeralVolume:
263 if d.StorageClass.FromName || d.StorageClass.FromFile != "" || d.StorageClass.FromExistingClassName != "" {
264 supported = true
265 }
266 case storageframework.CSIInlineVolume:
267 supported = len(d.InlineVolumes) != 0
268 }
269 if !supported {
270 e2eskipper.Skipf("Driver %q does not support volume type %q - skipping", d.DriverInfo.Name, pattern.VolType)
271 }
272
273 }
274
275 func (d *driverDefinition) GetDynamicProvisionStorageClass(ctx context.Context, e2econfig *storageframework.PerTestConfig, fsType string) *storagev1.StorageClass {
276 var (
277 sc *storagev1.StorageClass
278 err error
279 )
280
281 f := e2econfig.Framework
282 switch {
283 case d.StorageClass.FromName:
284 sc = &storagev1.StorageClass{Provisioner: d.DriverInfo.Name}
285 case d.StorageClass.FromExistingClassName != "":
286 sc, err = f.ClientSet.StorageV1().StorageClasses().Get(ctx, d.StorageClass.FromExistingClassName, metav1.GetOptions{})
287 framework.ExpectNoError(err, "getting storage class %s", d.StorageClass.FromExistingClassName)
288 case d.StorageClass.FromFile != "":
289 var ok bool
290 items, err := utils.LoadFromManifests(d.StorageClass.FromFile)
291 framework.ExpectNoError(err, "load storage class from %s", d.StorageClass.FromFile)
292 gomega.Expect(items).To(gomega.HaveLen(1), "exactly one item from %s", d.StorageClass.FromFile)
293 err = utils.PatchItems(f, f.Namespace, items...)
294 framework.ExpectNoError(err, "patch items")
295
296 sc, ok = items[0].(*storagev1.StorageClass)
297 if !ok {
298 framework.Failf("storage class from %s", d.StorageClass.FromFile)
299 }
300 }
301
302 gomega.Expect(sc).ToNot(gomega.BeNil(), "storage class is unexpectantly nil")
303
304 if fsType != "" {
305 if sc.Parameters == nil {
306 sc.Parameters = map[string]string{}
307 }
308
309
310 sc.Parameters["csi.storage.k8s.io/fstype"] = fsType
311 }
312 return storageframework.CopyStorageClass(sc, f.Namespace.Name, "e2e-sc")
313 }
314
315 func (d *driverDefinition) GetTimeouts() *framework.TimeoutContext {
316 timeouts := framework.NewTimeoutContext()
317 if d.Timeouts == nil {
318 return timeouts
319 }
320
321
322 c := make(map[string]time.Duration)
323 for k, v := range d.Timeouts {
324 duration, err := time.ParseDuration(v)
325 if err != nil {
326
327
328 klog.Errorf("Could not parse duration for key %s, will use default values: %v", k, err)
329 return timeouts
330 }
331 c[k] = duration
332 }
333
334
335 t, err := json.Marshal(c)
336 if err != nil {
337 klog.Errorf("Could not marshal custom timeouts, will use default values: %v", err)
338 return timeouts
339 }
340
341
342 err = json.Unmarshal(t, &timeouts)
343 if err != nil {
344 klog.Errorf("Could not unmarshal custom timeouts, will use default values: %v", err)
345 return timeouts
346 }
347
348 return timeouts
349 }
350
351 func loadSnapshotClass(filename string) (*unstructured.Unstructured, error) {
352 data, err := os.ReadFile(filename)
353 if err != nil {
354 return nil, err
355 }
356 snapshotClass := &unstructured.Unstructured{}
357
358 if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), data, snapshotClass); err != nil {
359 return nil, fmt.Errorf("%s: %w", filename, err)
360 }
361
362 return snapshotClass, nil
363 }
364
365 func (d *driverDefinition) GetSnapshotClass(ctx context.Context, e2econfig *storageframework.PerTestConfig, parameters map[string]string) *unstructured.Unstructured {
366 if !d.SnapshotClass.FromName && d.SnapshotClass.FromFile == "" && d.SnapshotClass.FromExistingClassName == "" {
367 e2eskipper.Skipf("Driver %q does not support snapshotting - skipping", d.DriverInfo.Name)
368 }
369
370 f := e2econfig.Framework
371 snapshotter := d.DriverInfo.Name
372 ns := e2econfig.Framework.Namespace.Name
373
374 switch {
375 case d.SnapshotClass.FromName:
376
377 case d.SnapshotClass.FromExistingClassName != "":
378 snapshotClass, err := f.DynamicClient.Resource(utils.SnapshotClassGVR).Get(ctx, d.SnapshotClass.FromExistingClassName, metav1.GetOptions{})
379 framework.ExpectNoError(err, "getting snapshot class %s", d.SnapshotClass.FromExistingClassName)
380
381 if params, ok := snapshotClass.Object["parameters"].(map[string]interface{}); ok {
382 for k, v := range params {
383 parameters[k] = v.(string)
384 }
385 }
386
387 if snapshotProvider, ok := snapshotClass.Object["driver"]; ok {
388 snapshotter = snapshotProvider.(string)
389 }
390 case d.SnapshotClass.FromFile != "":
391 snapshotClass, err := loadSnapshotClass(d.SnapshotClass.FromFile)
392 framework.ExpectNoError(err, "load snapshot class from %s", d.SnapshotClass.FromFile)
393
394 if params, ok := snapshotClass.Object["parameters"].(map[string]interface{}); ok {
395 for k, v := range params {
396 parameters[k] = v.(string)
397 }
398 }
399
400 if snapshotProvider, ok := snapshotClass.Object["driver"]; ok {
401 snapshotter = snapshotProvider.(string)
402 }
403 }
404
405 return utils.GenerateSnapshotClassSpec(snapshotter, parameters, ns)
406 }
407
408 func (d *driverDefinition) GetVolume(e2econfig *storageframework.PerTestConfig, volumeNumber int) (map[string]string, bool, bool) {
409 if len(d.InlineVolumes) == 0 {
410 e2eskipper.Skipf("%s does not have any InlineVolumeAttributes defined", d.DriverInfo.Name)
411 }
412 e2evolume := d.InlineVolumes[volumeNumber%len(d.InlineVolumes)]
413 return e2evolume.Attributes, e2evolume.Shared, e2evolume.ReadOnly
414 }
415
416 func (d *driverDefinition) GetCSIDriverName(e2econfig *storageframework.PerTestConfig) string {
417 return d.DriverInfo.Name
418 }
419
420 func (d *driverDefinition) PrepareTest(ctx context.Context, f *framework.Framework) *storageframework.PerTestConfig {
421 e2econfig := &storageframework.PerTestConfig{
422 Driver: d,
423 Prefix: "external",
424 Framework: f,
425 ClientNodeSelection: e2epod.NodeSelection{Name: d.ClientNodeName},
426 }
427
428 if framework.NodeOSDistroIs("windows") {
429 e2econfig.ClientNodeSelection.Selector = map[string]string{"kubernetes.io/os": "windows"}
430 } else {
431 e2econfig.ClientNodeSelection.Selector = map[string]string{"kubernetes.io/os": "linux"}
432 }
433
434 return e2econfig
435 }
436
View as plain text