...

Source file src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/metrics_test.go

Documentation: k8s.io/apiextensions-apiserver/pkg/apiserver/validation

     1  /*
     2  Copyright 2023 The Kubernetes 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  
    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  	// Hardcode 1 ns duration for testing to exercise all buckets
    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  	// Wrap metric to keep time constant
    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 // how many times to validate the same update before checking metric
    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  			// Ensure that the real durations is > 0 for all tests
   294  			if testMetrics.realSum <= 0 {
   295  				t.Errorf("realSum = %v, want > 0", testMetrics.realSum)
   296  			}
   297  		})
   298  	}
   299  }
   300  

View as plain text