1
16
17 package defaulting
18
19 import (
20 "context"
21 "strings"
22 "testing"
23
24 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
25 structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
26 apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/util/validation/field"
29 utilfeature "k8s.io/apiserver/pkg/util/feature"
30 "k8s.io/component-base/featuregate"
31 featuregatetesting "k8s.io/component-base/featuregate/testing"
32 "k8s.io/utils/ptr"
33 )
34
35 func jsonPtr(x interface{}) *apiextensions.JSON {
36 ret := apiextensions.JSON(x)
37 return &ret
38 }
39
40 func TestDefaultValidationWithCostBudget(t *testing.T) {
41 tests := []struct {
42 name string
43 input apiextensions.CustomResourceValidation
44 features []featuregate.Feature
45 }{
46 {
47 name: "default cel validation",
48 input: apiextensions.CustomResourceValidation{
49 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
50 Type: "object",
51 Properties: map[string]apiextensions.JSONSchemaProps{
52 "embedded": {
53 Type: "object",
54 Properties: map[string]apiextensions.JSONSchemaProps{
55 "metadata": {
56 Type: "object",
57 XEmbeddedResource: true,
58 Properties: map[string]apiextensions.JSONSchemaProps{
59 "name": {
60 Type: "string",
61 XValidations: apiextensions.ValidationRules{
62 {
63 Rule: "self == 'singleton'",
64 },
65 },
66 Default: jsonPtr("singleton"),
67 },
68 },
69 },
70 },
71 },
72 "value": {
73 Type: "string",
74 XValidations: apiextensions.ValidationRules{
75 {
76 Rule: "self.startsWith('kube')",
77 },
78 },
79 Default: jsonPtr("kube-everything"),
80 },
81 "object": {
82 Type: "object",
83 Properties: map[string]apiextensions.JSONSchemaProps{
84 "field1": {
85 Type: "integer",
86 },
87 "field2": {
88 Type: "integer",
89 },
90 },
91 XValidations: apiextensions.ValidationRules{
92 {
93 Rule: "self.field1 < self.field2",
94 },
95 },
96 Default: jsonPtr(map[string]interface{}{"field1": 1, "field2": 2}),
97 },
98 },
99 },
100 },
101 },
102 }
103
104 for _, tt := range tests {
105 ctx := context.TODO()
106 t.Run(tt.name, func(t *testing.T) {
107 for _, f := range tt.features {
108 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, f, true)()
109 }
110
111 schema := tt.input.OpenAPIV3Schema
112 ss, err := structuralschema.NewStructural(schema)
113 if err != nil {
114 t.Errorf("unexpected error: %v", err)
115 }
116
117 f := NewRootObjectFunc().WithTypeMeta(metav1.TypeMeta{APIVersion: "validation/v1", Kind: "Validation"})
118
119
120 allErrs, err, _ := validate(ctx, field.NewPath("test"), ss, ss, f, false, false, 10)
121 if err != nil {
122 t.Errorf("unexpected error: %v", err)
123 }
124
125 for _, valErr := range allErrs {
126 t.Errorf("unexpected error: %v", valErr)
127 }
128
129
130 allErrs, err, _ = validate(ctx, field.NewPath("test"), ss, ss, f, false, false, 0)
131 meet := 0
132 for _, er := range allErrs {
133 if er.Type == field.ErrorTypeInvalid && strings.Contains(er.Error(), "validation failed due to running out of cost budget, no further validation rules will be run") {
134 meet += 1
135 }
136 }
137 if meet != 1 {
138 t.Errorf("expected to get cost budget exceed error once but got %v cost budget exceed error", meet)
139 }
140 if err != nil {
141 t.Errorf("unexpected error: %v", err)
142 }
143
144
145 allErrs, err, _ = validate(ctx, field.NewPath("test"), ss, ss, f, false, false, 9)
146 meet = 0
147 for _, er := range allErrs {
148 if er.Type == field.ErrorTypeInvalid && strings.Contains(er.Error(), "validation failed due to running out of cost budget, no further validation rules will be run") {
149 meet += 1
150 }
151 }
152 if meet != 1 {
153 t.Errorf("expected to get cost budget exceed error once but got %v cost budget exceed error", meet)
154 }
155 if err != nil {
156 t.Errorf("unexpected error: %v", err)
157 }
158 })
159 }
160 }
161
162 func TestDefaultValidationWithOptionalOldSelf(t *testing.T) {
163 tests := []struct {
164 name string
165 input apiextensions.CustomResourceValidation
166 errors []string
167 }{
168 {
169 name: "invalid default",
170 input: apiextensions.CustomResourceValidation{
171 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
172 Type: "object",
173 Properties: map[string]apiextensions.JSONSchemaProps{
174 "defaultFailsRatcheting": {
175 Type: "string",
176 Default: jsonPtr("default"),
177 XValidations: apiextensions.ValidationRules{
178 {
179 Rule: "oldSelf.hasValue()",
180 OptionalOldSelf: ptr.To(true),
181 Message: "foobarErrorMessage",
182 },
183 },
184 },
185 },
186 },
187 },
188 errors: []string{"foobarErrorMessage"},
189 },
190 {
191 name: "valid default",
192 input: apiextensions.CustomResourceValidation{
193 OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
194 Type: "object",
195 Properties: map[string]apiextensions.JSONSchemaProps{
196 "defaultFailsRatcheting": {
197 Type: "string",
198 Default: jsonPtr("default"),
199 XValidations: apiextensions.ValidationRules{
200 {
201 Rule: "oldSelf.orValue(self) == self",
202 OptionalOldSelf: ptr.To(true),
203 Message: "foobarErrorMessage",
204 },
205 },
206 },
207 },
208 },
209 },
210 errors: []string{},
211 },
212 }
213
214 for _, tt := range tests {
215 ctx := context.TODO()
216 t.Run(tt.name, func(t *testing.T) {
217 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CRDValidationRatcheting, true)()
218 schema := tt.input.OpenAPIV3Schema
219 ss, err := structuralschema.NewStructural(schema)
220 if err != nil {
221 t.Errorf("unexpected error: %v", err)
222 }
223
224 f := NewRootObjectFunc().WithTypeMeta(metav1.TypeMeta{APIVersion: "validation/v1", Kind: "Validation"})
225
226
227 allErrs, err, _ := validate(ctx, field.NewPath("test"), ss, ss, f, false, false, 10)
228 if err != nil {
229 t.Errorf("unexpected error: %v", err)
230 }
231
232 for _, err := range allErrs {
233 found := false
234 for _, expected := range tt.errors {
235 if strings.Contains(err.Error(), expected) {
236 found = true
237 break
238 }
239 }
240 if !found {
241 t.Errorf("unexpected error: %v", err)
242 }
243 }
244
245 for _, expected := range tt.errors {
246 found := false
247 for _, err := range allErrs {
248 if strings.Contains(err.Error(), expected) {
249 found = true
250 break
251 }
252 }
253 if !found {
254 t.Errorf("expected error: %v", expected)
255 }
256 }
257
258 })
259 }
260 }
261
View as plain text