...

Source file src/k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs/fakeconfig_test.go

Documentation: k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs

     1  /*
     2  Copyright 2020 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 componentconfigs
    18  
    19  import (
    20  	"crypto/sha256"
    21  	"fmt"
    22  	"reflect"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/lithammer/dedent"
    28  
    29  	v1 "k8s.io/api/core/v1"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	clientset "k8s.io/client-go/kubernetes"
    33  	clientsetfake "k8s.io/client-go/kubernetes/fake"
    34  
    35  	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
    36  	kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
    37  	kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
    38  	outputapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha3"
    39  	"k8s.io/kubernetes/cmd/kubeadm/app/constants"
    40  	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
    41  )
    42  
    43  // All tests in this file use an alternative set of `known` component configs.
    44  // In this case it's just one known config and it's kubeadm's very own ClusterConfiguration.
    45  // ClusterConfiguration is normally not managed by this package. It's only used, because of the following:
    46  // - It's a versioned API that is under the control of kubeadm maintainers. This enables us to test
    47  //   the componentconfigs package more thoroughly without having to have full and always up to date
    48  //   knowledge about the config of another component.
    49  // - Other components often introduce new fields in their configs without bumping up the config version.
    50  //   This, often times, requires that the PR that introduces such new fields to touch kubeadm test code.
    51  //   Doing so, requires more work on the part of developers and reviewers. When kubeadm moves out of k/k
    52  //   this would allow for more sporadic breaks in kubeadm tests as PRs that merge in k/k and introduce
    53  //   new fields won't be able to fix the tests in kubeadm.
    54  // - If we implement tests for all common functionality using the config of another component and it gets
    55  //   deprecated and/or we stop supporting it in production, we'll have to focus on a massive test refactoring
    56  //   or just continue importing this config just for test use.
    57  //
    58  // Thus, to reduce maintenance costs without sacrificing test coverage, we introduce this mini-framework
    59  // and set of tests here which replace the normal component configs with a single one (ClusterConfiguration)
    60  // and test the component config independent logic of this package.
    61  
    62  // clusterConfigHandler is the handler instance for the latest supported ClusterConfiguration to be used in tests
    63  var clusterConfigHandler = handler{
    64  	GroupVersion: kubeadmapiv1.SchemeGroupVersion,
    65  	AddToScheme:  kubeadmapiv1.AddToScheme,
    66  	CreateEmpty: func() kubeadmapi.ComponentConfig {
    67  		return &clusterConfig{
    68  			configBase: configBase{
    69  				GroupVersion: kubeadmapiv1.SchemeGroupVersion,
    70  			},
    71  		}
    72  	},
    73  	fromCluster: clusterConfigFromCluster,
    74  }
    75  
    76  func clusterConfigFromCluster(h *handler, clientset clientset.Interface, _ *kubeadmapi.ClusterConfiguration) (kubeadmapi.ComponentConfig, error) {
    77  	return h.fromConfigMap(clientset, constants.KubeadmConfigConfigMap, constants.ClusterConfigurationConfigMapKey, true)
    78  }
    79  
    80  type clusterConfig struct {
    81  	configBase
    82  	config kubeadmapiv1.ClusterConfiguration
    83  }
    84  
    85  func (cc *clusterConfig) DeepCopy() kubeadmapi.ComponentConfig {
    86  	result := &clusterConfig{}
    87  	cc.configBase.DeepCopyInto(&result.configBase)
    88  	cc.config.DeepCopyInto(&result.config)
    89  	return result
    90  }
    91  
    92  func (cc *clusterConfig) Marshal() ([]byte, error) {
    93  	return cc.configBase.Marshal(&cc.config)
    94  }
    95  
    96  func (cc *clusterConfig) Unmarshal(docmap kubeadmapi.DocumentMap) error {
    97  	return cc.configBase.Unmarshal(docmap, &cc.config)
    98  }
    99  
   100  func (cc *clusterConfig) Get() interface{} {
   101  	return &cc.config
   102  }
   103  
   104  func (cc *clusterConfig) Set(cfg interface{}) {
   105  	cc.config = *cfg.(*kubeadmapiv1.ClusterConfiguration)
   106  }
   107  
   108  func (cc *clusterConfig) Default(_ *kubeadmapi.ClusterConfiguration, _ *kubeadmapi.APIEndpoint, _ *kubeadmapi.NodeRegistrationOptions) {
   109  	cc.config.ClusterName = "foo"
   110  	cc.config.KubernetesVersion = "bar"
   111  }
   112  
   113  func (cc *clusterConfig) Mutate() error {
   114  	return nil
   115  }
   116  
   117  // fakeKnown replaces temporarily during the execution of each test here known (in configset.go)
   118  var fakeKnown = []*handler{
   119  	&clusterConfigHandler,
   120  }
   121  
   122  // fakeKnownContext is the func that houses the fake component config context.
   123  // NOTE: It does not support concurrent test execution!
   124  func fakeKnownContext(f func()) {
   125  	// Save the real values
   126  	realKnown := known
   127  	realScheme := Scheme
   128  	realCodecs := Codecs
   129  
   130  	// Replace the context with the fake context
   131  	known = fakeKnown
   132  	Scheme = kubeadmscheme.Scheme
   133  	Codecs = kubeadmscheme.Codecs
   134  
   135  	// Upon function exit, restore the real values
   136  	defer func() {
   137  		known = realKnown
   138  		Scheme = realScheme
   139  		Codecs = realCodecs
   140  	}()
   141  
   142  	// Call f in the fake context
   143  	f()
   144  }
   145  
   146  // testClusterConfigMap is a short hand for creating and possibly signing a test config map.
   147  // This produces config maps that can be loaded by clusterConfigFromCluster
   148  func testClusterConfigMap(yaml string, signIt bool) *v1.ConfigMap {
   149  	cm := &v1.ConfigMap{
   150  		ObjectMeta: metav1.ObjectMeta{
   151  			Name:      constants.KubeadmConfigConfigMap,
   152  			Namespace: metav1.NamespaceSystem,
   153  		},
   154  		Data: map[string]string{
   155  			constants.ClusterConfigurationConfigMapKey: dedent.Dedent(yaml),
   156  		},
   157  	}
   158  
   159  	if signIt {
   160  		SignConfigMap(cm)
   161  	}
   162  
   163  	return cm
   164  }
   165  
   166  // oldClusterConfigVersion is used as an old unsupported version in tests throughout this file
   167  const oldClusterConfigVersion = "v1alpha1"
   168  
   169  var (
   170  	// currentClusterConfigVersion represents the current actively supported version of ClusterConfiguration
   171  	currentClusterConfigVersion = kubeadmapiv1.SchemeGroupVersion.Version
   172  
   173  	// currentFooClusterConfig is a minimal currently supported ClusterConfiguration
   174  	// with a well known value of clusterName (in this case `foo`)
   175  	currentFooClusterConfig = fmt.Sprintf(`
   176  		apiVersion: %s
   177  		kind: ClusterConfiguration
   178  		clusterName: foo
   179  	`, kubeadmapiv1.SchemeGroupVersion)
   180  
   181  	// oldFooClusterConfig is a minimal unsupported ClusterConfiguration
   182  	// with a well known value of clusterName (in this case `foo`)
   183  	oldFooClusterConfig = fmt.Sprintf(`
   184  		apiVersion: %s/%s
   185  		kind: ClusterConfiguration
   186  		clusterName: foo
   187  	`, kubeadmapiv1.GroupName, oldClusterConfigVersion)
   188  
   189  	// currentBarClusterConfig is a minimal currently supported ClusterConfiguration
   190  	// with a well known value of clusterName (in this case `bar`)
   191  	currentBarClusterConfig = fmt.Sprintf(`
   192  		apiVersion: %s
   193  		kind: ClusterConfiguration
   194  		clusterName: bar
   195  	`, kubeadmapiv1.SchemeGroupVersion)
   196  
   197  	// oldBarClusterConfig is a minimal unsupported ClusterConfiguration
   198  	// with a well known value of clusterName (in this case `bar`)
   199  	oldBarClusterConfig = fmt.Sprintf(`
   200  		apiVersion: %s/%s
   201  		kind: ClusterConfiguration
   202  		clusterName: bar
   203  	`, kubeadmapiv1.GroupName, oldClusterConfigVersion)
   204  
   205  	// This is the "minimal" valid config that can be unmarshalled to and from YAML.
   206  	// Due to same static defaulting it's not exactly small in size.
   207  	validUnmarshallableClusterConfig = struct {
   208  		yaml string
   209  		obj  kubeadmapiv1.ClusterConfiguration
   210  	}{
   211  		yaml: dedent.Dedent(fmt.Sprintf(`
   212  			apiServer:
   213  			  timeoutForControlPlane: 4m
   214  			apiVersion: %s
   215  			certificatesDir: /etc/kubernetes/pki
   216  			clusterName: LeCluster
   217  			controllerManager: {}
   218  			etcd:
   219  			  local:
   220  			    dataDir: /var/lib/etcd
   221  			imageRepository: registry.k8s.io
   222  			kind: ClusterConfiguration
   223  			kubernetesVersion: 1.2.3
   224  			networking:
   225  			  dnsDomain: cluster.local
   226  			  serviceSubnet: 10.96.0.0/12
   227  			scheduler: {}
   228  		`, kubeadmapiv1.SchemeGroupVersion.String())),
   229  		obj: kubeadmapiv1.ClusterConfiguration{
   230  			TypeMeta: metav1.TypeMeta{
   231  				APIVersion: kubeadmapiv1.SchemeGroupVersion.String(),
   232  				Kind:       "ClusterConfiguration",
   233  			},
   234  			ClusterName:       "LeCluster",
   235  			KubernetesVersion: "1.2.3",
   236  			CertificatesDir:   "/etc/kubernetes/pki",
   237  			ImageRepository:   "registry.k8s.io",
   238  			Networking: kubeadmapiv1.Networking{
   239  				DNSDomain:     "cluster.local",
   240  				ServiceSubnet: "10.96.0.0/12",
   241  			},
   242  			Etcd: kubeadmapiv1.Etcd{
   243  				Local: &kubeadmapiv1.LocalEtcd{
   244  					DataDir: "/var/lib/etcd",
   245  				},
   246  			},
   247  			APIServer: kubeadmapiv1.APIServer{
   248  				TimeoutForControlPlane: &metav1.Duration{
   249  					Duration: 4 * time.Minute,
   250  				},
   251  			},
   252  		},
   253  	}
   254  )
   255  
   256  func TestConfigBaseMarshal(t *testing.T) {
   257  	fakeKnownContext(func() {
   258  		cfg := &clusterConfig{
   259  			configBase: configBase{
   260  				GroupVersion: kubeadmapiv1.SchemeGroupVersion,
   261  			},
   262  			config: kubeadmapiv1.ClusterConfiguration{
   263  				TypeMeta: metav1.TypeMeta{
   264  					APIVersion: kubeadmapiv1.SchemeGroupVersion.String(),
   265  					Kind:       "ClusterConfiguration",
   266  				},
   267  				ClusterName:       "LeCluster",
   268  				KubernetesVersion: "1.2.3",
   269  			},
   270  		}
   271  
   272  		b, err := cfg.Marshal()
   273  		if err != nil {
   274  			t.Fatalf("Marshal failed: %v", err)
   275  		}
   276  
   277  		got := strings.TrimSpace(string(b))
   278  		expected := strings.TrimSpace(dedent.Dedent(fmt.Sprintf(`
   279  			apiServer: {}
   280  			apiVersion: %s
   281  			clusterName: LeCluster
   282  			controllerManager: {}
   283  			dns: {}
   284  			etcd: {}
   285  			kind: ClusterConfiguration
   286  			kubernetesVersion: 1.2.3
   287  			networking: {}
   288  			scheduler: {}
   289  		`, kubeadmapiv1.SchemeGroupVersion.String())))
   290  
   291  		if expected != got {
   292  			t.Fatalf("Missmatch between expected and got:\nExpected:\n%s\n---\nGot:\n%s", expected, got)
   293  		}
   294  	})
   295  }
   296  
   297  func TestConfigBaseUnmarshal(t *testing.T) {
   298  	fakeKnownContext(func() {
   299  		expected := &clusterConfig{
   300  			configBase: configBase{
   301  				GroupVersion: kubeadmapiv1.SchemeGroupVersion,
   302  			},
   303  			config: validUnmarshallableClusterConfig.obj,
   304  		}
   305  
   306  		gvkmap, err := kubeadmutil.SplitYAMLDocuments([]byte(validUnmarshallableClusterConfig.yaml))
   307  		if err != nil {
   308  			t.Fatalf("unexpected failure of SplitYAMLDocuments: %v", err)
   309  		}
   310  
   311  		got := &clusterConfig{
   312  			configBase: configBase{
   313  				GroupVersion: kubeadmapiv1.SchemeGroupVersion,
   314  			},
   315  		}
   316  		if err = got.Unmarshal(gvkmap); err != nil {
   317  			t.Fatalf("unexpected failure of Unmarshal: %v", err)
   318  		}
   319  
   320  		if !reflect.DeepEqual(got, expected) {
   321  			t.Fatalf("Missmatch between expected and got:\nExpected:\n%v\n---\nGot:\n%v", expected, got)
   322  		}
   323  	})
   324  }
   325  
   326  func TestGeneratedConfigFromCluster(t *testing.T) {
   327  	fakeKnownContext(func() {
   328  		testYAML := dedent.Dedent(fmt.Sprintf(`
   329  			apiVersion: %s
   330  			kind: ClusterConfiguration
   331  		`, kubeadmapiv1.SchemeGroupVersion.String()))
   332  		testYAMLHash := fmt.Sprintf("sha256:%x", sha256.Sum256([]byte(testYAML)))
   333  		// The SHA256 sum of "The quick brown fox jumps over the lazy dog"
   334  		const mismatchHash = "sha256:d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592"
   335  		tests := []struct {
   336  			name         string
   337  			hash         string
   338  			userSupplied bool
   339  		}{
   340  			{
   341  				name: "Matching hash means generated config",
   342  				hash: testYAMLHash,
   343  			},
   344  			{
   345  				name:         "Missmatching hash means user supplied config",
   346  				hash:         mismatchHash,
   347  				userSupplied: true,
   348  			},
   349  			{
   350  				name:         "No hash means user supplied config",
   351  				userSupplied: true,
   352  			},
   353  		}
   354  		for _, test := range tests {
   355  			t.Run(test.name, func(t *testing.T) {
   356  				configMap := testClusterConfigMap(testYAML, false)
   357  				if test.hash != "" {
   358  					configMap.Annotations = map[string]string{
   359  						constants.ComponentConfigHashAnnotationKey: test.hash,
   360  					}
   361  				}
   362  
   363  				client := clientsetfake.NewSimpleClientset(configMap)
   364  				cfg, err := clusterConfigHandler.FromCluster(client, testClusterCfg())
   365  				if err != nil {
   366  					t.Fatalf("unexpected failure of FromCluster: %v", err)
   367  				}
   368  
   369  				got := cfg.IsUserSupplied()
   370  				if got != test.userSupplied {
   371  					t.Fatalf("mismatch between expected and got:\n\tExpected: %t\n\tGot: %t", test.userSupplied, got)
   372  				}
   373  			})
   374  		}
   375  	})
   376  }
   377  
   378  // runClusterConfigFromTest holds common test case data and evaluation code for handler.From* functions
   379  func runClusterConfigFromTest(t *testing.T, perform func(t *testing.T, in string) (kubeadmapi.ComponentConfig, error)) {
   380  	fakeKnownContext(func() {
   381  		tests := []struct {
   382  			name      string
   383  			in        string
   384  			out       *clusterConfig
   385  			expectErr bool
   386  		}{
   387  			{
   388  				name: "Empty document map should return nothing successfully",
   389  			},
   390  			{
   391  				name: "Non-empty document map without the proper API group returns nothing successfully",
   392  				in: dedent.Dedent(`
   393  					apiVersion: api.example.com/v1
   394  					kind: Configuration
   395  				`),
   396  			},
   397  			{
   398  				name: "Old config version returns an error",
   399  				in: dedent.Dedent(`
   400  					apiVersion: kubeadm.k8s.io/v1alpha1
   401  					kind: ClusterConfiguration
   402  				`),
   403  				expectErr: true,
   404  			},
   405  			{
   406  				name: "Unknown kind returns an error",
   407  				in: dedent.Dedent(fmt.Sprintf(`
   408  					apiVersion: %s
   409  					kind: Configuration
   410  				`, kubeadmapiv1.SchemeGroupVersion.String())),
   411  				expectErr: true,
   412  			},
   413  			{
   414  				name: "Valid config gets loaded",
   415  				in:   validUnmarshallableClusterConfig.yaml,
   416  				out: &clusterConfig{
   417  					configBase: configBase{
   418  						GroupVersion: clusterConfigHandler.GroupVersion,
   419  						userSupplied: true,
   420  					},
   421  					config: validUnmarshallableClusterConfig.obj,
   422  				},
   423  			},
   424  			{
   425  				name: "Valid config gets loaded even if coupled with an extra document",
   426  				in:   "apiVersion: api.example.com/v1\nkind: Configuration\n---\n" + validUnmarshallableClusterConfig.yaml,
   427  				out: &clusterConfig{
   428  					configBase: configBase{
   429  						GroupVersion: clusterConfigHandler.GroupVersion,
   430  						userSupplied: true,
   431  					},
   432  					config: validUnmarshallableClusterConfig.obj,
   433  				},
   434  			},
   435  		}
   436  
   437  		for _, test := range tests {
   438  			t.Run(test.name, func(t *testing.T) {
   439  				componentCfg, err := perform(t, test.in)
   440  				if err != nil {
   441  					if !test.expectErr {
   442  						t.Errorf("unexpected failure: %v", err)
   443  					}
   444  				} else {
   445  					if test.expectErr {
   446  						t.Error("unexpected success")
   447  					} else {
   448  						if componentCfg == nil {
   449  							if test.out != nil {
   450  								t.Error("unexpected nil result")
   451  							}
   452  						} else {
   453  							if got, ok := componentCfg.(*clusterConfig); !ok {
   454  								t.Error("different result type")
   455  							} else {
   456  								if test.out == nil {
   457  									t.Errorf("unexpected result: %v", got)
   458  								} else {
   459  									if !reflect.DeepEqual(test.out, got) {
   460  										t.Errorf("mismatch between expected and got:\nExpected:\n%v\n---\nGot:\n%v", test.out, got)
   461  									}
   462  								}
   463  							}
   464  						}
   465  					}
   466  				}
   467  			})
   468  		}
   469  	})
   470  }
   471  
   472  func TestLoadingFromDocumentMap(t *testing.T) {
   473  	runClusterConfigFromTest(t, func(t *testing.T, in string) (kubeadmapi.ComponentConfig, error) {
   474  		gvkmap, err := kubeadmutil.SplitYAMLDocuments([]byte(in))
   475  		if err != nil {
   476  			t.Fatalf("unexpected failure of SplitYAMLDocuments: %v", err)
   477  		}
   478  
   479  		return clusterConfigHandler.FromDocumentMap(gvkmap)
   480  	})
   481  }
   482  
   483  func TestLoadingFromCluster(t *testing.T) {
   484  	runClusterConfigFromTest(t, func(t *testing.T, in string) (kubeadmapi.ComponentConfig, error) {
   485  		client := clientsetfake.NewSimpleClientset(
   486  			testClusterConfigMap(in, false),
   487  		)
   488  
   489  		return clusterConfigHandler.FromCluster(client, testClusterCfg())
   490  	})
   491  }
   492  
   493  func TestFetchFromClusterWithLocalOverwrites(t *testing.T) {
   494  	fakeKnownContext(func() {
   495  		cases := []struct {
   496  			desc          string
   497  			obj           runtime.Object
   498  			config        string
   499  			expectedValue string
   500  			isNotLoaded   bool
   501  			expectedErr   bool
   502  		}{
   503  			{
   504  				desc:          "appropriate cluster object without overwrite is used",
   505  				obj:           testClusterConfigMap(currentFooClusterConfig, false),
   506  				expectedValue: "foo",
   507  			},
   508  			{
   509  				desc:          "appropriate cluster object with appropriate overwrite is overwritten",
   510  				obj:           testClusterConfigMap(currentFooClusterConfig, false),
   511  				config:        dedent.Dedent(currentBarClusterConfig),
   512  				expectedValue: "bar",
   513  			},
   514  			{
   515  				desc:        "appropriate cluster object with old overwrite returns an error",
   516  				obj:         testClusterConfigMap(currentFooClusterConfig, false),
   517  				config:      dedent.Dedent(oldBarClusterConfig),
   518  				expectedErr: true,
   519  			},
   520  			{
   521  				desc:        "old config without overwrite returns an error",
   522  				obj:         testClusterConfigMap(oldFooClusterConfig, false),
   523  				expectedErr: true,
   524  			},
   525  			{
   526  				desc:          "old config with appropriate overwrite returns the substitute",
   527  				obj:           testClusterConfigMap(oldFooClusterConfig, false),
   528  				config:        dedent.Dedent(currentBarClusterConfig),
   529  				expectedValue: "bar",
   530  			},
   531  			{
   532  				desc:        "old config with old overwrite returns an error",
   533  				obj:         testClusterConfigMap(oldFooClusterConfig, false),
   534  				config:      dedent.Dedent(oldBarClusterConfig),
   535  				expectedErr: true,
   536  			},
   537  			{
   538  				desc:          "appropriate signed cluster object without overwrite is used",
   539  				obj:           testClusterConfigMap(currentFooClusterConfig, true),
   540  				expectedValue: "foo",
   541  			},
   542  			{
   543  				desc:          "appropriate signed cluster object with appropriate overwrite is overwritten",
   544  				obj:           testClusterConfigMap(currentFooClusterConfig, true),
   545  				config:        dedent.Dedent(currentBarClusterConfig),
   546  				expectedValue: "bar",
   547  			},
   548  			{
   549  				desc:        "appropriate signed cluster object with old overwrite returns an error",
   550  				obj:         testClusterConfigMap(currentFooClusterConfig, true),
   551  				config:      dedent.Dedent(oldBarClusterConfig),
   552  				expectedErr: true,
   553  			},
   554  			{
   555  				desc:        "old signed config without an overwrite is not loaded",
   556  				obj:         testClusterConfigMap(oldFooClusterConfig, true),
   557  				isNotLoaded: true,
   558  			},
   559  			{
   560  				desc:          "old signed config with appropriate overwrite returns the substitute",
   561  				obj:           testClusterConfigMap(oldFooClusterConfig, true),
   562  				config:        dedent.Dedent(currentBarClusterConfig),
   563  				expectedValue: "bar",
   564  			},
   565  			{
   566  				desc:        "old signed config with old overwrite returns an error",
   567  				obj:         testClusterConfigMap(oldFooClusterConfig, true),
   568  				config:      dedent.Dedent(oldBarClusterConfig),
   569  				expectedErr: true,
   570  			},
   571  		}
   572  
   573  		for _, test := range cases {
   574  			t.Run(test.desc, func(t *testing.T) {
   575  				client := clientsetfake.NewSimpleClientset(test.obj)
   576  
   577  				docmap, err := kubeadmutil.SplitYAMLDocuments([]byte(test.config))
   578  				if err != nil {
   579  					t.Fatalf("unexpected failure of SplitYAMLDocuments: %v", err)
   580  				}
   581  
   582  				clusterCfg := testClusterCfg()
   583  
   584  				err = FetchFromClusterWithLocalOverwrites(clusterCfg, client, docmap)
   585  				if err != nil {
   586  					if !test.expectedErr {
   587  						t.Errorf("unexpected failure: %v", err)
   588  					}
   589  				} else {
   590  					if test.expectedErr {
   591  						t.Error("unexpected success")
   592  					} else {
   593  						clusterCfg, ok := clusterCfg.ComponentConfigs[kubeadmapiv1.GroupName]
   594  						if !ok {
   595  							if !test.isNotLoaded {
   596  								t.Error("no config was loaded when it should have been")
   597  							}
   598  						} else {
   599  							actualConfig, ok := clusterCfg.(*clusterConfig)
   600  							if !ok {
   601  								t.Error("the config is not of the expected type")
   602  							} else if actualConfig.config.ClusterName != test.expectedValue {
   603  								t.Errorf("unexpected value:\n\tgot: %q\n\texpected: %q", actualConfig.config.ClusterName, test.expectedValue)
   604  							}
   605  						}
   606  					}
   607  				}
   608  			})
   609  		}
   610  	})
   611  }
   612  
   613  func TestGetVersionStates(t *testing.T) {
   614  	fakeKnownContext(func() {
   615  		versionStateCurrent := outputapiv1alpha3.ComponentConfigVersionState{
   616  			Group:            kubeadmapiv1.GroupName,
   617  			CurrentVersion:   currentClusterConfigVersion,
   618  			PreferredVersion: currentClusterConfigVersion,
   619  		}
   620  
   621  		cases := []struct {
   622  			desc        string
   623  			obj         runtime.Object
   624  			expectedErr bool
   625  			expected    outputapiv1alpha3.ComponentConfigVersionState
   626  		}{
   627  			{
   628  				desc:     "appropriate cluster object",
   629  				obj:      testClusterConfigMap(currentFooClusterConfig, false),
   630  				expected: versionStateCurrent,
   631  			},
   632  			{
   633  				desc:        "old config returns an error",
   634  				obj:         testClusterConfigMap(oldFooClusterConfig, false),
   635  				expectedErr: true,
   636  			},
   637  			{
   638  				desc:     "appropriate signed cluster object",
   639  				obj:      testClusterConfigMap(currentFooClusterConfig, true),
   640  				expected: versionStateCurrent,
   641  			},
   642  			{
   643  				desc: "old signed config",
   644  				obj:  testClusterConfigMap(oldFooClusterConfig, true),
   645  				expected: outputapiv1alpha3.ComponentConfigVersionState{
   646  					Group:            kubeadmapiv1.GroupName,
   647  					CurrentVersion:   "", // The config is treated as if it's missing
   648  					PreferredVersion: currentClusterConfigVersion,
   649  				},
   650  			},
   651  		}
   652  
   653  		for _, test := range cases {
   654  			t.Run(test.desc, func(t *testing.T) {
   655  				client := clientsetfake.NewSimpleClientset(test.obj)
   656  
   657  				clusterCfg := testClusterCfg()
   658  
   659  				got, err := GetVersionStates(clusterCfg, client)
   660  				if err != nil && !test.expectedErr {
   661  					t.Errorf("unexpected error: %v", err)
   662  				}
   663  				if err == nil {
   664  					if test.expectedErr {
   665  						t.Errorf("expected error not found: %v", test.expectedErr)
   666  					}
   667  					if len(got) != 1 {
   668  						t.Errorf("got %d, but expected only a single result: %v", len(got), got)
   669  					} else if got[0] != test.expected {
   670  						t.Errorf("unexpected result:\n\texpected: %v\n\tgot: %v", test.expected, got[0])
   671  					}
   672  				}
   673  			})
   674  		}
   675  	})
   676  }
   677  

View as plain text