|
| 1 | +/* |
| 2 | +Copyright 2021 The Fluid Authors. |
| 3 | +
|
| 4 | +Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +you may not use this file except in compliance with the License. |
| 6 | +You may obtain a copy of the License at |
| 7 | +
|
| 8 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +
|
| 10 | +Unless required by applicable law or agreed to in writing, software |
| 11 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +See the License for the specific language governing permissions and |
| 14 | +limitations under the License. |
| 15 | +*/ |
| 16 | + |
1 | 17 | package validating |
2 | 18 |
|
3 | 19 | import ( |
4 | 20 | "context" |
5 | | - "testing" |
6 | | - |
7 | 21 | "encoding/json" |
| 22 | + "testing" |
8 | 23 |
|
9 | 24 | "github.com/stretchr/testify/assert" |
10 | 25 | admissionv1 "k8s.io/api/admission/v1" |
| 26 | + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
11 | 27 | "k8s.io/apimachinery/pkg/runtime" |
12 | 28 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" |
| 29 | + |
13 | 30 | v1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1" |
14 | 31 | ) |
15 | 32 |
|
16 | | -func TestValidatingHandler_Basic(t *testing.T) { |
17 | | - h := NewValidatingHandler() |
| 33 | +func TestValidatingHandler_EmptyRaw(t *testing.T) { |
| 34 | + h := newHandler(t) |
18 | 35 |
|
19 | | - // Valid object |
20 | | - obj := []byte(`{"metadata":{"name":"test-dataset"}}`) |
| 36 | + // DELETE requests may have empty Object.Raw — should be allowed. |
21 | 37 | req := admission.Request{ |
22 | 38 | AdmissionRequest: admissionv1.AdmissionRequest{ |
23 | | - Object: runtime.RawExtension{Raw: obj}, |
| 39 | + Object: runtime.RawExtension{Raw: nil}, |
24 | 40 | }, |
25 | 41 | } |
26 | 42 | resp := h.Handle(context.Background(), req) |
27 | 43 | assert.True(t, resp.Allowed) |
28 | | - |
29 | | - // Missing metadata.name |
30 | | - obj = []byte(`{"metadata":{}}`) |
31 | | - req.AdmissionRequest.Object.Raw = obj |
32 | | - resp = h.Handle(context.Background(), req) |
33 | | - assert.False(t, resp.Allowed) |
34 | | - assert.Contains(t, resp.Result.Message, "metadata.name is required") |
35 | 44 | } |
36 | 45 |
|
37 | 46 | func TestValidatingHandler_Dataset(t *testing.T) { |
38 | | - h := NewValidatingHandler() |
39 | | - // prepare decoder with scheme so that typed decoding works |
40 | | - scheme := runtime.NewScheme() |
41 | | - err := v1alpha1.AddToScheme(scheme) |
42 | | - if err != nil { |
43 | | - t.Fatalf("failed to add v1alpha1 to scheme: %v", err) |
44 | | - } |
45 | | - dec := admission.NewDecoder(scheme) |
46 | | - h.InjectDecoder(dec) |
| 47 | + h := newHandler(t) |
47 | 48 |
|
48 | | - // Dataset missing mounts and runtimes -> denied |
49 | | - ds := &v1alpha1.Dataset{} |
50 | | - raw, _ := json.Marshal(ds) |
51 | | - req := admission.Request{ |
52 | | - AdmissionRequest: admissionv1.AdmissionRequest{ |
53 | | - Object: runtime.RawExtension{Raw: raw}, |
| 49 | + tests := []struct { |
| 50 | + name string |
| 51 | + ds *v1alpha1.Dataset |
| 52 | + allowed bool |
| 53 | + message string |
| 54 | + }{ |
| 55 | + { |
| 56 | + name: "empty mounts and runtimes is allowed (mounts are optional)", |
| 57 | + ds: &v1alpha1.Dataset{ObjectMeta: metav1.ObjectMeta{Name: "ds"}}, |
| 58 | + allowed: true, |
| 59 | + }, |
| 60 | + { |
| 61 | + name: "valid mount is allowed", |
| 62 | + ds: &v1alpha1.Dataset{ |
| 63 | + ObjectMeta: metav1.ObjectMeta{Name: "ds"}, |
| 64 | + Spec: v1alpha1.DatasetSpec{Mounts: []v1alpha1.Mount{{MountPoint: "/data"}}}, |
| 65 | + }, |
| 66 | + allowed: true, |
| 67 | + }, |
| 68 | + { |
| 69 | + name: "empty mountPoint is denied", |
| 70 | + ds: &v1alpha1.Dataset{ |
| 71 | + ObjectMeta: metav1.ObjectMeta{Name: "ds"}, |
| 72 | + Spec: v1alpha1.DatasetSpec{Mounts: []v1alpha1.Mount{{MountPoint: ""}}}, |
| 73 | + }, |
| 74 | + allowed: false, |
| 75 | + message: "mount.mountPoint must not be empty", |
| 76 | + }, |
| 77 | + { |
| 78 | + name: "runtime missing namespace is denied", |
| 79 | + ds: &v1alpha1.Dataset{ |
| 80 | + ObjectMeta: metav1.ObjectMeta{Name: "ds"}, |
| 81 | + Spec: v1alpha1.DatasetSpec{Runtimes: []v1alpha1.Runtime{{Name: "alluxio"}}}, |
| 82 | + }, |
| 83 | + allowed: false, |
| 84 | + message: "runtime entries must include name and namespace", |
| 85 | + }, |
| 86 | + { |
| 87 | + name: "missing metadata.name is denied", |
| 88 | + ds: &v1alpha1.Dataset{}, |
| 89 | + allowed: false, |
| 90 | + message: "metadata.name is required", |
54 | 91 | }, |
55 | 92 | } |
56 | | - resp := h.Handle(context.Background(), req) |
57 | | - assert.False(t, resp.Allowed) |
58 | 93 |
|
59 | | - // Dataset with a valid mount -> allowed |
60 | | - ds = &v1alpha1.Dataset{} |
61 | | - ds.Spec.Mounts = []v1alpha1.Mount{{MountPoint: "/data"}} |
62 | | - raw, _ = json.Marshal(ds) |
63 | | - req.AdmissionRequest.Object.Raw = raw |
64 | | - resp = h.Handle(context.Background(), req) |
65 | | - assert.True(t, resp.Allowed) |
| 94 | + for _, tc := range tests { |
| 95 | + t.Run(tc.name, func(t *testing.T) { |
| 96 | + // Set TypeMeta so the decoder can recognize the GVK. |
| 97 | + tc.ds.TypeMeta = metav1.TypeMeta{ |
| 98 | + APIVersion: "data.fluid.io/v1alpha1", |
| 99 | + Kind: "Dataset", |
| 100 | + } |
| 101 | + raw, err := json.Marshal(tc.ds) |
| 102 | + assert.NoError(t, err) |
| 103 | + |
| 104 | + req := admission.Request{ |
| 105 | + AdmissionRequest: admissionv1.AdmissionRequest{ |
| 106 | + Object: runtime.RawExtension{Raw: raw}, |
| 107 | + }, |
| 108 | + } |
| 109 | + resp := h.Handle(context.Background(), req) |
| 110 | + assert.Equal(t, tc.allowed, resp.Allowed, "test %q", tc.name) |
| 111 | + if tc.message != "" { |
| 112 | + assert.Contains(t, resp.Result.Message, tc.message) |
| 113 | + } |
| 114 | + }) |
| 115 | + } |
| 116 | +} |
66 | 117 |
|
67 | | - // Dataset with invalid mount (too short) -> denied |
68 | | - ds = &v1alpha1.Dataset{} |
69 | | - ds.Spec.Mounts = []v1alpha1.Mount{{MountPoint: "abc"}} |
70 | | - raw, _ = json.Marshal(ds) |
71 | | - req.AdmissionRequest.Object.Raw = raw |
72 | | - resp = h.Handle(context.Background(), req) |
73 | | - assert.False(t, resp.Allowed) |
| 118 | +// newHandler creates a ValidatingHandler wired with a decoder for v1alpha1. |
| 119 | +func newHandler(t *testing.T) *ValidatingHandler { |
| 120 | + t.Helper() |
| 121 | + scheme := runtime.NewScheme() |
| 122 | + assert.NoError(t, v1alpha1.AddToScheme(scheme)) |
| 123 | + h := NewValidatingHandler() |
| 124 | + h.InjectDecoder(admission.NewDecoder(scheme)) |
| 125 | + return h |
74 | 126 | } |
0 commit comments