...

Source file src/k8s.io/kubernetes/cmd/kubeadm/app/util/config/common_test.go

Documentation: k8s.io/kubernetes/cmd/kubeadm/app/util/config

     1  /*
     2  Copyright 2018 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 config
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/google/go-cmp/cmp"
    26  	"github.com/lithammer/dedent"
    27  
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/runtime/schema"
    30  	"k8s.io/apimachinery/pkg/util/version"
    31  	apimachineryversion "k8s.io/apimachinery/pkg/version"
    32  
    33  	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
    34  	kubeadmapiv1old "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
    35  	kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta4"
    36  	"k8s.io/kubernetes/cmd/kubeadm/app/constants"
    37  	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
    38  )
    39  
    40  const KubeadmGroupName = "kubeadm.k8s.io"
    41  
    42  func TestValidateSupportedVersion(t *testing.T) {
    43  	tests := []struct {
    44  		gv                schema.GroupVersion
    45  		allowDeprecated   bool
    46  		allowExperimental bool
    47  		expectedErr       bool
    48  	}{
    49  		{
    50  			gv: schema.GroupVersion{
    51  				Group:   KubeadmGroupName,
    52  				Version: "v1alpha1",
    53  			},
    54  			expectedErr: true,
    55  		},
    56  		{
    57  			gv: schema.GroupVersion{
    58  				Group:   KubeadmGroupName,
    59  				Version: "v1alpha2",
    60  			},
    61  			expectedErr: true,
    62  		},
    63  		{
    64  			gv: schema.GroupVersion{
    65  				Group:   KubeadmGroupName,
    66  				Version: "v1alpha3",
    67  			},
    68  			expectedErr: true,
    69  		},
    70  		{
    71  			gv: schema.GroupVersion{
    72  				Group:   KubeadmGroupName,
    73  				Version: "v1beta1",
    74  			},
    75  			expectedErr: true,
    76  		},
    77  		{
    78  			gv: schema.GroupVersion{
    79  				Group:   KubeadmGroupName,
    80  				Version: "v1beta2",
    81  			},
    82  			expectedErr: true,
    83  		},
    84  		{
    85  			gv: schema.GroupVersion{
    86  				Group:   KubeadmGroupName,
    87  				Version: "v1beta3",
    88  			},
    89  		},
    90  		{
    91  			gv: schema.GroupVersion{
    92  				Group:   "foo.k8s.io",
    93  				Version: "v1",
    94  			},
    95  		},
    96  		{
    97  			gv: schema.GroupVersion{
    98  				Group:   KubeadmGroupName,
    99  				Version: "v1beta4",
   100  			},
   101  			allowExperimental: true,
   102  		},
   103  		{
   104  			gv: schema.GroupVersion{
   105  				Group:   KubeadmGroupName,
   106  				Version: "v1beta4",
   107  			},
   108  			expectedErr: true,
   109  		},
   110  	}
   111  
   112  	for _, rt := range tests {
   113  		t.Run(fmt.Sprintf("%s/allowDeprecated:%t", rt.gv, rt.allowDeprecated), func(t *testing.T) {
   114  			err := validateSupportedVersion(rt.gv, rt.allowDeprecated, rt.allowExperimental)
   115  			if rt.expectedErr && err == nil {
   116  				t.Error("unexpected success")
   117  			} else if !rt.expectedErr && err != nil {
   118  				t.Errorf("unexpected failure: %v", err)
   119  			}
   120  		})
   121  	}
   122  }
   123  
   124  func TestLowercaseSANs(t *testing.T) {
   125  	tests := []struct {
   126  		name string
   127  		in   []string
   128  		out  []string
   129  	}{
   130  		{
   131  			name: "empty struct",
   132  		},
   133  		{
   134  			name: "already lowercase",
   135  			in:   []string{"example.k8s.io"},
   136  			out:  []string{"example.k8s.io"},
   137  		},
   138  		{
   139  			name: "ip addresses and uppercase",
   140  			in:   []string{"EXAMPLE.k8s.io", "10.100.0.1"},
   141  			out:  []string{"example.k8s.io", "10.100.0.1"},
   142  		},
   143  		{
   144  			name: "punycode and uppercase",
   145  			in:   []string{"xn--7gq663byk9a.xn--fiqz9s", "ANOTHEREXAMPLE.k8s.io"},
   146  			out:  []string{"xn--7gq663byk9a.xn--fiqz9s", "anotherexample.k8s.io"},
   147  		},
   148  	}
   149  
   150  	for _, test := range tests {
   151  		t.Run(test.name, func(t *testing.T) {
   152  			cfg := &kubeadmapiv1.ClusterConfiguration{
   153  				APIServer: kubeadmapiv1.APIServer{
   154  					CertSANs: test.in,
   155  				},
   156  			}
   157  
   158  			LowercaseSANs(cfg.APIServer.CertSANs)
   159  
   160  			if len(cfg.APIServer.CertSANs) != len(test.out) {
   161  				t.Fatalf("expected %d elements, got %d", len(test.out), len(cfg.APIServer.CertSANs))
   162  			}
   163  
   164  			for i, expected := range test.out {
   165  				if cfg.APIServer.CertSANs[i] != expected {
   166  					t.Errorf("expected element %d to be %q, got %q", i, expected, cfg.APIServer.CertSANs[i])
   167  				}
   168  			}
   169  		})
   170  	}
   171  }
   172  
   173  func TestVerifyAPIServerBindAddress(t *testing.T) {
   174  	tests := []struct {
   175  		name          string
   176  		address       string
   177  		expectedError bool
   178  	}{
   179  		{
   180  			name:    "valid address: IPV4",
   181  			address: "192.168.0.1",
   182  		},
   183  		{
   184  			name:    "valid address: IPV6",
   185  			address: "2001:db8:85a3::8a2e:370:7334",
   186  		},
   187  		{
   188  			name:          "valid address 127.0.0.1",
   189  			address:       "127.0.0.1",
   190  			expectedError: false,
   191  		},
   192  		{
   193  			name:          "invalid address: not a global unicast 0.0.0.0",
   194  			address:       "0.0.0.0",
   195  			expectedError: true,
   196  		},
   197  		{
   198  			name:          "invalid address: not a global unicast ::",
   199  			address:       "::",
   200  			expectedError: true,
   201  		},
   202  		{
   203  			name:          "invalid address: cannot parse IPV4",
   204  			address:       "255.255.255.255.255",
   205  			expectedError: true,
   206  		},
   207  		{
   208  			name:          "invalid address: cannot parse IPV6",
   209  			address:       "2a00:800::2a00:800:10102a00",
   210  			expectedError: true,
   211  		},
   212  	}
   213  
   214  	for _, test := range tests {
   215  		t.Run(test.name, func(t *testing.T) {
   216  			if err := VerifyAPIServerBindAddress(test.address); (err != nil) != test.expectedError {
   217  				t.Errorf("expected error: %v, got %v, error: %v", test.expectedError, (err != nil), err)
   218  			}
   219  		})
   220  	}
   221  }
   222  
   223  // NOTE: do not delete this test once an older API is removed and there is only one API left.
   224  // Update the inline "gv" and "gvExperimental" variables, to have the GroupVersion String of
   225  // the API to be tested. If there are no experimental APIs make "gvExperimental" point to
   226  // an non-experimental API.
   227  func TestMigrateOldConfig(t *testing.T) {
   228  	var (
   229  		gv             = kubeadmapiv1old.SchemeGroupVersion.String()
   230  		gvExperimental = kubeadmapiv1.SchemeGroupVersion.String()
   231  	)
   232  	tests := []struct {
   233  		name              string
   234  		oldCfg            string
   235  		expectedKinds     []string
   236  		expectErr         bool
   237  		allowExperimental bool
   238  	}{
   239  		{
   240  			name:      "empty file produces empty result",
   241  			oldCfg:    "",
   242  			expectErr: false,
   243  		},
   244  		{
   245  			name: "bad config produces error",
   246  			oldCfg: dedent.Dedent(fmt.Sprintf(`
   247  			apiVersion: %s
   248  			`, gv)),
   249  			expectErr: true,
   250  		},
   251  		{
   252  			name: "unknown API produces error",
   253  			oldCfg: dedent.Dedent(fmt.Sprintf(`
   254  			apiVersion: %s
   255  			kind: Foo
   256  			`, gv)),
   257  			expectErr: true,
   258  		},
   259  		{
   260  			name: "InitConfiguration only gets migrated",
   261  			oldCfg: dedent.Dedent(fmt.Sprintf(`
   262  			apiVersion: %s
   263  			kind: InitConfiguration
   264  			`, gv)),
   265  			expectedKinds: []string{
   266  				constants.InitConfigurationKind,
   267  				constants.ClusterConfigurationKind,
   268  			},
   269  			expectErr: false,
   270  		},
   271  		{
   272  			name: "ClusterConfiguration only gets migrated",
   273  			oldCfg: dedent.Dedent(fmt.Sprintf(`
   274  			apiVersion: %s
   275  			kind: ClusterConfiguration
   276  			`, gv)),
   277  			expectedKinds: []string{
   278  				constants.InitConfigurationKind,
   279  				constants.ClusterConfigurationKind,
   280  			},
   281  			expectErr: false,
   282  		},
   283  		{
   284  			name: "JoinConfiguration only gets migrated",
   285  			oldCfg: dedent.Dedent(fmt.Sprintf(`
   286  			apiVersion: %s
   287  			kind: JoinConfiguration
   288  			discovery:
   289  			  bootstrapToken:
   290  			    token: abcdef.0123456789abcdef
   291  			    apiServerEndpoint: kube-apiserver:6443
   292  			    unsafeSkipCAVerification: true
   293  			`, gv)),
   294  			expectedKinds: []string{
   295  				constants.JoinConfigurationKind,
   296  			},
   297  			expectErr: false,
   298  		},
   299  		{
   300  			name: "Init + Cluster Configurations are migrated",
   301  			oldCfg: dedent.Dedent(fmt.Sprintf(`
   302  			apiVersion: %s
   303  			kind: InitConfiguration
   304  			---
   305  			apiVersion: %[1]s
   306  			kind: ClusterConfiguration
   307  			`, gv)),
   308  			expectedKinds: []string{
   309  				constants.InitConfigurationKind,
   310  				constants.ClusterConfigurationKind,
   311  			},
   312  			expectErr: false,
   313  		},
   314  		{
   315  			name: "Init + Join Configurations are migrated",
   316  			oldCfg: dedent.Dedent(fmt.Sprintf(`
   317  			apiVersion: %s
   318  			kind: InitConfiguration
   319  			---
   320  			apiVersion: %[1]s
   321  			kind: JoinConfiguration
   322  			discovery:
   323  			  bootstrapToken:
   324  			    token: abcdef.0123456789abcdef
   325  			    apiServerEndpoint: kube-apiserver:6443
   326  			    unsafeSkipCAVerification: true
   327  			`, gv)),
   328  			expectedKinds: []string{
   329  				constants.InitConfigurationKind,
   330  				constants.ClusterConfigurationKind,
   331  				constants.JoinConfigurationKind,
   332  			},
   333  			expectErr: false,
   334  		},
   335  		{
   336  			name: "Cluster + Join Configurations are migrated",
   337  			oldCfg: dedent.Dedent(fmt.Sprintf(`
   338  			apiVersion: %s
   339  			kind: ClusterConfiguration
   340  			---
   341  			apiVersion: %[1]s
   342  			kind: JoinConfiguration
   343  			discovery:
   344  			  bootstrapToken:
   345  			    token: abcdef.0123456789abcdef
   346  			    apiServerEndpoint: kube-apiserver:6443
   347  			    unsafeSkipCAVerification: true
   348  			`, gv)),
   349  			expectedKinds: []string{
   350  				constants.InitConfigurationKind,
   351  				constants.ClusterConfigurationKind,
   352  				constants.JoinConfigurationKind,
   353  			},
   354  			expectErr: false,
   355  		},
   356  		{
   357  			name: "Init + Cluster + Join Configurations are migrated",
   358  			oldCfg: dedent.Dedent(fmt.Sprintf(`
   359  			apiVersion: %s
   360  			kind: InitConfiguration
   361  			---
   362  			apiVersion: %[1]s
   363  			kind: ClusterConfiguration
   364  			---
   365  			apiVersion: %[1]s
   366  			kind: JoinConfiguration
   367  			discovery:
   368  			  bootstrapToken:
   369  			    token: abcdef.0123456789abcdef
   370  			    apiServerEndpoint: kube-apiserver:6443
   371  			    unsafeSkipCAVerification: true
   372  			`, gv)),
   373  			expectedKinds: []string{
   374  				constants.InitConfigurationKind,
   375  				constants.ClusterConfigurationKind,
   376  				constants.JoinConfigurationKind,
   377  			},
   378  			expectErr: false,
   379  		},
   380  		{
   381  			name: "component configs are not migrated",
   382  			oldCfg: dedent.Dedent(fmt.Sprintf(`
   383  			apiVersion: %s
   384  			kind: InitConfiguration
   385  			---
   386  			apiVersion: %[1]s
   387  			kind: ClusterConfiguration
   388  			---
   389  			apiVersion: %[1]s
   390  			kind: JoinConfiguration
   391  			discovery:
   392  			  bootstrapToken:
   393  			    token: abcdef.0123456789abcdef
   394  			    apiServerEndpoint: kube-apiserver:6443
   395  			    unsafeSkipCAVerification: true
   396  			---
   397  			apiVersion: kubeproxy.config.k8s.io/v1alpha1
   398  			kind: KubeProxyConfiguration
   399  			---
   400  			apiVersion: kubelet.config.k8s.io/v1beta1
   401  			kind: KubeletConfiguration
   402  			`, gv)),
   403  			expectedKinds: []string{
   404  				constants.InitConfigurationKind,
   405  				constants.ClusterConfigurationKind,
   406  				constants.JoinConfigurationKind,
   407  			},
   408  			expectErr: false,
   409  		},
   410  		{
   411  			name: "ClusterConfiguration gets migrated from experimental API",
   412  			oldCfg: dedent.Dedent(fmt.Sprintf(`
   413  			apiVersion: %s
   414  			kind: ClusterConfiguration
   415  			`, gvExperimental)),
   416  			expectedKinds: []string{
   417  				constants.InitConfigurationKind,
   418  				constants.ClusterConfigurationKind,
   419  			},
   420  			allowExperimental: true,
   421  			expectErr:         false,
   422  		},
   423  		{
   424  			name: "ClusterConfiguration from experimental API cannot be migrated",
   425  			oldCfg: dedent.Dedent(fmt.Sprintf(`
   426  			apiVersion: %s
   427  			kind: ClusterConfiguration
   428  			`, gvExperimental)),
   429  			allowExperimental: false,
   430  			expectErr:         true,
   431  		},
   432  		{
   433  			name: "ResetConfiguration gets migrated from experimental API",
   434  			oldCfg: dedent.Dedent(fmt.Sprintf(`
   435  			apiVersion: %s
   436  			kind: ResetConfiguration
   437  			force: true
   438  			cleanupTmpDir: true
   439  			criSocket: unix:///var/run/containerd/containerd.sock
   440  			certificatesDir: /etc/kubernetes/pki
   441  			`, gvExperimental)),
   442  			expectedKinds: []string{
   443  				constants.ResetConfigurationKind,
   444  			},
   445  			allowExperimental: true,
   446  			expectErr:         false,
   447  		},
   448  		{
   449  			name: "ResetConfiguration from experimental API cannot be migrated",
   450  			oldCfg: dedent.Dedent(fmt.Sprintf(`
   451  			apiVersion: %s
   452  			kind: ResetConfiguration
   453  			`, gvExperimental)),
   454  			allowExperimental: false,
   455  			expectErr:         true,
   456  		},
   457  	}
   458  
   459  	for _, test := range tests {
   460  		t.Run(test.name, func(t *testing.T) {
   461  			b, err := MigrateOldConfig([]byte(test.oldCfg), test.allowExperimental, defaultEmptyMigrateMutators())
   462  			if test.expectErr {
   463  				if err == nil {
   464  					t.Fatalf("unexpected success:\n%s", b)
   465  				}
   466  			} else {
   467  				if err != nil {
   468  					t.Fatalf("unexpected failure: %v", err)
   469  				}
   470  				gvks, err := kubeadmutil.GroupVersionKindsFromBytes(b)
   471  				if err != nil {
   472  					t.Fatalf("unexpected error returned by GroupVersionKindsFromBytes: %v", err)
   473  				}
   474  				if len(gvks) != len(test.expectedKinds) {
   475  					t.Fatalf("length mismatch between resulting gvks and expected kinds:\n\tlen(gvks)=%d\n\tlen(expectedKinds)=%d",
   476  						len(gvks), len(test.expectedKinds))
   477  				}
   478  				for _, expectedKind := range test.expectedKinds {
   479  					if !kubeadmutil.GroupVersionKindsHasKind(gvks, expectedKind) {
   480  						t.Fatalf("migration failed to produce config kind: %s", expectedKind)
   481  					}
   482  				}
   483  				expectedGV := gv
   484  				if test.allowExperimental {
   485  					expectedGV = gvExperimental
   486  				}
   487  				for _, gvk := range gvks {
   488  					if gvk.GroupVersion().String() != expectedGV {
   489  						t.Errorf("GV mismatch, expected GV: %s, got GV: %s", expectedGV, gvk.GroupVersion().String())
   490  					}
   491  				}
   492  			}
   493  		})
   494  	}
   495  }
   496  
   497  // NOTE: do not delete this test once an older API is removed and there is only one API left.
   498  // Update the inline "gv" and "gvExperimental" variables, to have the GroupVersion String of
   499  // the API to be tested. If there are no experimental APIs make "gvExperimental" point to
   500  // an non-experimental API.
   501  func TestValidateConfig(t *testing.T) {
   502  	var (
   503  		gv             = kubeadmapiv1old.SchemeGroupVersion.String()
   504  		gvExperimental = kubeadmapiv1.SchemeGroupVersion.String()
   505  	)
   506  	tests := []struct {
   507  		name              string
   508  		cfg               string
   509  		expectedError     bool
   510  		allowExperimental bool
   511  	}{
   512  		{
   513  			name: "invalid subdomain",
   514  			cfg: dedent.Dedent(fmt.Sprintf(`
   515  			apiVersion: %s
   516  			kind: InitConfiguration
   517  			  name: foo bar # not a valid subdomain
   518  			`, gv)),
   519  			expectedError: true,
   520  		},
   521  		{
   522  			name: "unknown API GVK",
   523  			cfg: dedent.Dedent(`
   524  			apiVersion: foo/bar # not a valid GroupVersion
   525  			kind: zzz # not a valid Kind
   526  			`),
   527  			expectedError: true,
   528  		},
   529  		{
   530  			name: "legacy API GVK",
   531  			cfg: dedent.Dedent(`
   532  			apiVersion: kubeadm.k8s.io/v1beta1 # legacy API
   533  			kind: InitConfiguration
   534  			`),
   535  			expectedError: true,
   536  		},
   537  		{
   538  			name: "unknown field",
   539  			cfg: dedent.Dedent(fmt.Sprintf(`
   540  			apiVersion: %s
   541  			kind: InitConfiguration
   542  			foo: bar
   543  			`, gv)),
   544  			expectedError: true,
   545  		},
   546  		{
   547  			name: "valid",
   548  			cfg: dedent.Dedent(fmt.Sprintf(`
   549  			apiVersion: %s
   550  			kind: InitConfiguration
   551  			`, gv)),
   552  			expectedError: false,
   553  		},
   554  		{
   555  			name: "valid: experimental API",
   556  			cfg: dedent.Dedent(fmt.Sprintf(`
   557  			apiVersion: %s
   558  			kind: InitConfiguration
   559  			`, gvExperimental)),
   560  			expectedError:     false,
   561  			allowExperimental: true,
   562  		},
   563  		{
   564  			name: "invalid: experimental API",
   565  			cfg: dedent.Dedent(fmt.Sprintf(`
   566  			apiVersion: %s
   567  			kind: InitConfiguration
   568  			`, gvExperimental)),
   569  			expectedError:     true,
   570  			allowExperimental: false,
   571  		},
   572  		{
   573  			name: "valid ResetConfiguration",
   574  			cfg: dedent.Dedent(fmt.Sprintf(`
   575  			apiVersion: %s
   576  			kind: ResetConfiguration
   577  			force: true
   578  			`, gvExperimental)),
   579  			expectedError:     false,
   580  			allowExperimental: true,
   581  		},
   582  		{
   583  			name: "invalid field in ResetConfiguration",
   584  			cfg: dedent.Dedent(fmt.Sprintf(`
   585  			apiVersion: %s
   586  			kind: ResetConfiguration
   587  			foo: bar
   588  			`, gvExperimental)),
   589  			expectedError:     true,
   590  			allowExperimental: true,
   591  		},
   592  		{
   593  			name: "experimental API is not allowed in ResetConfiguration",
   594  			cfg: dedent.Dedent(fmt.Sprintf(`
   595  			apiVersion: %s
   596  			kind: ResetConfiguration
   597  			`, gvExperimental)),
   598  			expectedError:     true,
   599  			allowExperimental: false,
   600  		},
   601  	}
   602  
   603  	for _, test := range tests {
   604  		t.Run(test.name, func(t *testing.T) {
   605  			err := ValidateConfig([]byte(test.cfg), test.allowExperimental)
   606  			if (err != nil) != test.expectedError {
   607  				t.Fatalf("expected error: %v, got: %v, error: %v", test.expectedError, (err != nil), err)
   608  			}
   609  		})
   610  	}
   611  }
   612  
   613  func TestIsKubeadmPrereleaseVersion(t *testing.T) {
   614  	validVersionInfo := &apimachineryversion.Info{Major: "1", GitVersion: "v1.23.0-alpha.1"}
   615  	tests := []struct {
   616  		name           string
   617  		versionInfo    *apimachineryversion.Info
   618  		k8sVersion     *version.Version
   619  		mcpVersion     *version.Version
   620  		expectedResult bool
   621  	}{
   622  		{
   623  			name:           "invalid versionInfo",
   624  			versionInfo:    &apimachineryversion.Info{},
   625  			expectedResult: false,
   626  		},
   627  		{
   628  			name:           "kubeadm is not a prerelease version",
   629  			versionInfo:    &apimachineryversion.Info{Major: "1", GitVersion: "v1.23.0"},
   630  			expectedResult: false,
   631  		},
   632  		{
   633  			name:           "mcpVersion is equal to k8sVersion",
   634  			versionInfo:    validVersionInfo,
   635  			k8sVersion:     version.MustParseSemantic("v1.21.0"),
   636  			mcpVersion:     version.MustParseSemantic("v1.21.0"),
   637  			expectedResult: true,
   638  		},
   639  		{
   640  			name:           "k8sVersion is 1 MINOR version older than mcpVersion",
   641  			versionInfo:    validVersionInfo,
   642  			k8sVersion:     version.MustParseSemantic("v1.21.0"),
   643  			mcpVersion:     version.MustParseSemantic("v1.22.0"),
   644  			expectedResult: true,
   645  		},
   646  		{
   647  			name:           "k8sVersion is 2 MINOR versions older than mcpVersion",
   648  			versionInfo:    validVersionInfo,
   649  			k8sVersion:     version.MustParseSemantic("v1.21.0"),
   650  			mcpVersion:     version.MustParseSemantic("v1.23.0"),
   651  			expectedResult: false,
   652  		},
   653  	}
   654  
   655  	for _, tc := range tests {
   656  		t.Run(tc.name, func(t *testing.T) {
   657  			result := isKubeadmPrereleaseVersion(tc.versionInfo, tc.k8sVersion, tc.mcpVersion)
   658  			if result != tc.expectedResult {
   659  				t.Errorf("expected result: %v, got %v", tc.expectedResult, result)
   660  			}
   661  		})
   662  	}
   663  }
   664  
   665  func TestNormalizeKubernetesVersion(t *testing.T) {
   666  	validVersion := fmt.Sprintf("v%v", constants.MinimumControlPlaneVersion)
   667  	validCIVersion := fmt.Sprintf("%s%s", constants.CIKubernetesVersionPrefix, validVersion)
   668  	tests := []struct {
   669  		name        string
   670  		cfg         *kubeadmapi.ClusterConfiguration
   671  		expectedCfg *kubeadmapi.ClusterConfiguration
   672  		expectErr   bool
   673  	}{
   674  		{
   675  			name: "normal version, default image repository",
   676  			cfg: &kubeadmapi.ClusterConfiguration{
   677  				KubernetesVersion: validVersion,
   678  				ImageRepository:   kubeadmapiv1.DefaultImageRepository,
   679  			},
   680  			expectedCfg: &kubeadmapi.ClusterConfiguration{
   681  				KubernetesVersion:   validVersion,
   682  				CIKubernetesVersion: "",
   683  				ImageRepository:     kubeadmapiv1.DefaultImageRepository,
   684  				CIImageRepository:   "",
   685  			},
   686  			expectErr: false,
   687  		},
   688  		{
   689  			name: "normal version, custom image repository",
   690  			cfg: &kubeadmapi.ClusterConfiguration{
   691  				KubernetesVersion: validVersion,
   692  				ImageRepository:   "custom.repository",
   693  			},
   694  			expectedCfg: &kubeadmapi.ClusterConfiguration{
   695  				KubernetesVersion:   validVersion,
   696  				CIKubernetesVersion: "",
   697  				ImageRepository:     "custom.repository",
   698  				CIImageRepository:   "",
   699  			},
   700  			expectErr: false,
   701  		},
   702  		{
   703  			name: "ci version, default image repository",
   704  			cfg: &kubeadmapi.ClusterConfiguration{
   705  				KubernetesVersion: validCIVersion,
   706  				ImageRepository:   kubeadmapiv1.DefaultImageRepository,
   707  			},
   708  			expectedCfg: &kubeadmapi.ClusterConfiguration{
   709  				KubernetesVersion:   validVersion,
   710  				CIKubernetesVersion: validCIVersion,
   711  				ImageRepository:     kubeadmapiv1.DefaultImageRepository,
   712  				CIImageRepository:   constants.DefaultCIImageRepository,
   713  			},
   714  			expectErr: false,
   715  		},
   716  		{
   717  			name: "ci version, custom image repository",
   718  			cfg: &kubeadmapi.ClusterConfiguration{
   719  				KubernetesVersion: validCIVersion,
   720  				ImageRepository:   "custom.repository",
   721  			},
   722  			expectedCfg: &kubeadmapi.ClusterConfiguration{
   723  				KubernetesVersion:   validVersion,
   724  				CIKubernetesVersion: validCIVersion,
   725  				ImageRepository:     "custom.repository",
   726  				CIImageRepository:   "",
   727  			},
   728  			expectErr: false,
   729  		},
   730  		{
   731  			name: "unsupported old version",
   732  			cfg: &kubeadmapi.ClusterConfiguration{
   733  				KubernetesVersion: "v0.0.0",
   734  				ImageRepository:   kubeadmapiv1.DefaultImageRepository,
   735  			},
   736  			expectedCfg: &kubeadmapi.ClusterConfiguration{
   737  				KubernetesVersion:   "v0.0.0",
   738  				CIKubernetesVersion: "",
   739  				ImageRepository:     kubeadmapiv1.DefaultImageRepository,
   740  				CIImageRepository:   "",
   741  			},
   742  			expectErr: true,
   743  		},
   744  	}
   745  
   746  	for _, tc := range tests {
   747  		t.Run(tc.name, func(t *testing.T) {
   748  			err := NormalizeKubernetesVersion(tc.cfg)
   749  			if !reflect.DeepEqual(tc.cfg, tc.expectedCfg) {
   750  				t.Errorf("expected ClusterConfiguration: %#v, got %#v", tc.expectedCfg, tc.cfg)
   751  			}
   752  			if !tc.expectErr && err != nil {
   753  				t.Errorf("unexpected failure: %v", err)
   754  			}
   755  		})
   756  	}
   757  }
   758  
   759  // TODO: update the test cases for this test once v1beta3 is removed.
   760  func TestDefaultMigrateMutators(t *testing.T) {
   761  	tests := []struct {
   762  		name          string
   763  		mutators      migrateMutators
   764  		input         []any
   765  		expected      []any
   766  		expectedDiff  bool
   767  		expectedError bool
   768  	}{
   769  		{
   770  			name:     "mutate InitConfiguration",
   771  			mutators: defaultMigrateMutators(),
   772  			input: []any{&kubeadmapi.InitConfiguration{
   773  				ClusterConfiguration: kubeadmapi.ClusterConfiguration{
   774  					APIServer: kubeadmapi.APIServer{
   775  						TimeoutForControlPlane: &metav1.Duration{
   776  							Duration: 1234 * time.Millisecond,
   777  						},
   778  					},
   779  				},
   780  				Timeouts: &kubeadmapi.Timeouts{
   781  					ControlPlaneComponentHealthCheck: &metav1.Duration{},
   782  				},
   783  			}},
   784  			expected: []any{&kubeadmapi.InitConfiguration{
   785  				Timeouts: &kubeadmapi.Timeouts{
   786  					ControlPlaneComponentHealthCheck: &metav1.Duration{
   787  						Duration: 1234 * time.Millisecond,
   788  					},
   789  				},
   790  			}},
   791  		},
   792  		{
   793  			name:     "mutate JoinConfiguration",
   794  			mutators: defaultMigrateMutators(),
   795  			input: []any{&kubeadmapi.JoinConfiguration{
   796  				Discovery: kubeadmapi.Discovery{
   797  					Timeout: &metav1.Duration{
   798  						Duration: 1234 * time.Microsecond,
   799  					},
   800  				},
   801  				Timeouts: &kubeadmapi.Timeouts{
   802  					Discovery: &metav1.Duration{},
   803  				},
   804  			}},
   805  			expected: []any{&kubeadmapi.JoinConfiguration{
   806  				Timeouts: &kubeadmapi.Timeouts{
   807  					Discovery: &metav1.Duration{
   808  						Duration: 1234 * time.Microsecond,
   809  					},
   810  				},
   811  			}},
   812  		},
   813  		{
   814  			name:     "diff when mutating InitConfiguration",
   815  			mutators: defaultMigrateMutators(),
   816  			input: []any{&kubeadmapi.InitConfiguration{
   817  				ClusterConfiguration: kubeadmapi.ClusterConfiguration{
   818  					APIServer: kubeadmapi.APIServer{
   819  						TimeoutForControlPlane: &metav1.Duration{
   820  							Duration: 1234 * time.Millisecond,
   821  						},
   822  					},
   823  				},
   824  				Timeouts: &kubeadmapi.Timeouts{
   825  					ControlPlaneComponentHealthCheck: &metav1.Duration{},
   826  				},
   827  			}},
   828  			expected: []any{&kubeadmapi.InitConfiguration{
   829  				Timeouts: &kubeadmapi.Timeouts{
   830  					ControlPlaneComponentHealthCheck: &metav1.Duration{
   831  						Duration: 1 * time.Millisecond, // a different value
   832  					},
   833  				},
   834  			}},
   835  			expectedDiff: true,
   836  		},
   837  		{
   838  			name:          "expect an error for a missing mutator",
   839  			mutators:      migrateMutators{}, // empty list of mutators
   840  			input:         []any{&kubeadmapi.ResetConfiguration{}},
   841  			expectedError: true,
   842  		},
   843  	}
   844  
   845  	for _, tc := range tests {
   846  		t.Run(tc.name, func(t *testing.T) {
   847  			err := tc.mutators.mutate(tc.input)
   848  			if (err != nil) != tc.expectedError {
   849  				t.Fatalf("expected error: %v, got: %v, error: %v", tc.expectedError, (err != nil), err)
   850  			}
   851  			if err != nil {
   852  				return
   853  			}
   854  			diff := cmp.Diff(tc.expected, tc.input)
   855  			if (len(diff) > 0) != tc.expectedDiff {
   856  				t.Fatalf("got a diff with the expected config (-want,+got):\n%s", diff)
   857  			}
   858  		})
   859  	}
   860  }
   861  
   862  func TestIsKubeadmConfigPresent(t *testing.T) {
   863  	var tcases = []struct {
   864  		name     string
   865  		gvkmap   kubeadmapi.DocumentMap
   866  		expected bool
   867  	}{
   868  		{
   869  			name: " Wrong Group value",
   870  			gvkmap: kubeadmapi.DocumentMap{
   871  				{Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}: []byte(`kind: Foo`),
   872  			},
   873  			expected: false,
   874  		},
   875  		{
   876  			name: "Empty Group value",
   877  			gvkmap: kubeadmapi.DocumentMap{
   878  				{Group: "", Version: "v1", Kind: "Empty"}: []byte(`kind: Empty`),
   879  			},
   880  			expected: false,
   881  		},
   882  		{
   883  			name:     "Nil value",
   884  			gvkmap:   nil,
   885  			expected: false,
   886  		},
   887  		{
   888  			name: "Correct Group value 1",
   889  			gvkmap: kubeadmapi.DocumentMap{
   890  				{Group: "kubeadm.k8s.io", Version: "v1", Kind: "Empty"}: []byte(`kind: Empty`),
   891  			},
   892  			expected: true,
   893  		},
   894  		{
   895  			name: "Correct Group value 2",
   896  			gvkmap: kubeadmapi.DocumentMap{
   897  				{Group: kubeadmapi.GroupName, Version: "v1", Kind: "Empty"}: []byte(`kind: Empty`),
   898  			},
   899  			expected: true,
   900  		},
   901  	}
   902  	for _, tt := range tcases {
   903  		t.Run(tt.name, func(t *testing.T) {
   904  			if isKubeadmConfigPresent(tt.gvkmap) != tt.expected {
   905  				t.Error("unexpected result")
   906  			}
   907  		})
   908  	}
   909  }
   910  

View as plain text