mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 05:40:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			414 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			414 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright 2017 The Kubernetes Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package tolerations
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"math/rand"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| 	"k8s.io/apimachinery/pkg/util/validation/field"
 | |
| 	api "k8s.io/kubernetes/pkg/apis/core"
 | |
| 	"k8s.io/kubernetes/pkg/apis/core/validation"
 | |
| 	utilpointer "k8s.io/utils/pointer"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	tolerations = map[string]api.Toleration{
 | |
| 		"all": {Operator: api.TolerationOpExists},
 | |
| 		"all-nosched": {
 | |
| 			Operator: api.TolerationOpExists,
 | |
| 			Effect:   api.TaintEffectNoSchedule,
 | |
| 		},
 | |
| 		"all-noexec": {
 | |
| 			Operator: api.TolerationOpExists,
 | |
| 			Effect:   api.TaintEffectNoExecute,
 | |
| 		},
 | |
| 		"foo": {
 | |
| 			Key:      "foo",
 | |
| 			Operator: api.TolerationOpExists,
 | |
| 		},
 | |
| 		"foo-bar": {
 | |
| 			Key:      "foo",
 | |
| 			Operator: api.TolerationOpEqual,
 | |
| 			Value:    "bar",
 | |
| 		},
 | |
| 		"foo-nosched": {
 | |
| 			Key:      "foo",
 | |
| 			Operator: api.TolerationOpExists,
 | |
| 			Effect:   api.TaintEffectNoSchedule,
 | |
| 		},
 | |
| 		"foo-bar-nosched": {
 | |
| 			Key:      "foo",
 | |
| 			Operator: api.TolerationOpEqual,
 | |
| 			Value:    "bar",
 | |
| 			Effect:   api.TaintEffectNoSchedule,
 | |
| 		},
 | |
| 		"foo-baz-nosched": {
 | |
| 			Key:      "foo",
 | |
| 			Operator: api.TolerationOpEqual,
 | |
| 			Value:    "baz",
 | |
| 			Effect:   api.TaintEffectNoSchedule,
 | |
| 		},
 | |
| 		"faz-nosched": {
 | |
| 			Key:      "faz",
 | |
| 			Operator: api.TolerationOpExists,
 | |
| 			Effect:   api.TaintEffectNoSchedule,
 | |
| 		},
 | |
| 		"faz-baz-nosched": {
 | |
| 			Key:      "faz",
 | |
| 			Operator: api.TolerationOpEqual,
 | |
| 			Value:    "baz",
 | |
| 			Effect:   api.TaintEffectNoSchedule,
 | |
| 		},
 | |
| 		"foo-prefnosched": {
 | |
| 			Key:      "foo",
 | |
| 			Operator: api.TolerationOpExists,
 | |
| 			Effect:   api.TaintEffectPreferNoSchedule,
 | |
| 		},
 | |
| 		"foo-noexec": {
 | |
| 			Key:      "foo",
 | |
| 			Operator: api.TolerationOpExists,
 | |
| 			Effect:   api.TaintEffectNoExecute,
 | |
| 		},
 | |
| 		"foo-bar-noexec": {
 | |
| 			Key:      "foo",
 | |
| 			Operator: api.TolerationOpEqual,
 | |
| 			Value:    "bar",
 | |
| 			Effect:   api.TaintEffectNoExecute,
 | |
| 		},
 | |
| 		"foo-noexec-10": {
 | |
| 			Key:               "foo",
 | |
| 			Operator:          api.TolerationOpExists,
 | |
| 			Effect:            api.TaintEffectNoExecute,
 | |
| 			TolerationSeconds: utilpointer.Int64Ptr(10),
 | |
| 		},
 | |
| 		"foo-noexec-0": {
 | |
| 			Key:               "foo",
 | |
| 			Operator:          api.TolerationOpExists,
 | |
| 			Effect:            api.TaintEffectNoExecute,
 | |
| 			TolerationSeconds: utilpointer.Int64Ptr(0),
 | |
| 		},
 | |
| 		"foo-bar-noexec-10": {
 | |
| 			Key:               "foo",
 | |
| 			Operator:          api.TolerationOpEqual,
 | |
| 			Value:             "bar",
 | |
| 			Effect:            api.TaintEffectNoExecute,
 | |
| 			TolerationSeconds: utilpointer.Int64Ptr(10),
 | |
| 		},
 | |
| 	}
 | |
| )
 | |
| 
 | |
| func TestIsSuperset(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		toleration string
 | |
| 		ss         []string // t should be a superset of these
 | |
| 	}{{
 | |
| 		"all",
 | |
| 		[]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"},
 | |
| 	}, {
 | |
| 		"all-nosched",
 | |
| 		[]string{"foo-nosched", "foo-bar-nosched", "foo-baz-nosched", "faz-nosched", "faz-baz-nosched"},
 | |
| 	}, {
 | |
| 		"all-noexec",
 | |
| 		[]string{"foo-noexec", "foo-bar-noexec", "foo-noexec-10", "foo-noexec-0", "foo-bar-noexec-10"},
 | |
| 	}, {
 | |
| 		"foo",
 | |
| 		[]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"},
 | |
| 	}, {
 | |
| 		"foo-bar",
 | |
| 		[]string{"foo-bar-nosched", "foo-bar-noexec", "foo-bar-noexec-10"},
 | |
| 	}, {
 | |
| 		"foo-nosched",
 | |
| 		[]string{"foo-bar-nosched", "foo-baz-nosched"},
 | |
| 	}, {
 | |
| 		"foo-bar-nosched",
 | |
| 		[]string{},
 | |
| 	}, {
 | |
| 		"faz-nosched",
 | |
| 		[]string{"faz-baz-nosched"},
 | |
| 	}, {
 | |
| 		"faz-baz-nosched",
 | |
| 		[]string{},
 | |
| 	}, {
 | |
| 		"foo-prenosched",
 | |
| 		[]string{},
 | |
| 	}, {
 | |
| 		"foo-noexec",
 | |
| 		[]string{"foo-noexec", "foo-bar-noexec", "foo-noexec-10", "foo-noexec-0", "foo-bar-noexec-10"},
 | |
| 	}, {
 | |
| 		"foo-bar-noexec",
 | |
| 		[]string{"foo-bar-noexec-10"},
 | |
| 	}, {
 | |
| 		"foo-noexec-10",
 | |
| 		[]string{"foo-noexec-0", "foo-bar-noexec-10"},
 | |
| 	}, {
 | |
| 		"foo-noexec-0",
 | |
| 		[]string{},
 | |
| 	}, {
 | |
| 		"foo-bar-noexec-10",
 | |
| 		[]string{},
 | |
| 	}}
 | |
| 
 | |
| 	assertSuperset := func(t *testing.T, super, sub string) {
 | |
| 		assert.True(t, isSuperset(tolerations[super], tolerations[sub]),
 | |
| 			"%s should be a superset of %s", super, sub)
 | |
| 	}
 | |
| 	assertNotSuperset := func(t *testing.T, super, sub string) {
 | |
| 		assert.False(t, isSuperset(tolerations[super], tolerations[sub]),
 | |
| 			"%s should NOT be a superset of %s", super, sub)
 | |
| 	}
 | |
| 	contains := func(ss []string, s string) bool {
 | |
| 		for _, str := range ss {
 | |
| 			if str == s {
 | |
| 				return true
 | |
| 			}
 | |
| 		}
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	for _, test := range tests {
 | |
| 		t.Run(test.toleration, func(t *testing.T) {
 | |
| 			for name := range tolerations {
 | |
| 				if name == test.toleration || contains(test.ss, name) {
 | |
| 					assertSuperset(t, test.toleration, name)
 | |
| 				} else {
 | |
| 					assertNotSuperset(t, test.toleration, name)
 | |
| 				}
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestVerifyAgainstWhitelist(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		testName  string
 | |
| 		input     []string
 | |
| 		whitelist []string
 | |
| 		expected  bool
 | |
| 	}{
 | |
| 		{
 | |
| 			testName:  "equal input and whitelist",
 | |
| 			input:     []string{"foo-bar-nosched", "foo-baz-nosched"},
 | |
| 			whitelist: []string{"foo-bar-nosched", "foo-baz-nosched"},
 | |
| 			expected:  true,
 | |
| 		},
 | |
| 		{
 | |
| 			testName:  "duplicate input allowed",
 | |
| 			input:     []string{"foo-bar-nosched", "foo-bar-nosched"},
 | |
| 			whitelist: []string{"foo-bar-nosched", "foo-baz-nosched"},
 | |
| 			expected:  true,
 | |
| 		},
 | |
| 		{
 | |
| 			testName:  "allow all",
 | |
| 			input:     []string{"foo-bar-nosched", "foo-bar-nosched"},
 | |
| 			whitelist: []string{"all"},
 | |
| 			expected:  true,
 | |
| 		},
 | |
| 		{
 | |
| 			testName:  "duplicate input forbidden",
 | |
| 			input:     []string{"foo-bar-nosched", "foo-bar-nosched"},
 | |
| 			whitelist: []string{"foo-baz-nosched"},
 | |
| 			expected:  false,
 | |
| 		},
 | |
| 		{
 | |
| 			testName:  "value mismatch",
 | |
| 			input:     []string{"foo-bar-nosched", "foo-baz-nosched"},
 | |
| 			whitelist: []string{"foo-baz-nosched"},
 | |
| 			expected:  false,
 | |
| 		},
 | |
| 		{
 | |
| 			testName:  "input does not exist in whitelist",
 | |
| 			input:     []string{"foo-bar-nosched"},
 | |
| 			whitelist: []string{"foo-baz-nosched"},
 | |
| 			expected:  false,
 | |
| 		},
 | |
| 		{
 | |
| 			testName:  "disjoint sets",
 | |
| 			input:     []string{"foo-bar"},
 | |
| 			whitelist: []string{"foo-nosched"},
 | |
| 			expected:  false,
 | |
| 		},
 | |
| 		{
 | |
| 			testName:  "empty whitelist",
 | |
| 			input:     []string{"foo-bar"},
 | |
| 			whitelist: []string{},
 | |
| 			expected:  true,
 | |
| 		},
 | |
| 		{
 | |
| 			testName:  "empty input",
 | |
| 			input:     []string{},
 | |
| 			whitelist: []string{"foo-bar"},
 | |
| 			expected:  true,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, c := range tests {
 | |
| 		t.Run(c.testName, func(t *testing.T) {
 | |
| 			actual := VerifyAgainstWhitelist(getTolerations(c.input), getTolerations(c.whitelist))
 | |
| 			assert.Equal(t, c.expected, actual)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestMergeTolerations(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name     string
 | |
| 		a, b     []string
 | |
| 		expected []string
 | |
| 	}{{
 | |
| 		name:     "disjoint",
 | |
| 		a:        []string{"foo-bar-nosched", "faz-baz-nosched", "foo-noexec-10"},
 | |
| 		b:        []string{"foo-prefnosched", "foo-baz-nosched"},
 | |
| 		expected: []string{"foo-bar-nosched", "faz-baz-nosched", "foo-noexec-10", "foo-prefnosched", "foo-baz-nosched"},
 | |
| 	}, {
 | |
| 		name:     "duplicate",
 | |
| 		a:        []string{"foo-bar-nosched", "faz-baz-nosched", "foo-noexec-10"},
 | |
| 		b:        []string{"foo-bar-nosched", "faz-baz-nosched", "foo-noexec-10"},
 | |
| 		expected: []string{"foo-bar-nosched", "faz-baz-nosched", "foo-noexec-10"},
 | |
| 	}, {
 | |
| 		name:     "merge redundant",
 | |
| 		a:        []string{"foo-bar-nosched", "foo-baz-nosched"},
 | |
| 		b:        []string{"foo-nosched", "faz-baz-nosched"},
 | |
| 		expected: []string{"foo-nosched", "faz-baz-nosched"},
 | |
| 	}, {
 | |
| 		name:     "merge all",
 | |
| 		a:        []string{"foo-bar-nosched", "foo-baz-nosched", "foo-noexec-10"},
 | |
| 		b:        []string{"all"},
 | |
| 		expected: []string{"all"},
 | |
| 	}, {
 | |
| 		name:     "merge into all",
 | |
| 		a:        []string{"all"},
 | |
| 		b:        []string{"foo-bar-nosched", "foo-baz-nosched", "foo-noexec-10"},
 | |
| 		expected: []string{"all"},
 | |
| 	}}
 | |
| 
 | |
| 	for _, test := range tests {
 | |
| 		t.Run(test.name, func(t *testing.T) {
 | |
| 			actual := MergeTolerations(getTolerations(test.a), getTolerations(test.b))
 | |
| 			require.Len(t, actual, len(test.expected))
 | |
| 			for i, expect := range getTolerations(test.expected) {
 | |
| 				assert.Equal(t, expect, actual[i], "expected[%d] = %s", i, test.expected[i])
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestFuzzed(t *testing.T) {
 | |
| 	r := rand.New(rand.NewSource(1234)) // Fixed source to prevent flakes.
 | |
| 
 | |
| 	const (
 | |
| 		allProbability               = 0.01 // Chance of getting a tolerate all
 | |
| 		existsProbability            = 0.3
 | |
| 		tolerationSecondsProbability = 0.5
 | |
| 	)
 | |
| 	effects := []api.TaintEffect{"", api.TaintEffectNoExecute, api.TaintEffectNoSchedule, api.TaintEffectPreferNoSchedule}
 | |
| 	genToleration := func() api.Toleration {
 | |
| 		gen := api.Toleration{
 | |
| 			Effect: effects[r.Intn(len(effects))],
 | |
| 		}
 | |
| 		if r.Float32() < allProbability {
 | |
| 			gen = tolerations["all"]
 | |
| 			return gen
 | |
| 		}
 | |
| 		// Small key/value space to encourage collisions
 | |
| 		gen.Key = strings.Repeat("a", r.Intn(6)+1)
 | |
| 		if r.Float32() < existsProbability {
 | |
| 			gen.Operator = api.TolerationOpExists
 | |
| 		} else {
 | |
| 			gen.Operator = api.TolerationOpEqual
 | |
| 			gen.Value = strings.Repeat("b", r.Intn(6)+1)
 | |
| 		}
 | |
| 		if gen.Effect == api.TaintEffectNoExecute && r.Float32() < tolerationSecondsProbability {
 | |
| 			gen.TolerationSeconds = utilpointer.Int64Ptr(r.Int63n(10))
 | |
| 		}
 | |
| 		// Ensure only valid tolerations are generated.
 | |
| 		require.NoError(t, validation.ValidateTolerations([]api.Toleration{gen}, field.NewPath("")).ToAggregate(), "%#v", gen)
 | |
| 		return gen
 | |
| 	}
 | |
| 	genTolerations := func() []api.Toleration {
 | |
| 		result := []api.Toleration{}
 | |
| 		for i := 0; i < r.Intn(10); i++ {
 | |
| 			result = append(result, genToleration())
 | |
| 		}
 | |
| 		return result
 | |
| 	}
 | |
| 
 | |
| 	// Check whether the toleration is a subset of a toleration in the set.
 | |
| 	isContained := func(toleration api.Toleration, set []api.Toleration) bool {
 | |
| 		for _, ss := range set {
 | |
| 			if isSuperset(ss, toleration) {
 | |
| 				return true
 | |
| 			}
 | |
| 		}
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	const iterations = 1000
 | |
| 
 | |
| 	debugMsg := func(tolerations ...[]api.Toleration) string {
 | |
| 		str, err := json.Marshal(tolerations)
 | |
| 		if err != nil {
 | |
| 			return fmt.Sprintf("[ERR: %v] %v", err, tolerations)
 | |
| 		}
 | |
| 		return string(str)
 | |
| 	}
 | |
| 	t.Run("VerifyAgainstWhitelist", func(t *testing.T) {
 | |
| 		for i := 0; i < iterations; i++ {
 | |
| 			input := genTolerations()
 | |
| 			whitelist := append(genTolerations(), genToleration()) // Non-empty
 | |
| 			if VerifyAgainstWhitelist(input, whitelist) {
 | |
| 				for _, tol := range input {
 | |
| 					require.True(t, isContained(tol, whitelist), debugMsg(input, whitelist))
 | |
| 				}
 | |
| 			} else {
 | |
| 				uncontained := false
 | |
| 				for _, tol := range input {
 | |
| 					if !isContained(tol, whitelist) {
 | |
| 						uncontained = true
 | |
| 						break
 | |
| 					}
 | |
| 				}
 | |
| 				require.True(t, uncontained, debugMsg(input, whitelist))
 | |
| 			}
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	t.Run("MergeTolerations", func(t *testing.T) {
 | |
| 		for i := 0; i < iterations; i++ {
 | |
| 			a := genTolerations()
 | |
| 			b := genTolerations()
 | |
| 			result := MergeTolerations(a, b)
 | |
| 			for _, tol := range append(a, b...) {
 | |
| 				require.True(t, isContained(tol, result), debugMsg(a, b, result))
 | |
| 			}
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func getTolerations(names []string) []api.Toleration {
 | |
| 	result := []api.Toleration{}
 | |
| 	for _, name := range names {
 | |
| 		result = append(result, tolerations[name])
 | |
| 	}
 | |
| 	return result
 | |
| }
 |