...

Source file src/k8s.io/kubernetes/pkg/registry/flowcontrol/ensurer/flowschema_test.go

Documentation: k8s.io/kubernetes/pkg/registry/flowcontrol/ensurer

     1  /*
     2  Copyright 2021 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 ensurer
    18  
    19  import (
    20  	"context"
    21  	"reflect"
    22  	"testing"
    23  
    24  	flowcontrolv1 "k8s.io/api/flowcontrol/v1"
    25  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apiserver/pkg/apis/flowcontrol/bootstrap"
    28  	"k8s.io/client-go/kubernetes/fake"
    29  	flowcontrollisters "k8s.io/client-go/listers/flowcontrol/v1"
    30  	toolscache "k8s.io/client-go/tools/cache"
    31  	"k8s.io/klog/v2"
    32  	flowcontrolapisv1 "k8s.io/kubernetes/pkg/apis/flowcontrol/v1"
    33  
    34  	"github.com/google/go-cmp/cmp"
    35  	"github.com/stretchr/testify/assert"
    36  )
    37  
    38  func init() {
    39  	klog.InitFlags(nil)
    40  }
    41  
    42  func TestEnsureFlowSchema(t *testing.T) {
    43  	tests := []struct {
    44  		name      string
    45  		strategy  func() EnsureStrategy[*flowcontrolv1.FlowSchema]
    46  		current   *flowcontrolv1.FlowSchema
    47  		bootstrap *flowcontrolv1.FlowSchema
    48  		expected  *flowcontrolv1.FlowSchema
    49  	}{
    50  		// for suggested configurations
    51  		{
    52  			name:      "suggested flow schema does not exist - the object should always be re-created",
    53  			strategy:  NewSuggestedEnsureStrategy[*flowcontrolv1.FlowSchema],
    54  			bootstrap: newFlowSchema("fs1", "pl1", 100).Object(),
    55  			current:   nil,
    56  			expected:  newFlowSchema("fs1", "pl1", 100).Object(),
    57  		},
    58  		{
    59  			name:      "suggested flow schema exists, auto update is enabled, spec does not match - current object should be updated",
    60  			strategy:  NewSuggestedEnsureStrategy[*flowcontrolv1.FlowSchema],
    61  			bootstrap: newFlowSchema("fs1", "pl1", 100).Object(),
    62  			current:   newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("true").Object(),
    63  			expected:  newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").Object(),
    64  		},
    65  		{
    66  			name:      "suggested flow schema exists, auto update is disabled, spec does not match - current object should not be updated",
    67  			strategy:  NewSuggestedEnsureStrategy[*flowcontrolv1.FlowSchema],
    68  			bootstrap: newFlowSchema("fs1", "pl1", 100).Object(),
    69  			current:   newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("false").Object(),
    70  			expected:  newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("false").Object(),
    71  		},
    72  
    73  		// for mandatory configurations
    74  		{
    75  			name:      "mandatory flow schema does not exist - new object should be created",
    76  			strategy:  NewMandatoryEnsureStrategy[*flowcontrolv1.FlowSchema],
    77  			bootstrap: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").Object(),
    78  			current:   nil,
    79  			expected:  newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").Object(),
    80  		},
    81  		{
    82  			name:      "mandatory flow schema exists, annotation is missing - annotation should be added",
    83  			strategy:  NewMandatoryEnsureStrategy[*flowcontrolv1.FlowSchema],
    84  			bootstrap: newFlowSchema("fs1", "pl1", 100).Object(),
    85  			current:   newFlowSchema("fs1", "pl1", 100).Object(),
    86  			expected:  newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").Object(),
    87  		},
    88  		{
    89  			name:      "mandatory flow schema exists, auto update is disabled, spec does not match - current object should be updated",
    90  			strategy:  NewMandatoryEnsureStrategy[*flowcontrolv1.FlowSchema],
    91  			bootstrap: newFlowSchema("fs1", "pl1", 100).Object(),
    92  			current:   newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("false").Object(),
    93  			expected:  newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").Object(),
    94  		},
    95  	}
    96  
    97  	for _, test := range tests {
    98  		t.Run(test.name, func(t *testing.T) {
    99  			client := fake.NewSimpleClientset().FlowcontrolV1().FlowSchemas()
   100  			indexer := toolscache.NewIndexer(toolscache.MetaNamespaceKeyFunc, toolscache.Indexers{})
   101  			if test.current != nil {
   102  				client.Create(context.TODO(), test.current, metav1.CreateOptions{})
   103  				indexer.Add(test.current)
   104  			}
   105  
   106  			ops := NewFlowSchemaOps(client, flowcontrollisters.NewFlowSchemaLister(indexer))
   107  			boots := []*flowcontrolv1.FlowSchema{test.bootstrap}
   108  			strategy := test.strategy()
   109  			err := EnsureConfigurations(context.Background(), ops, boots, strategy)
   110  			if err != nil {
   111  				t.Fatalf("Expected no error, but got: %v", err)
   112  			}
   113  
   114  			fsGot, err := client.Get(context.TODO(), test.bootstrap.Name, metav1.GetOptions{})
   115  			switch {
   116  			case test.expected == nil:
   117  				if !apierrors.IsNotFound(err) {
   118  					t.Fatalf("Expected GET to return an %q error, but got: %v", metav1.StatusReasonNotFound, err)
   119  				}
   120  			case err != nil:
   121  				t.Fatalf("Expected GET to return no error, but got: %v", err)
   122  			}
   123  
   124  			if !reflect.DeepEqual(test.expected, fsGot) {
   125  				t.Errorf("FlowSchema does not match - diff: %s", cmp.Diff(test.expected, fsGot))
   126  			}
   127  		})
   128  	}
   129  }
   130  
   131  func TestSuggestedFSEnsureStrategy_ShouldUpdate(t *testing.T) {
   132  	tests := []struct {
   133  		name              string
   134  		current           *flowcontrolv1.FlowSchema
   135  		bootstrap         *flowcontrolv1.FlowSchema
   136  		newObjectExpected *flowcontrolv1.FlowSchema
   137  	}{
   138  		{
   139  			name:              "auto update is enabled, first generation, spec does not match - spec update expected",
   140  			current:           newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").WithGeneration(1).Object(),
   141  			bootstrap:         newFlowSchema("fs1", "pl1", 200).Object(),
   142  			newObjectExpected: newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("true").WithGeneration(1).Object(),
   143  		},
   144  		{
   145  			name:              "auto update is enabled, first generation, spec matches - no update expected",
   146  			current:           newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").WithGeneration(1).Object(),
   147  			bootstrap:         newFlowSchema("fs1", "pl1", 100).Object(),
   148  			newObjectExpected: nil,
   149  		},
   150  		{
   151  			name:              "auto update is enabled, second generation, spec does not match - spec update expected",
   152  			current:           newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").WithGeneration(2).Object(),
   153  			bootstrap:         newFlowSchema("fs1", "pl2", 200).Object(),
   154  			newObjectExpected: newFlowSchema("fs1", "pl2", 200).WithAutoUpdateAnnotation("true").WithGeneration(2).Object(),
   155  		},
   156  		{
   157  			name:              "auto update is enabled, second generation, spec matches - no update expected",
   158  			current:           newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").WithGeneration(2).Object(),
   159  			bootstrap:         newFlowSchema("fs1", "pl1", 100).Object(),
   160  			newObjectExpected: nil,
   161  		},
   162  		{
   163  			name:              "auto update is disabled, first generation, spec does not match - no update expected",
   164  			current:           newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("false").WithGeneration(1).Object(),
   165  			bootstrap:         newFlowSchema("fs1", "pl1", 200).Object(),
   166  			newObjectExpected: nil,
   167  		},
   168  		{
   169  			name:              "auto update is disabled, first generation, spec matches - no update expected",
   170  			current:           newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("false").WithGeneration(1).Object(),
   171  			bootstrap:         newFlowSchema("fs1", "pl1", 100).Object(),
   172  			newObjectExpected: nil,
   173  		},
   174  		{
   175  			name:              "auto update is disabled, second generation, spec does not match - no update expected",
   176  			current:           newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("false").WithGeneration(2).Object(),
   177  			bootstrap:         newFlowSchema("fs1", "pl2", 200).Object(),
   178  			newObjectExpected: nil,
   179  		},
   180  		{
   181  			name:              "auto update is disabled, second generation, spec matches - no update expected",
   182  			current:           newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("false").WithGeneration(2).Object(),
   183  			bootstrap:         newFlowSchema("fs1", "pl1", 100).Object(),
   184  			newObjectExpected: nil,
   185  		},
   186  		{
   187  			name:              "annotation is missing, first generation, spec does not match - both annotation and spec update expected",
   188  			current:           newFlowSchema("fs1", "pl1", 100).WithGeneration(1).Object(),
   189  			bootstrap:         newFlowSchema("fs1", "pl2", 200).Object(),
   190  			newObjectExpected: newFlowSchema("fs1", "pl2", 200).WithAutoUpdateAnnotation("true").WithGeneration(1).Object(),
   191  		},
   192  		{
   193  			name:              "annotation is missing, first generation, spec matches - annotation update is expected",
   194  			current:           newFlowSchema("fs1", "pl1", 100).WithGeneration(1).Object(),
   195  			bootstrap:         newFlowSchema("fs1", "pl1", 100).Object(),
   196  			newObjectExpected: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").WithGeneration(1).Object(),
   197  		},
   198  		{
   199  			name:              "annotation is missing, second generation, spec does not match - annotation update is expected",
   200  			current:           newFlowSchema("fs1", "pl1", 100).WithGeneration(2).Object(),
   201  			bootstrap:         newFlowSchema("fs1", "pl2", 200).Object(),
   202  			newObjectExpected: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("false").WithGeneration(2).Object(),
   203  		},
   204  		{
   205  			name:              "annotation is missing, second generation, spec matches - annotation update is expected",
   206  			current:           newFlowSchema("fs1", "pl1", 100).WithGeneration(2).Object(),
   207  			bootstrap:         newFlowSchema("fs1", "pl1", 100).Object(),
   208  			newObjectExpected: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("false").WithGeneration(2).Object(),
   209  		},
   210  	}
   211  
   212  	ops := NewFlowSchemaOps(nil, nil)
   213  	for _, test := range tests {
   214  		t.Run(test.name, func(t *testing.T) {
   215  			strategy := NewSuggestedEnsureStrategy[*flowcontrolv1.FlowSchema]()
   216  			updatableGot, updateGot, err := strategy.ReviseIfNeeded(ops, test.current, test.bootstrap)
   217  			if err != nil {
   218  				t.Errorf("Expected no error, but got: %v", err)
   219  			}
   220  			if test.newObjectExpected == nil {
   221  				if updatableGot != nil {
   222  					t.Errorf("Expected a nil object, but got: %#v", updatableGot)
   223  				}
   224  				if updateGot {
   225  					t.Errorf("Expected update=%t but got: %t", false, updateGot)
   226  				}
   227  				return
   228  			}
   229  
   230  			if !updateGot {
   231  				t.Errorf("Expected update=%t but got: %t", true, updateGot)
   232  			}
   233  			if !reflect.DeepEqual(test.newObjectExpected, updatableGot) {
   234  				t.Errorf("Expected the object to be updated to match - diff: %s", cmp.Diff(test.newObjectExpected, updatableGot))
   235  			}
   236  		})
   237  	}
   238  }
   239  
   240  func TestFlowSchemaSpecChanged(t *testing.T) {
   241  	fs1 := &flowcontrolv1.FlowSchema{
   242  		Spec: flowcontrolv1.FlowSchemaSpec{},
   243  	}
   244  	fs2 := &flowcontrolv1.FlowSchema{
   245  		Spec: flowcontrolv1.FlowSchemaSpec{
   246  			MatchingPrecedence: 1,
   247  		},
   248  	}
   249  	fs1Defaulted := &flowcontrolv1.FlowSchema{
   250  		Spec: flowcontrolv1.FlowSchemaSpec{
   251  			MatchingPrecedence: flowcontrolapisv1.FlowSchemaDefaultMatchingPrecedence,
   252  		},
   253  	}
   254  	testCases := []struct {
   255  		name        string
   256  		expected    *flowcontrolv1.FlowSchema
   257  		actual      *flowcontrolv1.FlowSchema
   258  		specChanged bool
   259  	}{
   260  		{
   261  			name:        "identical flow-schemas should work",
   262  			expected:    bootstrap.MandatoryFlowSchemaCatchAll,
   263  			actual:      bootstrap.MandatoryFlowSchemaCatchAll,
   264  			specChanged: false,
   265  		},
   266  		{
   267  			name:        "defaulted flow-schemas should work",
   268  			expected:    fs1,
   269  			actual:      fs1Defaulted,
   270  			specChanged: false,
   271  		},
   272  		{
   273  			name:        "non-defaulted flow-schema has wrong spec",
   274  			expected:    fs1,
   275  			actual:      fs2,
   276  			specChanged: true,
   277  		},
   278  	}
   279  	for _, testCase := range testCases {
   280  		t.Run(testCase.name, func(t *testing.T) {
   281  			w := !flowSchemaSpecEqual(testCase.expected, testCase.actual)
   282  			assert.Equal(t, testCase.specChanged, w)
   283  		})
   284  	}
   285  }
   286  
   287  func TestRemoveFlowSchema(t *testing.T) {
   288  	tests := []struct {
   289  		name           string
   290  		current        *flowcontrolv1.FlowSchema
   291  		bootstrapName  string
   292  		removeExpected bool
   293  	}{
   294  		{
   295  			name:          "no flow schema objects exist",
   296  			bootstrapName: "fs1",
   297  			current:       nil,
   298  		},
   299  		{
   300  			name:           "flow schema unwanted, auto update is enabled",
   301  			bootstrapName:  "fs0",
   302  			current:        newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("true").Object(),
   303  			removeExpected: true,
   304  		},
   305  		{
   306  			name:           "flow schema unwanted, auto update is disabled",
   307  			bootstrapName:  "fs0",
   308  			current:        newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("false").Object(),
   309  			removeExpected: false,
   310  		},
   311  		{
   312  			name:           "flow schema unwanted, the auto-update annotation is malformed",
   313  			bootstrapName:  "fs0",
   314  			current:        newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("invalid").Object(),
   315  			removeExpected: false,
   316  		},
   317  		{
   318  			name:           "flow schema wanted, auto update is enabled",
   319  			bootstrapName:  "fs1",
   320  			current:        newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("true").Object(),
   321  			removeExpected: false,
   322  		},
   323  		{
   324  			name:           "flow schema wanted, auto update is disabled",
   325  			bootstrapName:  "fs1",
   326  			current:        newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("false").Object(),
   327  			removeExpected: false,
   328  		},
   329  		{
   330  			name:           "flow schema wanted, the auto-update annotation is malformed",
   331  			bootstrapName:  "fs1",
   332  			current:        newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("invalid").Object(),
   333  			removeExpected: false,
   334  		},
   335  	}
   336  
   337  	for _, test := range tests {
   338  		t.Run(test.name, func(t *testing.T) {
   339  			client := fake.NewSimpleClientset().FlowcontrolV1().FlowSchemas()
   340  			indexer := toolscache.NewIndexer(toolscache.MetaNamespaceKeyFunc, toolscache.Indexers{})
   341  			if test.current != nil {
   342  				client.Create(context.TODO(), test.current, metav1.CreateOptions{})
   343  				indexer.Add(test.current)
   344  			}
   345  			bootFS := newFlowSchema(test.bootstrapName, "pl", 100).Object()
   346  			ops := NewFlowSchemaOps(client, flowcontrollisters.NewFlowSchemaLister(indexer))
   347  			boots := []*flowcontrolv1.FlowSchema{bootFS}
   348  			err := RemoveUnwantedObjects(context.Background(), ops, boots)
   349  
   350  			if err != nil {
   351  				t.Fatalf("Expected no error, but got: %v", err)
   352  			}
   353  
   354  			if test.current == nil {
   355  				return
   356  			}
   357  			_, err = client.Get(context.TODO(), test.current.Name, metav1.GetOptions{})
   358  			switch {
   359  			case test.removeExpected:
   360  				if !apierrors.IsNotFound(err) {
   361  					t.Errorf("Expected error from Get after Delete: %q, but got: %v", metav1.StatusReasonNotFound, err)
   362  				}
   363  			default:
   364  				if err != nil {
   365  					t.Errorf("Expected no error from Get after Delete, but got: %v", err)
   366  				}
   367  			}
   368  		})
   369  	}
   370  }
   371  
   372  type fsBuilder struct {
   373  	object *flowcontrolv1.FlowSchema
   374  }
   375  
   376  func newFlowSchema(name, plName string, matchingPrecedence int32) *fsBuilder {
   377  	return &fsBuilder{
   378  		object: &flowcontrolv1.FlowSchema{
   379  			ObjectMeta: metav1.ObjectMeta{
   380  				Name: name,
   381  			},
   382  			Spec: flowcontrolv1.FlowSchemaSpec{
   383  				PriorityLevelConfiguration: flowcontrolv1.PriorityLevelConfigurationReference{
   384  					Name: plName,
   385  				},
   386  				MatchingPrecedence: matchingPrecedence,
   387  			},
   388  		},
   389  	}
   390  }
   391  
   392  func (b *fsBuilder) Object() *flowcontrolv1.FlowSchema {
   393  	return b.object
   394  }
   395  
   396  func (b *fsBuilder) WithGeneration(value int64) *fsBuilder {
   397  	b.object.SetGeneration(value)
   398  	return b
   399  }
   400  
   401  func (b *fsBuilder) WithAutoUpdateAnnotation(value string) *fsBuilder {
   402  	setAnnotation(b.object, value)
   403  	return b
   404  }
   405  
   406  func setAnnotation(accessor metav1.Object, value string) {
   407  	if accessor.GetAnnotations() == nil {
   408  		accessor.SetAnnotations(map[string]string{})
   409  	}
   410  
   411  	accessor.GetAnnotations()[flowcontrolv1.AutoUpdateAnnotationKey] = value
   412  }
   413  

View as plain text