k8s.io/apiserver@v0.31.1/pkg/util/version/registry_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 version
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  	"testing"
    23  
    24  	"github.com/spf13/pflag"
    25  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    26  	"k8s.io/apimachinery/pkg/util/version"
    27  	"k8s.io/component-base/featuregate"
    28  )
    29  
    30  const (
    31  	testComponent = "test"
    32  )
    33  
    34  func TestEffectiveVersionRegistry(t *testing.T) {
    35  	r := NewComponentGlobalsRegistry()
    36  	ver1 := NewEffectiveVersion("1.31")
    37  	ver2 := NewEffectiveVersion("1.28")
    38  
    39  	if r.EffectiveVersionFor(testComponent) != nil {
    40  		t.Fatalf("expected nil EffectiveVersion initially")
    41  	}
    42  	if err := r.Register(testComponent, ver1, nil); err != nil {
    43  		t.Fatalf("expected no error to register new component, but got err: %v", err)
    44  	}
    45  	if !r.EffectiveVersionFor(testComponent).EqualTo(ver1) {
    46  		t.Fatalf("expected EffectiveVersionFor to return the version registered")
    47  	}
    48  	// overwrite
    49  	if err := r.Register(testComponent, ver2, nil); err == nil {
    50  		t.Fatalf("expected error to register existing component when override is false")
    51  	}
    52  	if !r.EffectiveVersionFor(testComponent).EqualTo(ver1) {
    53  		t.Fatalf("expected EffectiveVersionFor to return the version overridden")
    54  	}
    55  }
    56  
    57  func testRegistry(t *testing.T) *componentGlobalsRegistry {
    58  	r := NewComponentGlobalsRegistry()
    59  	verKube := NewEffectiveVersion("1.31")
    60  	fgKube := featuregate.NewVersionedFeatureGate(version.MustParse("0.0"))
    61  	err := fgKube.AddVersioned(map[featuregate.Feature]featuregate.VersionedSpecs{
    62  		"kubeA": {
    63  			{Version: version.MustParse("1.31"), Default: true, LockToDefault: true, PreRelease: featuregate.GA},
    64  			{Version: version.MustParse("1.28"), Default: false, PreRelease: featuregate.Beta},
    65  			{Version: version.MustParse("1.27"), Default: false, PreRelease: featuregate.Alpha},
    66  		},
    67  		"kubeB": {
    68  			{Version: version.MustParse("1.30"), Default: false, PreRelease: featuregate.Alpha},
    69  		},
    70  		"commonC": {
    71  			{Version: version.MustParse("1.29"), Default: true, PreRelease: featuregate.Beta},
    72  			{Version: version.MustParse("1.27"), Default: false, PreRelease: featuregate.Alpha},
    73  		},
    74  	})
    75  	if err != nil {
    76  		t.Fatal(err)
    77  	}
    78  
    79  	verTest := NewEffectiveVersion("2.8")
    80  	fgTest := featuregate.NewVersionedFeatureGate(version.MustParse("0.0"))
    81  	err = fgTest.AddVersioned(map[featuregate.Feature]featuregate.VersionedSpecs{
    82  		"testA": {
    83  			{Version: version.MustParse("2.10"), Default: true, PreRelease: featuregate.GA},
    84  			{Version: version.MustParse("2.8"), Default: false, PreRelease: featuregate.Beta},
    85  			{Version: version.MustParse("2.7"), Default: false, PreRelease: featuregate.Alpha},
    86  		},
    87  		"testB": {
    88  			{Version: version.MustParse("2.9"), Default: false, PreRelease: featuregate.Alpha},
    89  		},
    90  		"commonC": {
    91  			{Version: version.MustParse("2.9"), Default: true, PreRelease: featuregate.Beta},
    92  			{Version: version.MustParse("2.7"), Default: false, PreRelease: featuregate.Alpha},
    93  		},
    94  	})
    95  	if err != nil {
    96  		t.Fatal(err)
    97  	}
    98  	utilruntime.Must(r.Register(DefaultKubeComponent, verKube, fgKube))
    99  	utilruntime.Must(r.Register(testComponent, verTest, fgTest))
   100  	return r
   101  }
   102  
   103  func TestVersionFlagOptions(t *testing.T) {
   104  	r := testRegistry(t)
   105  	emuVers := strings.Join(r.unsafeVersionFlagOptions(true), "\n")
   106  	expectedEmuVers := "kube=1.31..1.31 (default=1.31)\ntest=2.8..2.8 (default=2.8)"
   107  	if emuVers != expectedEmuVers {
   108  		t.Errorf("wanted emulation version flag options to be: %s, got %s", expectedEmuVers, emuVers)
   109  	}
   110  	minCompVers := strings.Join(r.unsafeVersionFlagOptions(false), "\n")
   111  	expectedMinCompVers := "kube=1.30..1.31 (default=1.30)\ntest=2.7..2.8 (default=2.7)"
   112  	if minCompVers != expectedMinCompVers {
   113  		t.Errorf("wanted min compatibility version flag options to be: %s, got %s", expectedMinCompVers, minCompVers)
   114  	}
   115  }
   116  
   117  func TestVersionFlagOptionsWithMapping(t *testing.T) {
   118  	r := testRegistry(t)
   119  	utilruntime.Must(r.SetEmulationVersionMapping(testComponent, DefaultKubeComponent,
   120  		func(from *version.Version) *version.Version { return from.OffsetMinor(3) }))
   121  	emuVers := strings.Join(r.unsafeVersionFlagOptions(true), "\n")
   122  	expectedEmuVers := "test=2.8..2.8 (default=2.8)"
   123  	if emuVers != expectedEmuVers {
   124  		t.Errorf("wanted emulation version flag options to be: %s, got %s", expectedEmuVers, emuVers)
   125  	}
   126  	minCompVers := strings.Join(r.unsafeVersionFlagOptions(false), "\n")
   127  	expectedMinCompVers := "kube=1.30..1.31 (default=1.30)\ntest=2.7..2.8 (default=2.7)"
   128  	if minCompVers != expectedMinCompVers {
   129  		t.Errorf("wanted min compatibility version flag options to be: %s, got %s", expectedMinCompVers, minCompVers)
   130  	}
   131  }
   132  
   133  func TestVersionedFeatureGateFlag(t *testing.T) {
   134  	r := testRegistry(t)
   135  	known := strings.Join(r.unsafeKnownFeatures(), "\n")
   136  	expectedKnown := "kube:AllAlpha=true|false (ALPHA - default=false)\n" +
   137  		"kube:AllBeta=true|false (BETA - default=false)\n" +
   138  		"kube:commonC=true|false (BETA - default=true)\n" +
   139  		"kube:kubeB=true|false (ALPHA - default=false)\n" +
   140  		"test:AllAlpha=true|false (ALPHA - default=false)\n" +
   141  		"test:AllBeta=true|false (BETA - default=false)\n" +
   142  		"test:commonC=true|false (ALPHA - default=false)\n" +
   143  		"test:testA=true|false (BETA - default=false)"
   144  	if known != expectedKnown {
   145  		t.Errorf("wanted min compatibility version flag options to be:\n%s, got:\n%s", expectedKnown, known)
   146  	}
   147  }
   148  
   149  func TestFlags(t *testing.T) {
   150  	tests := []struct {
   151  		name                         string
   152  		flags                        []string
   153  		parseError                   string
   154  		expectedKubeEmulationVersion string
   155  		expectedTestEmulationVersion string
   156  		expectedKubeFeatureValues    map[featuregate.Feature]bool
   157  		expectedTestFeatureValues    map[featuregate.Feature]bool
   158  	}{
   159  		{
   160  			name:                         "setting kube emulation version",
   161  			flags:                        []string{"--emulated-version=kube=1.30"},
   162  			expectedKubeEmulationVersion: "1.30",
   163  		},
   164  		{
   165  			name: "setting kube emulation version twice",
   166  			flags: []string{
   167  				"--emulated-version=kube=1.30",
   168  				"--emulated-version=kube=1.32",
   169  			},
   170  			parseError: "duplicate version flag, kube=1.30 and kube=1.32",
   171  		},
   172  		{
   173  			name:                         "prefix v ok",
   174  			flags:                        []string{"--emulated-version=kube=v1.30"},
   175  			expectedKubeEmulationVersion: "1.30",
   176  		},
   177  		{
   178  			name:       "patch version not ok",
   179  			flags:      []string{"--emulated-version=kube=1.30.2"},
   180  			parseError: "patch version not allowed, got: kube=1.30.2",
   181  		},
   182  		{
   183  			name:                         "setting test emulation version",
   184  			flags:                        []string{"--emulated-version=test=2.7"},
   185  			expectedKubeEmulationVersion: "1.31",
   186  			expectedTestEmulationVersion: "2.7",
   187  		},
   188  		{
   189  			name:                         "version missing component default to kube",
   190  			flags:                        []string{"--emulated-version=1.30"},
   191  			expectedKubeEmulationVersion: "1.30",
   192  		},
   193  		{
   194  			name:       "version missing component default to kube with duplicate",
   195  			flags:      []string{"--emulated-version=1.30", "--emulated-version=kube=1.30"},
   196  			parseError: "duplicate version flag, kube=1.30 and kube=1.30",
   197  		},
   198  		{
   199  			name:       "version unregistered component",
   200  			flags:      []string{"--emulated-version=test3=1.31"},
   201  			parseError: "component not registered: test3",
   202  		},
   203  		{
   204  			name:       "invalid version",
   205  			flags:      []string{"--emulated-version=test=1.foo"},
   206  			parseError: "illegal version string \"1.foo\"",
   207  		},
   208  		{
   209  			name: "setting test feature flag",
   210  			flags: []string{
   211  				"--emulated-version=test=2.7",
   212  				"--feature-gates=test:testA=true",
   213  			},
   214  			expectedKubeEmulationVersion: "1.31",
   215  			expectedTestEmulationVersion: "2.7",
   216  			expectedKubeFeatureValues:    map[featuregate.Feature]bool{"kubeA": true, "kubeB": false, "commonC": true},
   217  			expectedTestFeatureValues:    map[featuregate.Feature]bool{"testA": true, "testB": false, "commonC": false},
   218  		},
   219  		{
   220  			name: "setting future test feature flag",
   221  			flags: []string{
   222  				"--emulated-version=test=2.7",
   223  				"--feature-gates=test:testA=true,test:testB=true",
   224  			},
   225  			parseError: "cannot set feature gate testB to true, feature is PreAlpha at emulated version 2.7",
   226  		},
   227  		{
   228  			name: "setting kube feature flag",
   229  			flags: []string{
   230  				"--emulated-version=test=2.7",
   231  				"--emulated-version=kube=1.30",
   232  				"--feature-gates=kubeB=false,test:commonC=true",
   233  				"--feature-gates=commonC=false,kubeB=true",
   234  			},
   235  			expectedKubeEmulationVersion: "1.30",
   236  			expectedTestEmulationVersion: "2.7",
   237  			expectedKubeFeatureValues:    map[featuregate.Feature]bool{"kubeA": false, "kubeB": true, "commonC": false},
   238  			expectedTestFeatureValues:    map[featuregate.Feature]bool{"testA": false, "testB": false, "commonC": true},
   239  		},
   240  		{
   241  			name: "setting kube feature flag with different prefix",
   242  			flags: []string{
   243  				"--emulated-version=test=2.7",
   244  				"--emulated-version=kube=1.30",
   245  				"--feature-gates=kube:kubeB=false,test:commonC=true",
   246  				"--feature-gates=commonC=false,kubeB=true",
   247  			},
   248  			parseError: "set kube feature gates with default empty prefix or kube: prefix consistently, do not mix use",
   249  		},
   250  		{
   251  			name: "setting locked kube feature flag",
   252  			flags: []string{
   253  				"--emulated-version=test=2.7",
   254  				"--feature-gates=kubeA=false",
   255  			},
   256  			parseError: "cannot set feature gate kubeA to false, feature is locked to true",
   257  		},
   258  		{
   259  			name: "setting unknown test feature flag",
   260  			flags: []string{
   261  				"--emulated-version=test=2.7",
   262  				"--feature-gates=test:testD=true",
   263  			},
   264  			parseError: "unrecognized feature gate: testD",
   265  		},
   266  		{
   267  			name: "setting unknown component feature flag",
   268  			flags: []string{
   269  				"--emulated-version=test=2.7",
   270  				"--feature-gates=test3:commonC=true",
   271  			},
   272  			parseError: "component not registered: test3",
   273  		},
   274  	}
   275  	for i, test := range tests {
   276  		t.Run(test.name, func(t *testing.T) {
   277  			fs := pflag.NewFlagSet("testflag", pflag.ContinueOnError)
   278  			r := testRegistry(t)
   279  			r.AddFlags(fs)
   280  			err := fs.Parse(test.flags)
   281  			if err == nil {
   282  				err = r.Set()
   283  			}
   284  			if test.parseError != "" {
   285  				if err == nil || !strings.Contains(err.Error(), test.parseError) {
   286  					t.Fatalf("%d: Parse() expected: %v, got: %v", i, test.parseError, err)
   287  				}
   288  				return
   289  			}
   290  			if err != nil {
   291  				t.Fatalf("%d: Parse() expected: nil, got: %v", i, err)
   292  			}
   293  			if len(test.expectedKubeEmulationVersion) > 0 {
   294  				assertVersionEqualTo(t, r.EffectiveVersionFor(DefaultKubeComponent).EmulationVersion(), test.expectedKubeEmulationVersion)
   295  			}
   296  			if len(test.expectedTestEmulationVersion) > 0 {
   297  				assertVersionEqualTo(t, r.EffectiveVersionFor(testComponent).EmulationVersion(), test.expectedTestEmulationVersion)
   298  			}
   299  			for f, v := range test.expectedKubeFeatureValues {
   300  				if r.FeatureGateFor(DefaultKubeComponent).Enabled(f) != v {
   301  					t.Errorf("%d: expected kube feature Enabled(%s)=%v", i, f, v)
   302  				}
   303  			}
   304  			for f, v := range test.expectedTestFeatureValues {
   305  				if r.FeatureGateFor(testComponent).Enabled(f) != v {
   306  					t.Errorf("%d: expected test feature Enabled(%s)=%v", i, f, v)
   307  				}
   308  			}
   309  		})
   310  	}
   311  }
   312  
   313  func TestVersionMapping(t *testing.T) {
   314  	r := NewComponentGlobalsRegistry()
   315  	ver1 := NewEffectiveVersion("0.58")
   316  	ver2 := NewEffectiveVersion("1.28")
   317  	ver3 := NewEffectiveVersion("2.10")
   318  
   319  	utilruntime.Must(r.Register("test1", ver1, nil))
   320  	utilruntime.Must(r.Register("test2", ver2, nil))
   321  	utilruntime.Must(r.Register("test3", ver3, nil))
   322  
   323  	assertVersionEqualTo(t, r.EffectiveVersionFor("test1").EmulationVersion(), "0.58")
   324  	assertVersionEqualTo(t, r.EffectiveVersionFor("test2").EmulationVersion(), "1.28")
   325  	assertVersionEqualTo(t, r.EffectiveVersionFor("test3").EmulationVersion(), "2.10")
   326  
   327  	utilruntime.Must(r.SetEmulationVersionMapping("test2", "test3",
   328  		func(from *version.Version) *version.Version {
   329  			return version.MajorMinor(from.Major()+1, from.Minor()-19)
   330  		}))
   331  	utilruntime.Must(r.SetEmulationVersionMapping("test1", "test2",
   332  		func(from *version.Version) *version.Version {
   333  			return version.MajorMinor(from.Major()+1, from.Minor()-28)
   334  		}))
   335  	assertVersionEqualTo(t, r.EffectiveVersionFor("test1").EmulationVersion(), "0.58")
   336  	assertVersionEqualTo(t, r.EffectiveVersionFor("test2").EmulationVersion(), "1.30")
   337  	assertVersionEqualTo(t, r.EffectiveVersionFor("test3").EmulationVersion(), "2.11")
   338  
   339  	fs := pflag.NewFlagSet("testflag", pflag.ContinueOnError)
   340  	r.AddFlags(fs)
   341  
   342  	if err := fs.Parse([]string{fmt.Sprintf("--emulated-version=%s", "test1=0.56")}); err != nil {
   343  		t.Fatal(err)
   344  		return
   345  	}
   346  	if err := r.Set(); err != nil {
   347  		t.Fatal(err)
   348  		return
   349  	}
   350  	assertVersionEqualTo(t, r.EffectiveVersionFor("test1").EmulationVersion(), "0.56")
   351  	assertVersionEqualTo(t, r.EffectiveVersionFor("test2").EmulationVersion(), "1.28")
   352  	assertVersionEqualTo(t, r.EffectiveVersionFor("test3").EmulationVersion(), "2.09")
   353  }
   354  
   355  func TestVersionMappingWithMultipleDependency(t *testing.T) {
   356  	r := NewComponentGlobalsRegistry()
   357  	ver1 := NewEffectiveVersion("0.58")
   358  	ver2 := NewEffectiveVersion("1.28")
   359  	ver3 := NewEffectiveVersion("2.10")
   360  
   361  	utilruntime.Must(r.Register("test1", ver1, nil))
   362  	utilruntime.Must(r.Register("test2", ver2, nil))
   363  	utilruntime.Must(r.Register("test3", ver3, nil))
   364  
   365  	assertVersionEqualTo(t, r.EffectiveVersionFor("test1").EmulationVersion(), "0.58")
   366  	assertVersionEqualTo(t, r.EffectiveVersionFor("test2").EmulationVersion(), "1.28")
   367  	assertVersionEqualTo(t, r.EffectiveVersionFor("test3").EmulationVersion(), "2.10")
   368  
   369  	utilruntime.Must(r.SetEmulationVersionMapping("test1", "test2",
   370  		func(from *version.Version) *version.Version {
   371  			return version.MajorMinor(from.Major()+1, from.Minor()-28)
   372  		}))
   373  	err := r.SetEmulationVersionMapping("test3", "test2",
   374  		func(from *version.Version) *version.Version {
   375  			return version.MajorMinor(from.Major()-1, from.Minor()+19)
   376  		})
   377  	if err == nil {
   378  		t.Errorf("expect error when setting 2nd mapping to test2")
   379  	}
   380  }
   381  
   382  func TestVersionMappingWithCyclicDependency(t *testing.T) {
   383  	r := NewComponentGlobalsRegistry()
   384  	ver1 := NewEffectiveVersion("0.58")
   385  	ver2 := NewEffectiveVersion("1.28")
   386  	ver3 := NewEffectiveVersion("2.10")
   387  
   388  	utilruntime.Must(r.Register("test1", ver1, nil))
   389  	utilruntime.Must(r.Register("test2", ver2, nil))
   390  	utilruntime.Must(r.Register("test3", ver3, nil))
   391  
   392  	assertVersionEqualTo(t, r.EffectiveVersionFor("test1").EmulationVersion(), "0.58")
   393  	assertVersionEqualTo(t, r.EffectiveVersionFor("test2").EmulationVersion(), "1.28")
   394  	assertVersionEqualTo(t, r.EffectiveVersionFor("test3").EmulationVersion(), "2.10")
   395  
   396  	utilruntime.Must(r.SetEmulationVersionMapping("test1", "test2",
   397  		func(from *version.Version) *version.Version {
   398  			return version.MajorMinor(from.Major()+1, from.Minor()-28)
   399  		}))
   400  	utilruntime.Must(r.SetEmulationVersionMapping("test2", "test3",
   401  		func(from *version.Version) *version.Version {
   402  			return version.MajorMinor(from.Major()+1, from.Minor()-19)
   403  		}))
   404  	err := r.SetEmulationVersionMapping("test3", "test1",
   405  		func(from *version.Version) *version.Version {
   406  			return version.MajorMinor(from.Major()-2, from.Minor()+48)
   407  		})
   408  	if err == nil {
   409  		t.Errorf("expect cyclic version mapping error")
   410  	}
   411  }
   412  
   413  func assertVersionEqualTo(t *testing.T, ver *version.Version, expectedVer string) {
   414  	if ver.EqualTo(version.MustParse(expectedVer)) {
   415  		return
   416  	}
   417  	t.Errorf("expected: %s, got %s", expectedVer, ver.String())
   418  }