1
16
17 package validation_test
18
19 import (
20 "context"
21 "strings"
22 "testing"
23 "time"
24
25 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
26 structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
27 "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
28 apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
29 "k8s.io/apiextensions-apiserver/pkg/registry/customresource"
30 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
31 "k8s.io/apimachinery/pkg/runtime/schema"
32 utilfeature "k8s.io/apiserver/pkg/util/feature"
33 featuregatetesting "k8s.io/component-base/featuregate/testing"
34 "k8s.io/component-base/metrics"
35 "k8s.io/component-base/metrics/testutil"
36 )
37
38 type fakeMetrics struct {
39 original validation.ValidationMetrics
40 realSum time.Duration
41 }
42
43 func (f *fakeMetrics) ObserveRatchetingTime(d time.Duration) {
44
45 f.original.ObserveRatchetingTime(1 * time.Nanosecond)
46 f.realSum += d
47 }
48
49 func (f *fakeMetrics) Reset() []metrics.Registerable {
50 f.realSum = 0
51 originalResettable, ok := f.original.(resettable)
52 if !ok {
53 panic("wrapped metrics must implement resettable")
54 }
55 return originalResettable.Reset()
56 }
57
58 type resettable interface {
59 Reset() []metrics.Registerable
60 }
61
62 func TestMetrics(t *testing.T) {
63 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CRDValidationRatcheting, true)()
64
65
66 testMetrics := &fakeMetrics{original: validation.Metrics}
67 validation.Metrics = testMetrics
68 defer func() {
69 validation.Metrics = testMetrics.original
70 }()
71
72 metricNames := []string{
73 "apiextensions_apiserver_validation_ratcheting_seconds",
74 }
75
76 testCases := []struct {
77 desc string
78 obj *unstructured.Unstructured
79 old *unstructured.Unstructured
80 schema apiextensions.JSONSchemaProps
81 iters int
82 want string
83 }{
84 {
85 desc: "valid noop update",
86 obj: &unstructured.Unstructured{
87 Object: map[string]interface{}{
88 "foo": "bar",
89 },
90 },
91 old: &unstructured.Unstructured{
92 Object: map[string]interface{}{
93 "foo": "bar",
94 },
95 },
96 schema: apiextensions.JSONSchemaProps{
97 Type: "object",
98 Properties: map[string]apiextensions.JSONSchemaProps{
99 "foo": {
100 Type: "string",
101 },
102 },
103 },
104 want: `
105 # HELP apiextensions_apiserver_validation_ratcheting_seconds [ALPHA] Time for comparison of old to new for the purposes of CRDValidationRatcheting during an UPDATE in seconds.
106 # TYPE apiextensions_apiserver_validation_ratcheting_seconds histogram
107 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="1e-05"} 5
108 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="4e-05"} 5
109 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.00016"} 5
110 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.00064"} 5
111 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.00256"} 5
112 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.01024"} 5
113 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.04096"} 5
114 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.16384"} 5
115 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.65536"} 5
116 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="2.62144"} 5
117 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="Inf"} 5
118 apiextensions_apiserver_validation_ratcheting_seconds_sum 5e-09
119 apiextensions_apiserver_validation_ratcheting_seconds_count 5
120 `,
121 iters: 5,
122 },
123 {
124 desc: "valid change yields no metrics",
125 obj: &unstructured.Unstructured{
126 Object: map[string]interface{}{
127 "foo": "bar",
128 },
129 },
130 old: &unstructured.Unstructured{
131 Object: map[string]interface{}{
132 "foo": "barx",
133 },
134 },
135 schema: apiextensions.JSONSchemaProps{
136 Type: "object",
137 Properties: map[string]apiextensions.JSONSchemaProps{
138 "foo": {
139 Type: "string",
140 Enum: []apiextensions.JSON{
141 "barx", "bar",
142 },
143 },
144 },
145 },
146 want: `
147 # HELP apiextensions_apiserver_validation_ratcheting_seconds [ALPHA] Time for comparison of old to new for the purposes of CRDValidationRatcheting during an UPDATE in seconds.
148 # TYPE apiextensions_apiserver_validation_ratcheting_seconds histogram
149 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="1e-05"} 3
150 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="4e-05"} 3
151 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.00016"} 3
152 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.00064"} 3
153 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.00256"} 3
154 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.01024"} 3
155 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.04096"} 3
156 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.16384"} 3
157 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.65536"} 3
158 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="2.62144"} 3
159 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="Inf"} 3
160 apiextensions_apiserver_validation_ratcheting_seconds_sum 3.0000000000000004e-09
161 apiextensions_apiserver_validation_ratcheting_seconds_count 3
162 `,
163 iters: 3,
164 },
165 {
166 desc: "invalid noop yields no metrics",
167 obj: &unstructured.Unstructured{
168 Object: map[string]interface{}{
169 "foo": "bar",
170 },
171 },
172 old: &unstructured.Unstructured{
173 Object: map[string]interface{}{
174 "foo": "bar",
175 },
176 },
177 schema: apiextensions.JSONSchemaProps{
178 Type: "object",
179 Properties: map[string]apiextensions.JSONSchemaProps{
180 "foo": {
181 Type: "string",
182 Enum: []apiextensions.JSON{
183 "incorrect",
184 },
185 },
186 },
187 },
188 want: `
189 # HELP apiextensions_apiserver_validation_ratcheting_seconds [ALPHA] Time for comparison of old to new for the purposes of CRDValidationRatcheting during an UPDATE in seconds.
190 # TYPE apiextensions_apiserver_validation_ratcheting_seconds histogram
191 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="1e-05"} 10
192 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="4e-05"} 10
193 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.00016"} 10
194 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.00064"} 10
195 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.00256"} 10
196 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.01024"} 10
197 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.04096"} 10
198 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.16384"} 10
199 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.65536"} 10
200 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="2.62144"} 10
201 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="Inf"} 10
202 apiextensions_apiserver_validation_ratcheting_seconds_sum 1.0000000000000002e-08
203 apiextensions_apiserver_validation_ratcheting_seconds_count 10
204 `,
205 iters: 10,
206 },
207 {
208 desc: "ratcheted change object yields metrics",
209 obj: &unstructured.Unstructured{
210 Object: map[string]interface{}{
211 "foo": "bar",
212 },
213 },
214 old: &unstructured.Unstructured{
215 Object: map[string]interface{}{
216 "foo": "barx",
217 },
218 },
219 schema: apiextensions.JSONSchemaProps{
220 Type: "object",
221 Properties: map[string]apiextensions.JSONSchemaProps{
222 "foo": {
223 Type: "string",
224 Enum: []apiextensions.JSON{
225 "incorrect",
226 },
227 },
228 },
229 },
230 want: `
231 # HELP apiextensions_apiserver_validation_ratcheting_seconds [ALPHA] Time for comparison of old to new for the purposes of CRDValidationRatcheting during an UPDATE in seconds.
232 # TYPE apiextensions_apiserver_validation_ratcheting_seconds histogram
233 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="1e-05"} 5
234 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="4e-05"} 5
235 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.00016"} 5
236 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.00064"} 5
237 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.00256"} 5
238 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.01024"} 5
239 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.04096"} 5
240 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.16384"} 5
241 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="0.65536"} 5
242 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="2.62144"} 5
243 apiextensions_apiserver_validation_ratcheting_seconds_bucket{le="Inf"} 5
244 apiextensions_apiserver_validation_ratcheting_seconds_sum 5e-09
245 apiextensions_apiserver_validation_ratcheting_seconds_count 5
246 `,
247 iters: 5,
248 },
249 }
250
251 for _, tt := range testCases {
252 t.Run(tt.desc, func(t *testing.T) {
253 testRegistry := metrics.NewKubeRegistry()
254 ms := testMetrics.Reset()
255 testRegistry.MustRegister(ms...)
256
257 schemaValidator, _, err := validation.NewSchemaValidator(&tt.schema)
258 if err != nil {
259 t.Fatal(err)
260 return
261 }
262 sts, err := structuralschema.NewStructural(&tt.schema)
263 if err != nil {
264 t.Fatal(err)
265 }
266 gvk := schema.GroupVersionKind{Group: "example.com", Version: "v1", Kind: "Foo"}
267 tt.obj.SetGroupVersionKind(gvk)
268 tt.old.SetGroupVersionKind(gvk)
269 strategy := customresource.NewStrategy(
270 nil,
271 true,
272 gvk,
273 schemaValidator,
274 nil,
275 sts,
276 nil,
277 nil,
278 nil,
279 )
280
281 iters := 1
282 if tt.iters > 0 {
283 iters = tt.iters
284 }
285 for i := 0; i < iters; i++ {
286 _ = strategy.ValidateUpdate(context.TODO(), tt.obj, tt.old)
287 }
288
289 if err := testutil.GatherAndCompare(testRegistry, strings.NewReader(tt.want), metricNames...); err != nil {
290 t.Errorf("unexpected collecting result:\n%s", err)
291 }
292
293
294 if testMetrics.realSum <= 0 {
295 t.Errorf("realSum = %v, want > 0", testMetrics.realSum)
296 }
297 })
298 }
299 }
300
View as plain text