...

Source file src/k8s.io/kubernetes/pkg/util/tolerations/tolerations_test.go

Documentation: k8s.io/kubernetes/pkg/util/tolerations

     1  /*
     2  Copyright 2017 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 tolerations
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"math/rand"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/stretchr/testify/assert"
    27  	"github.com/stretchr/testify/require"
    28  	"k8s.io/apimachinery/pkg/util/validation/field"
    29  	api "k8s.io/kubernetes/pkg/apis/core"
    30  	"k8s.io/kubernetes/pkg/apis/core/validation"
    31  	utilpointer "k8s.io/utils/pointer"
    32  )
    33  
    34  var (
    35  	tolerations = map[string]api.Toleration{
    36  		"all": {Operator: api.TolerationOpExists},
    37  		"all-nosched": {
    38  			Operator: api.TolerationOpExists,
    39  			Effect:   api.TaintEffectNoSchedule,
    40  		},
    41  		"all-noexec": {
    42  			Operator: api.TolerationOpExists,
    43  			Effect:   api.TaintEffectNoExecute,
    44  		},
    45  		"foo": {
    46  			Key:      "foo",
    47  			Operator: api.TolerationOpExists,
    48  		},
    49  		"foo-bar": {
    50  			Key:      "foo",
    51  			Operator: api.TolerationOpEqual,
    52  			Value:    "bar",
    53  		},
    54  		"foo-nosched": {
    55  			Key:      "foo",
    56  			Operator: api.TolerationOpExists,
    57  			Effect:   api.TaintEffectNoSchedule,
    58  		},
    59  		"foo-bar-nosched": {
    60  			Key:      "foo",
    61  			Operator: api.TolerationOpEqual,
    62  			Value:    "bar",
    63  			Effect:   api.TaintEffectNoSchedule,
    64  		},
    65  		"foo-baz-nosched": {
    66  			Key:      "foo",
    67  			Operator: api.TolerationOpEqual,
    68  			Value:    "baz",
    69  			Effect:   api.TaintEffectNoSchedule,
    70  		},
    71  		"faz-nosched": {
    72  			Key:      "faz",
    73  			Operator: api.TolerationOpExists,
    74  			Effect:   api.TaintEffectNoSchedule,
    75  		},
    76  		"faz-baz-nosched": {
    77  			Key:      "faz",
    78  			Operator: api.TolerationOpEqual,
    79  			Value:    "baz",
    80  			Effect:   api.TaintEffectNoSchedule,
    81  		},
    82  		"foo-prefnosched": {
    83  			Key:      "foo",
    84  			Operator: api.TolerationOpExists,
    85  			Effect:   api.TaintEffectPreferNoSchedule,
    86  		},
    87  		"foo-noexec": {
    88  			Key:      "foo",
    89  			Operator: api.TolerationOpExists,
    90  			Effect:   api.TaintEffectNoExecute,
    91  		},
    92  		"foo-bar-noexec": {
    93  			Key:      "foo",
    94  			Operator: api.TolerationOpEqual,
    95  			Value:    "bar",
    96  			Effect:   api.TaintEffectNoExecute,
    97  		},
    98  		"foo-noexec-10": {
    99  			Key:               "foo",
   100  			Operator:          api.TolerationOpExists,
   101  			Effect:            api.TaintEffectNoExecute,
   102  			TolerationSeconds: utilpointer.Int64Ptr(10),
   103  		},
   104  		"foo-noexec-0": {
   105  			Key:               "foo",
   106  			Operator:          api.TolerationOpExists,
   107  			Effect:            api.TaintEffectNoExecute,
   108  			TolerationSeconds: utilpointer.Int64Ptr(0),
   109  		},
   110  		"foo-bar-noexec-10": {
   111  			Key:               "foo",
   112  			Operator:          api.TolerationOpEqual,
   113  			Value:             "bar",
   114  			Effect:            api.TaintEffectNoExecute,
   115  			TolerationSeconds: utilpointer.Int64Ptr(10),
   116  		},
   117  	}
   118  )
   119  
   120  func TestIsSuperset(t *testing.T) {
   121  	tests := []struct {
   122  		toleration string
   123  		ss         []string // t should be a superset of these
   124  	}{{
   125  		"all",
   126  		[]string{"all-nosched", "all-noexec", "foo", "foo-bar", "foo-nosched", "foo-bar-nosched", "foo-baz-nosched", "faz-nosched", "faz-baz-nosched", "foo-prefnosched", "foo-noexec", "foo-bar-noexec", "foo-noexec-10", "foo-noexec-0", "foo-bar-noexec-10"},
   127  	}, {
   128  		"all-nosched",
   129  		[]string{"foo-nosched", "foo-bar-nosched", "foo-baz-nosched", "faz-nosched", "faz-baz-nosched"},
   130  	}, {
   131  		"all-noexec",
   132  		[]string{"foo-noexec", "foo-bar-noexec", "foo-noexec-10", "foo-noexec-0", "foo-bar-noexec-10"},
   133  	}, {
   134  		"foo",
   135  		[]string{"foo-bar", "foo-nosched", "foo-bar-nosched", "foo-baz-nosched", "foo-prefnosched", "foo-noexec", "foo-bar-noexec", "foo-noexec-10", "foo-noexec-0", "foo-bar-noexec-10"},
   136  	}, {
   137  		"foo-bar",
   138  		[]string{"foo-bar-nosched", "foo-bar-noexec", "foo-bar-noexec-10"},
   139  	}, {
   140  		"foo-nosched",
   141  		[]string{"foo-bar-nosched", "foo-baz-nosched"},
   142  	}, {
   143  		"foo-bar-nosched",
   144  		[]string{},
   145  	}, {
   146  		"faz-nosched",
   147  		[]string{"faz-baz-nosched"},
   148  	}, {
   149  		"faz-baz-nosched",
   150  		[]string{},
   151  	}, {
   152  		"foo-prenosched",
   153  		[]string{},
   154  	}, {
   155  		"foo-noexec",
   156  		[]string{"foo-noexec", "foo-bar-noexec", "foo-noexec-10", "foo-noexec-0", "foo-bar-noexec-10"},
   157  	}, {
   158  		"foo-bar-noexec",
   159  		[]string{"foo-bar-noexec-10"},
   160  	}, {
   161  		"foo-noexec-10",
   162  		[]string{"foo-noexec-0", "foo-bar-noexec-10"},
   163  	}, {
   164  		"foo-noexec-0",
   165  		[]string{},
   166  	}, {
   167  		"foo-bar-noexec-10",
   168  		[]string{},
   169  	}}
   170  
   171  	assertSuperset := func(t *testing.T, super, sub string) {
   172  		assert.True(t, isSuperset(tolerations[super], tolerations[sub]),
   173  			"%s should be a superset of %s", super, sub)
   174  	}
   175  	assertNotSuperset := func(t *testing.T, super, sub string) {
   176  		assert.False(t, isSuperset(tolerations[super], tolerations[sub]),
   177  			"%s should NOT be a superset of %s", super, sub)
   178  	}
   179  	contains := func(ss []string, s string) bool {
   180  		for _, str := range ss {
   181  			if str == s {
   182  				return true
   183  			}
   184  		}
   185  		return false
   186  	}
   187  
   188  	for _, test := range tests {
   189  		t.Run(test.toleration, func(t *testing.T) {
   190  			for name := range tolerations {
   191  				if name == test.toleration || contains(test.ss, name) {
   192  					assertSuperset(t, test.toleration, name)
   193  				} else {
   194  					assertNotSuperset(t, test.toleration, name)
   195  				}
   196  			}
   197  		})
   198  	}
   199  }
   200  
   201  func TestVerifyAgainstWhitelist(t *testing.T) {
   202  	tests := []struct {
   203  		testName  string
   204  		input     []string
   205  		whitelist []string
   206  		expected  bool
   207  	}{
   208  		{
   209  			testName:  "equal input and whitelist",
   210  			input:     []string{"foo-bar-nosched", "foo-baz-nosched"},
   211  			whitelist: []string{"foo-bar-nosched", "foo-baz-nosched"},
   212  			expected:  true,
   213  		},
   214  		{
   215  			testName:  "duplicate input allowed",
   216  			input:     []string{"foo-bar-nosched", "foo-bar-nosched"},
   217  			whitelist: []string{"foo-bar-nosched", "foo-baz-nosched"},
   218  			expected:  true,
   219  		},
   220  		{
   221  			testName:  "allow all",
   222  			input:     []string{"foo-bar-nosched", "foo-bar-nosched"},
   223  			whitelist: []string{"all"},
   224  			expected:  true,
   225  		},
   226  		{
   227  			testName:  "duplicate input forbidden",
   228  			input:     []string{"foo-bar-nosched", "foo-bar-nosched"},
   229  			whitelist: []string{"foo-baz-nosched"},
   230  			expected:  false,
   231  		},
   232  		{
   233  			testName:  "value mismatch",
   234  			input:     []string{"foo-bar-nosched", "foo-baz-nosched"},
   235  			whitelist: []string{"foo-baz-nosched"},
   236  			expected:  false,
   237  		},
   238  		{
   239  			testName:  "input does not exist in whitelist",
   240  			input:     []string{"foo-bar-nosched"},
   241  			whitelist: []string{"foo-baz-nosched"},
   242  			expected:  false,
   243  		},
   244  		{
   245  			testName:  "disjoint sets",
   246  			input:     []string{"foo-bar"},
   247  			whitelist: []string{"foo-nosched"},
   248  			expected:  false,
   249  		},
   250  		{
   251  			testName:  "empty whitelist",
   252  			input:     []string{"foo-bar"},
   253  			whitelist: []string{},
   254  			expected:  true,
   255  		},
   256  		{
   257  			testName:  "empty input",
   258  			input:     []string{},
   259  			whitelist: []string{"foo-bar"},
   260  			expected:  true,
   261  		},
   262  	}
   263  
   264  	for _, c := range tests {
   265  		t.Run(c.testName, func(t *testing.T) {
   266  			actual := VerifyAgainstWhitelist(getTolerations(c.input), getTolerations(c.whitelist))
   267  			assert.Equal(t, c.expected, actual)
   268  		})
   269  	}
   270  }
   271  
   272  func TestMergeTolerations(t *testing.T) {
   273  	tests := []struct {
   274  		name     string
   275  		a, b     []string
   276  		expected []string
   277  	}{{
   278  		name:     "disjoint",
   279  		a:        []string{"foo-bar-nosched", "faz-baz-nosched", "foo-noexec-10"},
   280  		b:        []string{"foo-prefnosched", "foo-baz-nosched"},
   281  		expected: []string{"foo-bar-nosched", "faz-baz-nosched", "foo-noexec-10", "foo-prefnosched", "foo-baz-nosched"},
   282  	}, {
   283  		name:     "duplicate",
   284  		a:        []string{"foo-bar-nosched", "faz-baz-nosched", "foo-noexec-10"},
   285  		b:        []string{"foo-bar-nosched", "faz-baz-nosched", "foo-noexec-10"},
   286  		expected: []string{"foo-bar-nosched", "faz-baz-nosched", "foo-noexec-10"},
   287  	}, {
   288  		name:     "merge redundant",
   289  		a:        []string{"foo-bar-nosched", "foo-baz-nosched"},
   290  		b:        []string{"foo-nosched", "faz-baz-nosched"},
   291  		expected: []string{"foo-nosched", "faz-baz-nosched"},
   292  	}, {
   293  		name:     "merge all",
   294  		a:        []string{"foo-bar-nosched", "foo-baz-nosched", "foo-noexec-10"},
   295  		b:        []string{"all"},
   296  		expected: []string{"all"},
   297  	}, {
   298  		name:     "merge into all",
   299  		a:        []string{"all"},
   300  		b:        []string{"foo-bar-nosched", "foo-baz-nosched", "foo-noexec-10"},
   301  		expected: []string{"all"},
   302  	}}
   303  
   304  	for _, test := range tests {
   305  		t.Run(test.name, func(t *testing.T) {
   306  			actual := MergeTolerations(getTolerations(test.a), getTolerations(test.b))
   307  			require.Len(t, actual, len(test.expected))
   308  			for i, expect := range getTolerations(test.expected) {
   309  				assert.Equal(t, expect, actual[i], "expected[%d] = %s", i, test.expected[i])
   310  			}
   311  		})
   312  	}
   313  }
   314  
   315  func TestFuzzed(t *testing.T) {
   316  	r := rand.New(rand.NewSource(1234)) // Fixed source to prevent flakes.
   317  
   318  	const (
   319  		allProbability               = 0.01 // Chance of getting a tolerate all
   320  		existsProbability            = 0.3
   321  		tolerationSecondsProbability = 0.5
   322  	)
   323  	effects := []api.TaintEffect{"", api.TaintEffectNoExecute, api.TaintEffectNoSchedule, api.TaintEffectPreferNoSchedule}
   324  	genToleration := func() api.Toleration {
   325  		gen := api.Toleration{
   326  			Effect: effects[r.Intn(len(effects))],
   327  		}
   328  		if r.Float32() < allProbability {
   329  			gen = tolerations["all"]
   330  			return gen
   331  		}
   332  		// Small key/value space to encourage collisions
   333  		gen.Key = strings.Repeat("a", r.Intn(6)+1)
   334  		if r.Float32() < existsProbability {
   335  			gen.Operator = api.TolerationOpExists
   336  		} else {
   337  			gen.Operator = api.TolerationOpEqual
   338  			gen.Value = strings.Repeat("b", r.Intn(6)+1)
   339  		}
   340  		if gen.Effect == api.TaintEffectNoExecute && r.Float32() < tolerationSecondsProbability {
   341  			gen.TolerationSeconds = utilpointer.Int64Ptr(r.Int63n(10))
   342  		}
   343  		// Ensure only valid tolerations are generated.
   344  		require.NoError(t, validation.ValidateTolerations([]api.Toleration{gen}, field.NewPath("")).ToAggregate(), "%#v", gen)
   345  		return gen
   346  	}
   347  	genTolerations := func() []api.Toleration {
   348  		result := []api.Toleration{}
   349  		for i := 0; i < r.Intn(10); i++ {
   350  			result = append(result, genToleration())
   351  		}
   352  		return result
   353  	}
   354  
   355  	// Check whether the toleration is a subset of a toleration in the set.
   356  	isContained := func(toleration api.Toleration, set []api.Toleration) bool {
   357  		for _, ss := range set {
   358  			if isSuperset(ss, toleration) {
   359  				return true
   360  			}
   361  		}
   362  		return false
   363  	}
   364  
   365  	const iterations = 1000
   366  
   367  	debugMsg := func(tolerations ...[]api.Toleration) string {
   368  		str, err := json.Marshal(tolerations)
   369  		if err != nil {
   370  			return fmt.Sprintf("[ERR: %v] %v", err, tolerations)
   371  		}
   372  		return string(str)
   373  	}
   374  	t.Run("VerifyAgainstWhitelist", func(t *testing.T) {
   375  		for i := 0; i < iterations; i++ {
   376  			input := genTolerations()
   377  			whitelist := append(genTolerations(), genToleration()) // Non-empty
   378  			if VerifyAgainstWhitelist(input, whitelist) {
   379  				for _, tol := range input {
   380  					require.True(t, isContained(tol, whitelist), debugMsg(input, whitelist))
   381  				}
   382  			} else {
   383  				uncontained := false
   384  				for _, tol := range input {
   385  					if !isContained(tol, whitelist) {
   386  						uncontained = true
   387  						break
   388  					}
   389  				}
   390  				require.True(t, uncontained, debugMsg(input, whitelist))
   391  			}
   392  		}
   393  	})
   394  
   395  	t.Run("MergeTolerations", func(t *testing.T) {
   396  		for i := 0; i < iterations; i++ {
   397  			a := genTolerations()
   398  			b := genTolerations()
   399  			result := MergeTolerations(a, b)
   400  			for _, tol := range append(a, b...) {
   401  				require.True(t, isContained(tol, result), debugMsg(a, b, result))
   402  			}
   403  		}
   404  	})
   405  }
   406  
   407  func getTolerations(names []string) []api.Toleration {
   408  	result := []api.Toleration{}
   409  	for _, name := range names {
   410  		result = append(result, tolerations[name])
   411  	}
   412  	return result
   413  }
   414  

View as plain text