k8s.io/client-go@v0.31.1/features/envvar_test.go (about)

     1  /*
     2  Copyright 2024 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 features
    18  
    19  import (
    20  	"fmt"
    21  	"testing"
    22  
    23  	"github.com/stretchr/testify/require"
    24  )
    25  
    26  var defaultTestFeatures = map[Feature]FeatureSpec{
    27  	"TestAlpha": {
    28  		Default:       false,
    29  		LockToDefault: false,
    30  		PreRelease:    "Alpha",
    31  	},
    32  	"TestBeta": {
    33  		Default:       true,
    34  		LockToDefault: false,
    35  		PreRelease:    "Beta",
    36  	},
    37  }
    38  
    39  func TestEnvVarFeatureGates(t *testing.T) {
    40  	expectedDefaultFeaturesState := map[Feature]bool{"TestAlpha": false, "TestBeta": true}
    41  	copyExpectedStateMap := func(toCopy map[Feature]bool) map[Feature]bool {
    42  		m := map[Feature]bool{}
    43  		for k, v := range toCopy {
    44  			m[k] = v
    45  		}
    46  		return m
    47  	}
    48  
    49  	scenarios := []struct {
    50  		name              string
    51  		features          map[Feature]FeatureSpec
    52  		envVariables      map[string]string
    53  		setMethodFeatures map[Feature]bool
    54  
    55  		expectedFeaturesState                           map[Feature]bool
    56  		expectedInternalEnabledViaEnvVarFeatureState    map[Feature]bool
    57  		expectedInternalEnabledViaSetMethodFeatureState map[Feature]bool
    58  	}{
    59  		{
    60  			name: "can add empty features",
    61  		},
    62  		{
    63  			name:                  "no env var, features get Defaults assigned",
    64  			features:              defaultTestFeatures,
    65  			expectedFeaturesState: expectedDefaultFeaturesState,
    66  		},
    67  		{
    68  			name:                  "incorrect env var, feature gets Default assigned",
    69  			features:              defaultTestFeatures,
    70  			envVariables:          map[string]string{"TestAlpha": "true"},
    71  			expectedFeaturesState: expectedDefaultFeaturesState,
    72  		},
    73  		{
    74  			name:         "correct env var changes the feature gets state",
    75  			features:     defaultTestFeatures,
    76  			envVariables: map[string]string{"KUBE_FEATURE_TestAlpha": "true"},
    77  			expectedFeaturesState: func() map[Feature]bool {
    78  				expectedDefaultFeaturesStateCopy := copyExpectedStateMap(expectedDefaultFeaturesState)
    79  				expectedDefaultFeaturesStateCopy["TestAlpha"] = true
    80  				return expectedDefaultFeaturesStateCopy
    81  			}(),
    82  			expectedInternalEnabledViaEnvVarFeatureState: map[Feature]bool{"TestAlpha": true},
    83  		},
    84  		{
    85  			name:                  "incorrect env var value gets ignored",
    86  			features:              defaultTestFeatures,
    87  			envVariables:          map[string]string{"KUBE_FEATURE_TestAlpha": "TrueFalse"},
    88  			expectedFeaturesState: expectedDefaultFeaturesState,
    89  		},
    90  		{
    91  			name:                  "empty env var value gets ignored",
    92  			features:              defaultTestFeatures,
    93  			envVariables:          map[string]string{"KUBE_FEATURE_TestAlpha": ""},
    94  			expectedFeaturesState: expectedDefaultFeaturesState,
    95  		},
    96  		{
    97  			name: "a feature LockToDefault wins",
    98  			features: map[Feature]FeatureSpec{
    99  				"TestAlpha": {
   100  					Default:       true,
   101  					LockToDefault: true,
   102  					PreRelease:    "Alpha",
   103  				},
   104  			},
   105  			envVariables:          map[string]string{"KUBE_FEATURE_TestAlpha": "False"},
   106  			expectedFeaturesState: map[Feature]bool{"TestAlpha": true},
   107  		},
   108  		{
   109  			name: "setting a feature to LockToDefault changes the internal state",
   110  			features: map[Feature]FeatureSpec{
   111  				"TestAlpha": {
   112  					Default:       true,
   113  					LockToDefault: true,
   114  					PreRelease:    "Alpha",
   115  				},
   116  			},
   117  			envVariables:          map[string]string{"KUBE_FEATURE_TestAlpha": "True"},
   118  			expectedFeaturesState: map[Feature]bool{"TestAlpha": true},
   119  			expectedInternalEnabledViaEnvVarFeatureState: map[Feature]bool{"TestAlpha": true},
   120  		},
   121  		{
   122  			name:                  "setting a feature via the Set method works",
   123  			features:              defaultTestFeatures,
   124  			setMethodFeatures:     map[Feature]bool{"TestAlpha": true},
   125  			expectedFeaturesState: map[Feature]bool{"TestAlpha": true},
   126  			expectedInternalEnabledViaSetMethodFeatureState: map[Feature]bool{"TestAlpha": true},
   127  		},
   128  		{
   129  			name:                  "setting a feature via the Set method wins",
   130  			features:              defaultTestFeatures,
   131  			setMethodFeatures:     map[Feature]bool{"TestAlpha": false},
   132  			envVariables:          map[string]string{"KUBE_FEATURE_TestAlpha": "True"},
   133  			expectedFeaturesState: map[Feature]bool{"TestAlpha": false},
   134  			expectedInternalEnabledViaEnvVarFeatureState:    map[Feature]bool{"TestAlpha": true},
   135  			expectedInternalEnabledViaSetMethodFeatureState: map[Feature]bool{"TestAlpha": false},
   136  		},
   137  	}
   138  	for _, scenario := range scenarios {
   139  		t.Run(scenario.name, func(t *testing.T) {
   140  			for k, v := range scenario.envVariables {
   141  				t.Setenv(k, v)
   142  			}
   143  			target := newEnvVarFeatureGates(scenario.features)
   144  
   145  			for k, v := range scenario.setMethodFeatures {
   146  				err := target.Set(k, v)
   147  				require.NoError(t, err)
   148  			}
   149  			for expectedFeature, expectedValue := range scenario.expectedFeaturesState {
   150  				actualValue := target.Enabled(expectedFeature)
   151  				require.Equal(t, actualValue, expectedValue, "expected feature=%v, to be=%v, not=%v", expectedFeature, expectedValue, actualValue)
   152  			}
   153  
   154  			enabledViaEnvVarInternalMap := target.enabledViaEnvVar.Load().(map[Feature]bool)
   155  			require.Len(t, enabledViaEnvVarInternalMap, len(scenario.expectedInternalEnabledViaEnvVarFeatureState))
   156  			for expectedFeatureName, expectedFeatureValue := range scenario.expectedInternalEnabledViaEnvVarFeatureState {
   157  				actualFeatureValue, wasExpectedFeatureFound := enabledViaEnvVarInternalMap[expectedFeatureName]
   158  				if !wasExpectedFeatureFound {
   159  					t.Errorf("feature %v has not been found in enabledViaEnvVarInternalMap", expectedFeatureName)
   160  				}
   161  				require.Equal(t, expectedFeatureValue, actualFeatureValue, "feature %v has incorrect value = %v, expected = %v", expectedFeatureName, actualFeatureValue, expectedFeatureValue)
   162  			}
   163  
   164  			enabledViaSetMethodInternalMap := target.enabledViaSetMethod
   165  			require.Len(t, enabledViaSetMethodInternalMap, len(scenario.expectedInternalEnabledViaSetMethodFeatureState))
   166  			for expectedFeatureName, expectedFeatureValue := range scenario.expectedInternalEnabledViaSetMethodFeatureState {
   167  				actualFeatureValue, wasExpectedFeatureFound := enabledViaSetMethodInternalMap[expectedFeatureName]
   168  				if !wasExpectedFeatureFound {
   169  					t.Errorf("feature %v has not been found in enabledViaSetMethod", expectedFeatureName)
   170  				}
   171  				require.Equal(t, expectedFeatureValue, actualFeatureValue, "feature %v has incorrect value = %v, expected = %v", expectedFeatureName, actualFeatureValue, expectedFeatureValue)
   172  			}
   173  		})
   174  	}
   175  }
   176  
   177  func TestEnvVarFeatureGatesEnabledPanic(t *testing.T) {
   178  	target := newEnvVarFeatureGates(nil)
   179  	require.PanicsWithError(t, fmt.Errorf("feature %q is not registered in FeatureGates %q", "UnknownFeature", target.callSiteName).Error(), func() { target.Enabled("UnknownFeature") })
   180  }
   181  
   182  func TestHasAlreadyReadEnvVar(t *testing.T) {
   183  	target := newEnvVarFeatureGates(nil)
   184  	require.False(t, target.hasAlreadyReadEnvVar())
   185  
   186  	_ = target.getEnabledMapFromEnvVar()
   187  	require.True(t, target.hasAlreadyReadEnvVar())
   188  }
   189  
   190  func TestEnvVarFeatureGatesSetNegative(t *testing.T) {
   191  	scenarios := []struct {
   192  		name         string
   193  		features     map[Feature]FeatureSpec
   194  		featureName  Feature
   195  		featureValue bool
   196  
   197  		expectedErr func(string) error
   198  	}{
   199  		{
   200  			name:     "empty feature name returns an error",
   201  			features: defaultTestFeatures,
   202  			expectedErr: func(callSiteName string) error {
   203  				return fmt.Errorf("feature %q is not registered in FeatureGates %q", "", callSiteName)
   204  			},
   205  		},
   206  		{
   207  			name:        "setting unknown feature returns an error",
   208  			features:    defaultTestFeatures,
   209  			featureName: "Unknown",
   210  			expectedErr: func(callSiteName string) error {
   211  				return fmt.Errorf("feature %q is not registered in FeatureGates %q", "Unknown", callSiteName)
   212  			},
   213  		},
   214  		{
   215  			name:         "setting locked feature returns an error",
   216  			features:     map[Feature]FeatureSpec{"LockedFeature": {LockToDefault: true, Default: true}},
   217  			featureName:  "LockedFeature",
   218  			featureValue: false,
   219  			expectedErr: func(_ string) error {
   220  				return fmt.Errorf("cannot set feature gate %q to %v, feature is locked to %v", "LockedFeature", false, true)
   221  			},
   222  		},
   223  	}
   224  
   225  	for _, scenario := range scenarios {
   226  		t.Run(scenario.name, func(t *testing.T) {
   227  			target := newEnvVarFeatureGates(scenario.features)
   228  
   229  			err := target.Set(scenario.featureName, scenario.featureValue)
   230  			require.Equal(t, scenario.expectedErr(target.callSiteName), err)
   231  		})
   232  	}
   233  }