...

Source file src/k8s.io/kubernetes/cmd/kubeadm/app/util/config/cluster_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  	"context"
    21  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  	"reflect"
    25  	"strconv"
    26  	"strings"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/pkg/errors"
    31  
    32  	v1 "k8s.io/api/core/v1"
    33  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/runtime"
    36  	clientsetfake "k8s.io/client-go/kubernetes/fake"
    37  	clienttesting "k8s.io/client-go/testing"
    38  
    39  	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
    40  	kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
    41  	"k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
    42  	kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
    43  	testresources "k8s.io/kubernetes/cmd/kubeadm/test/resources"
    44  )
    45  
    46  var k8sVersionString = kubeadmconstants.MinimumControlPlaneVersion.String()
    47  var nodeName = "mynode"
    48  var cfgFiles = map[string][]byte{
    49  	"InitConfiguration_v1beta3": []byte(fmt.Sprintf(`
    50  apiVersion: %s
    51  kind: InitConfiguration
    52  `, kubeadmapiv1.SchemeGroupVersion.String())),
    53  	"ClusterConfiguration_v1beta3": []byte(fmt.Sprintf(`
    54  apiVersion: %s
    55  kind: ClusterConfiguration
    56  kubernetesVersion: %s
    57  `, kubeadmapiv1.SchemeGroupVersion.String(), k8sVersionString)),
    58  	"Kube-proxy_componentconfig": []byte(`
    59  apiVersion: kubeproxy.config.k8s.io/v1alpha1
    60  kind: KubeProxyConfiguration
    61  `),
    62  	"Kubelet_componentconfig": []byte(`
    63  apiVersion: kubelet.config.k8s.io/v1beta1
    64  kind: KubeletConfiguration
    65  `),
    66  }
    67  
    68  var kubeletConfFiles = map[string][]byte{
    69  	"withoutX509Cert": []byte(`
    70  apiVersion: v1
    71  clusters:
    72  - cluster:
    73      server: https://10.0.2.15:6443
    74      name: kubernetes
    75  contexts:
    76  - context:
    77      cluster: kubernetes
    78      user: system:node:mynode
    79    name: system:node:mynode@kubernetes
    80  current-context: system:node:mynode@kubernetes
    81  kind: Config
    82  preferences: {}
    83  users:
    84  - name: system:node:mynode
    85    user:
    86  `),
    87  	"configWithEmbeddedCert": []byte(`
    88  apiVersion: v1
    89  clusters:
    90  - cluster:
    91      server: https://10.0.2.15:6443
    92    name: kubernetes
    93  contexts:
    94  - context:
    95      cluster: kubernetes
    96      user: system:node:mynode
    97    name: system:node:mynode@kubernetes
    98  current-context: system:node:mynode@kubernetes
    99  kind: Config
   100  preferences: {}
   101  users:
   102  - name: system:node:mynode
   103    user:
   104        client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJQWl3VURhYk5vZ1F3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T0RBNU1ERXhOVE14TWpaYUZ3MHhPVEE1TURFeE5qQXhOVGxhTURReApGVEFUQmdOVkJBb1RESE41YzNSbGJUcHViMlJsY3pFYk1Ca0dBMVVFQXhNU2MzbHpkR1Z0T201dlpHVTZiWGx1CmIyUmxNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQWs2UXUzeStyNEZYUzZ4VkoKWU1vNE9kSkt3R1d1MHY4TEJIUnhhOUhvVHo1RXZLQnB1OVJoemt5dStUaFczb0xta2ZTRmNJcitHa0M5MW0zOApFelRmVE5JY2dsL0V5YkpmR1QvdGdUazZYd1kxY1UrUUdmSEFNNTBCVzFXTFVHc25CSllJZjA5eENnZTVoTkxLCnREeUJOWWNQZzg1bUJpOU9CNFJ2UlgyQVFRMjJwZ0xrQUpJWklOU0FEdUFrODN2b051SXM2YVY2bHBkR2Vva3YKdDlpTFdNR3p3a3ZTZUZQTlNGeWZ3Q055eENjb1FDQUNtSnJRS3NoeUE2bWNzdVhORWVXdlRQdVVFSWZPVFl4dwpxdkszRVBOK0xUYlA2anhUMWtTcFJUOSt4Z29uSlFhU0RsbUNBd20zRGJkSVppWUt3R2ppMkxKL0kvYWc0cTlzCjNLb0J2UUlEQVFBQm95Y3dKVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLcVVrU21jdW85OG5EK015b005VFdEV0pyTndySXpQTUNqRQpCSkdyREhVaHIwcEZlRjc0RHViODNzRXlaNjFxNUVQd2Y0enNLSzdzdDRUTzZhcE9pZWJYVmN3dnZoa09HQ2dFCmFVdGNOMjFhUGxtU0tOd0c4ai8yK3ZhbU80bGplK1NnZzRUUVB0eDZWejh5VXN2RFhxSUZycjNNd1gzSDA1RW4KWXAzN05JYkhKbGxHUW5LVHA5aTg5aXF4WXVhSERqZldiVHlEY3B5NldNVjdVaFYvY1plc3lGL0NBamNHd1V6YgowRlo5bW5tMnFONlBGWHZ4RmdMSGFWZzN2SVVCbkNmVVVyY1BDNE94VFNPK21aUmUxazh3eUFpVWovSk0rZllvCkcrMi9sbThUYVZqb1U3Rmk1S2E1RzVIWTJHTGFSN1ArSXhZY3JNSENsNjJZN1JxY3JuYz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
   105  `),
   106  	"configWithLinkedCert": []byte(`
   107  apiVersion: v1
   108  clusters:
   109  - cluster:
   110      server: https://10.0.2.15:6443
   111    name: kubernetes
   112  contexts:
   113  - context:
   114      cluster: kubernetes
   115      user: system:node:mynode
   116    name: system:node:mynode@kubernetes
   117  current-context: system:node:mynode@kubernetes
   118  kind: Config
   119  preferences: {}
   120  users:
   121  - name: system:node:mynode
   122    user:
   123        client-certificate: kubelet.pem
   124  `),
   125  	"configWithInvalidContext": []byte(`
   126  apiVersion: v1
   127  clusters:
   128  - cluster:
   129      server: https://10.0.2.15:6443
   130    name: kubernetes
   131  contexts:
   132  - context:
   133      cluster: kubernetes
   134      user: system:node:mynode
   135    name: system:node:mynode@kubernetes
   136  current-context: invalidContext
   137  kind: Config
   138  preferences: {}
   139  users:
   140  - name: system:node:mynode
   141    user:
   142        client-certificate: kubelet.pem
   143  `),
   144  	"configWithInvalidUser": []byte(`
   145  apiVersion: v1
   146  clusters:
   147  - cluster:
   148      server: https://10.0.2.15:6443
   149    name: kubernetes
   150  contexts:
   151  - context:
   152      cluster: kubernetes
   153      user: invalidUser
   154    name: system:node:mynode@kubernetes
   155  current-context: system:node:mynode@kubernetes
   156  kind: Config
   157  preferences: {}
   158  users:
   159  - name: system:node:mynode
   160    user:
   161        client-certificate: kubelet.pem
   162  `),
   163  }
   164  
   165  var pemFiles = map[string][]byte{
   166  	"mynode.pem": []byte(`
   167  -----BEGIN CERTIFICATE-----
   168  MIIC8jCCAdqgAwIBAgIIAiwUDabNogQwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
   169  AxMKa3ViZXJuZXRlczAeFw0xODA5MDExNTMxMjZaFw0xOTA5MDExNjAxNTlaMDQx
   170  FTATBgNVBAoTDHN5c3RlbTpub2RlczEbMBkGA1UEAxMSc3lzdGVtOm5vZGU6bXlu
   171  b2RlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk6Qu3y+r4FXS6xVJ
   172  YMo4OdJKwGWu0v8LBHRxa9HoTz5EvKBpu9Rhzkyu+ThW3oLmkfSFcIr+GkC91m38
   173  EzTfTNIcgl/EybJfGT/tgTk6XwY1cU+QGfHAM50BW1WLUGsnBJYIf09xCge5hNLK
   174  tDyBNYcPg85mBi9OB4RvRX2AQQ22pgLkAJIZINSADuAk83voNuIs6aV6lpdGeokv
   175  t9iLWMGzwkvSeFPNSFyfwCNyxCcoQCACmJrQKshyA6mcsuXNEeWvTPuUEIfOTYxw
   176  qvK3EPN+LTbP6jxT1kSpRT9+xgonJQaSDlmCAwm3DbdIZiYKwGji2LJ/I/ag4q9s
   177  3KoBvQIDAQABoycwJTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUH
   178  AwIwDQYJKoZIhvcNAQELBQADggEBAKqUkSmcuo98nD+MyoM9TWDWJrNwrIzPMCjE
   179  BJGrDHUhr0pFeF74Dub83sEyZ61q5EPwf4zsKK7st4TO6apOiebXVcwvvhkOGCgE
   180  aUtcN21aPlmSKNwG8j/2+vamO4lje+Sgg4TQPtx6Vz8yUsvDXqIFrr3MwX3H05En
   181  Yp37NIbHJllGQnKTp9i89iqxYuaHDjfWbTyDcpy6WMV7UhV/cZesyF/CAjcGwUzb
   182  0FZ9mnm2qN6PFXvxFgLHaVg3vIUBnCfUUrcPC4OxTSO+mZRe1k8wyAiUj/JM+fYo
   183  G+2/lm8TaVjoU7Fi5Ka5G5HY2GLaR7P+IxYcrMHCl62Y7Rqcrnc=
   184  -----END CERTIFICATE-----
   185  `),
   186  }
   187  
   188  func TestGetNodeNameFromKubeletConfig(t *testing.T) {
   189  	tmpdir, err := os.MkdirTemp("", "")
   190  	if err != nil {
   191  		t.Fatalf("Couldn't create tmpdir")
   192  	}
   193  	defer os.RemoveAll(tmpdir)
   194  
   195  	var tests = []struct {
   196  		name              string
   197  		kubeconfigContent []byte
   198  		pemContent        []byte
   199  		expectedError     bool
   200  	}{
   201  		{
   202  			name:              "valid - with embedded cert",
   203  			kubeconfigContent: kubeletConfFiles["configWithEmbeddedCert"],
   204  		},
   205  		{
   206  			name:              "invalid - linked cert missing",
   207  			kubeconfigContent: kubeletConfFiles["configWithLinkedCert"],
   208  			expectedError:     true,
   209  		},
   210  		{
   211  			name:              "valid - with linked cert",
   212  			kubeconfigContent: kubeletConfFiles["configWithLinkedCert"],
   213  			pemContent:        pemFiles["mynode.pem"],
   214  		},
   215  		{
   216  			name:              "invalid - without embedded or linked X509Cert",
   217  			kubeconfigContent: kubeletConfFiles["withoutX509Cert"],
   218  			expectedError:     true,
   219  		},
   220  		{
   221  			name:              "invalid - the current context is invalid",
   222  			kubeconfigContent: kubeletConfFiles["configWithInvalidContext"],
   223  			expectedError:     true,
   224  		},
   225  		{
   226  			name:              "invalid - the user of the current context is invalid",
   227  			kubeconfigContent: kubeletConfFiles["configWithInvalidUser"],
   228  			expectedError:     true,
   229  		},
   230  	}
   231  
   232  	for _, rt := range tests {
   233  		t.Run(rt.name, func(t2 *testing.T) {
   234  			if len(rt.pemContent) > 0 {
   235  				pemPath := filepath.Join(tmpdir, "kubelet.pem")
   236  				err := os.WriteFile(pemPath, rt.pemContent, 0644)
   237  				if err != nil {
   238  					t.Errorf("Couldn't create pem file: %v", err)
   239  					return
   240  				}
   241  				rt.kubeconfigContent = []byte(strings.Replace(string(rt.kubeconfigContent), "kubelet.pem", pemPath, -1))
   242  			}
   243  
   244  			kubeconfigPath := filepath.Join(tmpdir, kubeadmconstants.KubeletKubeConfigFileName)
   245  			err := os.WriteFile(kubeconfigPath, rt.kubeconfigContent, 0644)
   246  			if err != nil {
   247  				t.Errorf("Couldn't create kubeconfig: %v", err)
   248  				return
   249  			}
   250  
   251  			name, err := getNodeNameFromKubeletConfig(kubeconfigPath)
   252  			if rt.expectedError != (err != nil) {
   253  				t.Errorf("unexpected return err from getNodeRegistration: %v", err)
   254  				return
   255  			}
   256  			if rt.expectedError {
   257  				return
   258  			}
   259  
   260  			if name != nodeName {
   261  				t.Errorf("invalid name")
   262  			}
   263  		})
   264  	}
   265  }
   266  
   267  func TestGetNodeRegistration(t *testing.T) {
   268  	tmpdir, err := os.MkdirTemp("", "")
   269  	if err != nil {
   270  		t.Fatalf("Couldn't create tmpdir")
   271  	}
   272  	defer os.RemoveAll(tmpdir)
   273  
   274  	var tests = []struct {
   275  		name          string
   276  		fileContents  []byte
   277  		node          *v1.Node
   278  		expectedError bool
   279  	}{
   280  		{
   281  			name:          "invalid - no kubelet.conf",
   282  			expectedError: true,
   283  		},
   284  		{
   285  			name:         "valid",
   286  			fileContents: kubeletConfFiles["configWithEmbeddedCert"],
   287  			node: &v1.Node{
   288  				ObjectMeta: metav1.ObjectMeta{
   289  					Name: nodeName,
   290  					Annotations: map[string]string{
   291  						kubeadmconstants.AnnotationKubeadmCRISocket: "myCRIsocket",
   292  					},
   293  				},
   294  				Spec: v1.NodeSpec{
   295  					Taints: []v1.Taint{kubeadmconstants.ControlPlaneTaint},
   296  				},
   297  			},
   298  		},
   299  		{
   300  			name:          "invalid - no node",
   301  			fileContents:  kubeletConfFiles["configWithEmbeddedCert"],
   302  			expectedError: true,
   303  		},
   304  	}
   305  
   306  	for _, rt := range tests {
   307  		t.Run(rt.name, func(t2 *testing.T) {
   308  			cfgPath := filepath.Join(tmpdir, kubeadmconstants.KubeletKubeConfigFileName)
   309  			if len(rt.fileContents) > 0 {
   310  				err := os.WriteFile(cfgPath, rt.fileContents, 0644)
   311  				if err != nil {
   312  					t.Errorf("Couldn't create file")
   313  					return
   314  				}
   315  			}
   316  
   317  			client := clientsetfake.NewSimpleClientset()
   318  
   319  			if rt.node != nil {
   320  				_, err := client.CoreV1().Nodes().Create(context.TODO(), rt.node, metav1.CreateOptions{})
   321  				if err != nil {
   322  					t.Errorf("couldn't create Node")
   323  					return
   324  				}
   325  			}
   326  
   327  			cfg := &kubeadmapi.InitConfiguration{}
   328  			err = GetNodeRegistration(cfgPath, client, &cfg.NodeRegistration)
   329  			if rt.expectedError != (err != nil) {
   330  				t.Errorf("unexpected return err from getNodeRegistration: %v", err)
   331  				return
   332  			}
   333  			if rt.expectedError {
   334  				return
   335  			}
   336  
   337  			if cfg.NodeRegistration.Name != nodeName {
   338  				t.Errorf("invalid cfg.NodeRegistration.Name")
   339  			}
   340  			if cfg.NodeRegistration.CRISocket != "myCRIsocket" {
   341  				t.Errorf("invalid cfg.NodeRegistration.CRISocket")
   342  			}
   343  			if len(cfg.NodeRegistration.Taints) != 1 {
   344  				t.Errorf("invalid cfg.NodeRegistration.Taints")
   345  			}
   346  		})
   347  	}
   348  }
   349  
   350  func TestGetAPIEndpointWithBackoff(t *testing.T) {
   351  	var tests = []struct {
   352  		name             string
   353  		nodeName         string
   354  		staticPod        *testresources.FakeStaticPod
   355  		expectedEndpoint *kubeadmapi.APIEndpoint
   356  		expectedErr      bool
   357  	}{
   358  		{
   359  			name:        "no pod annotations",
   360  			nodeName:    nodeName,
   361  			expectedErr: true,
   362  		},
   363  		{
   364  			name:     "valid ipv4 endpoint in pod annotation",
   365  			nodeName: nodeName,
   366  			staticPod: &testresources.FakeStaticPod{
   367  				Component: kubeadmconstants.KubeAPIServer,
   368  				Annotations: map[string]string{
   369  					kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:1234",
   370  				},
   371  			},
   372  			expectedEndpoint: &kubeadmapi.APIEndpoint{AdvertiseAddress: "1.2.3.4", BindPort: 1234},
   373  		},
   374  		{
   375  			name:     "invalid ipv4 endpoint in pod annotation",
   376  			nodeName: nodeName,
   377  			staticPod: &testresources.FakeStaticPod{
   378  				Component: kubeadmconstants.KubeAPIServer,
   379  				Annotations: map[string]string{
   380  					kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3::1234",
   381  				},
   382  			},
   383  			expectedErr: true,
   384  		},
   385  		{
   386  			name:     "invalid negative port with ipv4 address in pod annotation",
   387  			nodeName: nodeName,
   388  			staticPod: &testresources.FakeStaticPod{
   389  				Component: kubeadmconstants.KubeAPIServer,
   390  				Annotations: map[string]string{
   391  					kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:-1234",
   392  				},
   393  			},
   394  			expectedErr: true,
   395  		},
   396  		{
   397  			name:     "invalid high port with ipv4 address in pod annotation",
   398  			nodeName: nodeName,
   399  			staticPod: &testresources.FakeStaticPod{
   400  				Component: kubeadmconstants.KubeAPIServer,
   401  				Annotations: map[string]string{
   402  					kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:65536",
   403  				},
   404  			},
   405  			expectedErr: true,
   406  		},
   407  		{
   408  			name:     "valid ipv6 endpoint in pod annotation",
   409  			nodeName: nodeName,
   410  			staticPod: &testresources.FakeStaticPod{
   411  				Component: kubeadmconstants.KubeAPIServer,
   412  				Annotations: map[string]string{
   413  					kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "[::1]:1234",
   414  				},
   415  			},
   416  			expectedEndpoint: &kubeadmapi.APIEndpoint{AdvertiseAddress: "::1", BindPort: 1234},
   417  		},
   418  		{
   419  			name:     "invalid ipv6 endpoint in pod annotation",
   420  			nodeName: nodeName,
   421  			staticPod: &testresources.FakeStaticPod{
   422  				Component: kubeadmconstants.KubeAPIServer,
   423  				Annotations: map[string]string{
   424  					kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "[::1:1234",
   425  				},
   426  			},
   427  			expectedErr: true,
   428  		},
   429  		{
   430  			name:     "invalid negative port with ipv6 address in pod annotation",
   431  			nodeName: nodeName,
   432  			staticPod: &testresources.FakeStaticPod{
   433  				Component: kubeadmconstants.KubeAPIServer,
   434  				Annotations: map[string]string{
   435  					kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "[::1]:-1234",
   436  				},
   437  			},
   438  			expectedErr: true,
   439  		},
   440  		{
   441  			name:     "invalid high port with ipv6 address in pod annotation",
   442  			nodeName: nodeName,
   443  			staticPod: &testresources.FakeStaticPod{
   444  				Component: kubeadmconstants.KubeAPIServer,
   445  				Annotations: map[string]string{
   446  					kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "[::1]:65536",
   447  				},
   448  			},
   449  			expectedErr: true,
   450  		},
   451  	}
   452  
   453  	for _, rt := range tests {
   454  		t.Run(rt.name, func(t *testing.T) {
   455  			client := clientsetfake.NewSimpleClientset()
   456  			if rt.staticPod != nil {
   457  				rt.staticPod.NodeName = rt.nodeName
   458  				if err := rt.staticPod.Create(client); err != nil {
   459  					t.Error("could not create static pod")
   460  					return
   461  				}
   462  			}
   463  			apiEndpoint := kubeadmapi.APIEndpoint{}
   464  			err := getAPIEndpointWithRetry(client, rt.nodeName, &apiEndpoint,
   465  				time.Millisecond*10, time.Millisecond*100)
   466  			if err != nil && !rt.expectedErr {
   467  				t.Errorf("got error %q; was expecting no errors", err)
   468  				return
   469  			} else if err == nil && rt.expectedErr {
   470  				t.Error("got no error; was expecting an error")
   471  				return
   472  			}
   473  
   474  			if rt.expectedEndpoint != nil && !reflect.DeepEqual(apiEndpoint, *rt.expectedEndpoint) {
   475  				t.Errorf("expected API endpoint: %v; got %v", rt.expectedEndpoint, apiEndpoint)
   476  			}
   477  		})
   478  	}
   479  }
   480  
   481  func TestGetInitConfigurationFromCluster(t *testing.T) {
   482  	tmpdir, err := os.MkdirTemp("", "")
   483  	if err != nil {
   484  		t.Fatalf("Couldn't create tmpdir")
   485  	}
   486  	defer os.RemoveAll(tmpdir)
   487  
   488  	var tests = []struct {
   489  		name            string
   490  		fileContents    []byte
   491  		node            *v1.Node
   492  		staticPods      []testresources.FakeStaticPod
   493  		configMaps      []testresources.FakeConfigMap
   494  		newControlPlane bool
   495  		expectedError   bool
   496  	}{
   497  		{
   498  			name:          "invalid - No kubeadm-config ConfigMap",
   499  			expectedError: true,
   500  		},
   501  		{
   502  			name: "invalid - No ClusterConfiguration in kubeadm-config ConfigMap",
   503  			configMaps: []testresources.FakeConfigMap{
   504  				{
   505  					Name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
   506  					Data: map[string]string{},
   507  				},
   508  			},
   509  			expectedError: true,
   510  		},
   511  		{
   512  			name: "valid v1beta3 - new control plane == false", // InitConfiguration composed with data from different places, with also node specific information
   513  			staticPods: []testresources.FakeStaticPod{
   514  				{
   515  					NodeName:  nodeName,
   516  					Component: kubeadmconstants.KubeAPIServer,
   517  					Annotations: map[string]string{
   518  						kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:1234",
   519  					},
   520  				},
   521  			},
   522  			configMaps: []testresources.FakeConfigMap{
   523  				{
   524  					Name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
   525  					Data: map[string]string{
   526  						kubeadmconstants.ClusterConfigurationConfigMapKey: string(cfgFiles["ClusterConfiguration_v1beta3"]),
   527  					},
   528  				},
   529  				{
   530  					Name: kubeadmconstants.KubeProxyConfigMap, // Kube-proxy component config from corresponding ConfigMap.
   531  					Data: map[string]string{
   532  						kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]),
   533  					},
   534  				},
   535  				{
   536  					Name: kubeadmconstants.KubeletBaseConfigurationConfigMap, // Kubelet component config from corresponding ConfigMap.
   537  					Data: map[string]string{
   538  						kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]),
   539  					},
   540  				},
   541  			},
   542  			fileContents: kubeletConfFiles["configWithEmbeddedCert"],
   543  			node: &v1.Node{
   544  				ObjectMeta: metav1.ObjectMeta{
   545  					Name: nodeName,
   546  					Annotations: map[string]string{
   547  						kubeadmconstants.AnnotationKubeadmCRISocket: "myCRIsocket",
   548  					},
   549  				},
   550  				Spec: v1.NodeSpec{
   551  					Taints: []v1.Taint{kubeadmconstants.ControlPlaneTaint},
   552  				},
   553  			},
   554  		},
   555  		{
   556  			name: "valid v1beta3 - new control plane == true", // InitConfiguration composed with data from different places, without node specific information
   557  			staticPods: []testresources.FakeStaticPod{
   558  				{
   559  					NodeName:  nodeName,
   560  					Component: kubeadmconstants.KubeAPIServer,
   561  					Annotations: map[string]string{
   562  						kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:1234",
   563  					},
   564  				},
   565  			},
   566  			configMaps: []testresources.FakeConfigMap{
   567  				{
   568  					Name: kubeadmconstants.KubeadmConfigConfigMap, // ClusterConfiguration from kubeadm-config.
   569  					Data: map[string]string{
   570  						kubeadmconstants.ClusterConfigurationConfigMapKey: string(cfgFiles["ClusterConfiguration_v1beta3"]),
   571  					},
   572  				},
   573  				{
   574  					Name: kubeadmconstants.KubeProxyConfigMap, // Kube-proxy component config from corresponding ConfigMap.
   575  					Data: map[string]string{
   576  						kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]),
   577  					},
   578  				},
   579  				{
   580  					Name: kubeadmconstants.KubeletBaseConfigurationConfigMap, // Kubelet component config from corresponding ConfigMap.
   581  					Data: map[string]string{
   582  						kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]),
   583  					},
   584  				},
   585  			},
   586  			newControlPlane: true,
   587  		},
   588  	}
   589  
   590  	for _, rt := range tests {
   591  		t.Run(rt.name, func(t *testing.T) {
   592  			cfgPath := filepath.Join(tmpdir, kubeadmconstants.KubeletKubeConfigFileName)
   593  			if len(rt.fileContents) > 0 {
   594  				err := os.WriteFile(cfgPath, rt.fileContents, 0644)
   595  				if err != nil {
   596  					t.Errorf("Couldn't create file")
   597  					return
   598  				}
   599  			}
   600  
   601  			client := clientsetfake.NewSimpleClientset()
   602  
   603  			if rt.node != nil {
   604  				_, err := client.CoreV1().Nodes().Create(context.TODO(), rt.node, metav1.CreateOptions{})
   605  				if err != nil {
   606  					t.Errorf("couldn't create Node")
   607  					return
   608  				}
   609  			}
   610  
   611  			for _, p := range rt.staticPods {
   612  				err := p.Create(client)
   613  				if err != nil {
   614  					t.Errorf("couldn't create pod for nodename %s", p.NodeName)
   615  					return
   616  				}
   617  			}
   618  
   619  			for _, c := range rt.configMaps {
   620  				err := c.Create(client)
   621  				if err != nil {
   622  					t.Errorf("couldn't create ConfigMap %s", c.Name)
   623  					return
   624  				}
   625  			}
   626  
   627  			cfg, err := getInitConfigurationFromCluster(tmpdir, client, rt.newControlPlane, false)
   628  			if rt.expectedError != (err != nil) {
   629  				t.Errorf("unexpected return err from getInitConfigurationFromCluster: %v", err)
   630  				return
   631  			}
   632  			if rt.expectedError {
   633  				return
   634  			}
   635  
   636  			// Test expected values in InitConfiguration
   637  			if cfg == nil {
   638  				t.Errorf("unexpected nil return value")
   639  				return
   640  			}
   641  			if cfg.ClusterConfiguration.KubernetesVersion != k8sVersionString {
   642  				t.Errorf("invalid ClusterConfiguration.KubernetesVersion")
   643  			}
   644  			if cfg.NodeRegistration.ImagePullPolicy != kubeadmapiv1.DefaultImagePullPolicy {
   645  				t.Errorf("invalid cfg.NodeRegistration.ImagePullPolicy %v", cfg.NodeRegistration.ImagePullPolicy)
   646  			}
   647  			if !rt.newControlPlane && (cfg.LocalAPIEndpoint.AdvertiseAddress != "1.2.3.4" || cfg.LocalAPIEndpoint.BindPort != 1234) {
   648  				t.Errorf("invalid cfg.LocalAPIEndpoint: %v", cfg.LocalAPIEndpoint)
   649  			}
   650  			if !rt.newControlPlane && (cfg.NodeRegistration.Name != nodeName || cfg.NodeRegistration.CRISocket != "myCRIsocket" || len(cfg.NodeRegistration.Taints) != 1) {
   651  				t.Errorf("invalid cfg.NodeRegistration: %v", cfg.NodeRegistration)
   652  			}
   653  			if rt.newControlPlane && len(cfg.NodeRegistration.CRISocket) > 0 {
   654  				t.Errorf("invalid cfg.NodeRegistration.CRISocket: expected empty CRISocket, but got %v", cfg.NodeRegistration.CRISocket)
   655  			}
   656  			if _, ok := cfg.ComponentConfigs[componentconfigs.KubeletGroup]; !ok {
   657  				t.Errorf("no cfg.ComponentConfigs[%q]", componentconfigs.KubeletGroup)
   658  			}
   659  			if _, ok := cfg.ComponentConfigs[componentconfigs.KubeProxyGroup]; !ok {
   660  				t.Errorf("no cfg.ComponentConfigs[%q]", componentconfigs.KubeProxyGroup)
   661  			}
   662  		})
   663  	}
   664  }
   665  
   666  func TestGetAPIEndpointFromPodAnnotation(t *testing.T) {
   667  	var tests = []struct {
   668  		name             string
   669  		nodeName         string
   670  		pods             []testresources.FakeStaticPod
   671  		clientSetup      func(*clientsetfake.Clientset)
   672  		expectedEndpoint kubeadmapi.APIEndpoint
   673  		expectedErr      bool
   674  	}{
   675  		{
   676  			name:     "exactly one pod with annotation",
   677  			nodeName: nodeName,
   678  			pods: []testresources.FakeStaticPod{
   679  				{
   680  					Component:   kubeadmconstants.KubeAPIServer,
   681  					Annotations: map[string]string{kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:1234"},
   682  				},
   683  			},
   684  			expectedEndpoint: kubeadmapi.APIEndpoint{AdvertiseAddress: "1.2.3.4", BindPort: 1234},
   685  		},
   686  		{
   687  			name:        "no pods with annotation",
   688  			nodeName:    nodeName,
   689  			expectedErr: true,
   690  		},
   691  		{
   692  			name:     "exactly one pod with annotation; all requests fail",
   693  			nodeName: nodeName,
   694  			pods: []testresources.FakeStaticPod{
   695  				{
   696  					Component:   kubeadmconstants.KubeAPIServer,
   697  					Annotations: map[string]string{kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:1234"},
   698  				},
   699  			},
   700  			clientSetup: func(clientset *clientsetfake.Clientset) {
   701  				clientset.PrependReactor("list", "pods", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   702  					return true, nil, apierrors.NewInternalError(errors.New("API server down"))
   703  				})
   704  			},
   705  			expectedErr: true,
   706  		},
   707  	}
   708  	for _, rt := range tests {
   709  		t.Run(rt.name, func(t *testing.T) {
   710  			client := clientsetfake.NewSimpleClientset()
   711  			for i, pod := range rt.pods {
   712  				pod.NodeName = rt.nodeName
   713  				if err := pod.CreateWithPodSuffix(client, strconv.Itoa(i)); err != nil {
   714  					t.Errorf("error setting up test creating pod for node %q", pod.NodeName)
   715  					return
   716  				}
   717  			}
   718  			if rt.clientSetup != nil {
   719  				rt.clientSetup(client)
   720  			}
   721  			apiEndpoint := kubeadmapi.APIEndpoint{}
   722  			err := getAPIEndpointFromPodAnnotation(client, rt.nodeName, &apiEndpoint,
   723  				time.Millisecond*10, time.Millisecond*100)
   724  			if err != nil && !rt.expectedErr {
   725  				t.Errorf("got error %v, but wasn't expecting any error", err)
   726  				return
   727  			} else if err == nil && rt.expectedErr {
   728  				t.Error("didn't get any error; but was expecting an error")
   729  				return
   730  			} else if err != nil && rt.expectedErr {
   731  				return
   732  			}
   733  			if !reflect.DeepEqual(apiEndpoint, rt.expectedEndpoint) {
   734  				t.Errorf("expected API endpoint: %v; got %v", rt.expectedEndpoint, apiEndpoint)
   735  			}
   736  		})
   737  	}
   738  }
   739  
   740  func TestGetRawAPIEndpointFromPodAnnotationWithoutRetry(t *testing.T) {
   741  	var tests = []struct {
   742  		name             string
   743  		nodeName         string
   744  		pods             []testresources.FakeStaticPod
   745  		clientSetup      func(*clientsetfake.Clientset)
   746  		expectedEndpoint string
   747  		expectedErr      bool
   748  	}{
   749  		{
   750  			name:        "no pods",
   751  			nodeName:    nodeName,
   752  			expectedErr: true,
   753  		},
   754  		{
   755  			name:     "exactly one pod with annotation",
   756  			nodeName: nodeName,
   757  			pods: []testresources.FakeStaticPod{
   758  				{
   759  					Component:   kubeadmconstants.KubeAPIServer,
   760  					Annotations: map[string]string{kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:1234"},
   761  				},
   762  			},
   763  			expectedEndpoint: "1.2.3.4:1234",
   764  		},
   765  		{
   766  			name:     "two pods: one with annotation, one missing annotation",
   767  			nodeName: nodeName,
   768  			pods: []testresources.FakeStaticPod{
   769  				{
   770  					Component:   kubeadmconstants.KubeAPIServer,
   771  					Annotations: map[string]string{kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:1234"},
   772  				},
   773  				{
   774  					Component: kubeadmconstants.KubeAPIServer,
   775  				},
   776  			},
   777  			expectedErr: true,
   778  		},
   779  		{
   780  			name:     "two pods: different annotations",
   781  			nodeName: nodeName,
   782  			pods: []testresources.FakeStaticPod{
   783  				{
   784  					Component:   kubeadmconstants.KubeAPIServer,
   785  					Annotations: map[string]string{kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:1234"},
   786  				},
   787  				{
   788  					Component:   kubeadmconstants.KubeAPIServer,
   789  					Annotations: map[string]string{kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.5:1234"},
   790  				},
   791  			},
   792  			expectedErr: true,
   793  		},
   794  		{
   795  			name:     "two pods: both missing annotation",
   796  			nodeName: nodeName,
   797  			pods: []testresources.FakeStaticPod{
   798  				{
   799  					Component: kubeadmconstants.KubeAPIServer,
   800  				},
   801  				{
   802  					Component: kubeadmconstants.KubeAPIServer,
   803  				},
   804  			},
   805  			expectedErr: true,
   806  		},
   807  		{
   808  			name:     "exactly one pod with annotation; request fails",
   809  			nodeName: nodeName,
   810  			pods: []testresources.FakeStaticPod{
   811  				{
   812  					Component:   kubeadmconstants.KubeAPIServer,
   813  					Annotations: map[string]string{kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:1234"},
   814  				},
   815  			},
   816  			clientSetup: func(clientset *clientsetfake.Clientset) {
   817  				clientset.PrependReactor("list", "pods", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
   818  					return true, nil, apierrors.NewInternalError(errors.New("API server down"))
   819  				})
   820  			},
   821  			expectedErr: true,
   822  		},
   823  	}
   824  	for _, rt := range tests {
   825  		t.Run(rt.name, func(t *testing.T) {
   826  			client := clientsetfake.NewSimpleClientset()
   827  			for i, pod := range rt.pods {
   828  				pod.NodeName = rt.nodeName
   829  				if err := pod.CreateWithPodSuffix(client, strconv.Itoa(i)); err != nil {
   830  					t.Errorf("error setting up test creating pod for node %q", pod.NodeName)
   831  					return
   832  				}
   833  			}
   834  			if rt.clientSetup != nil {
   835  				rt.clientSetup(client)
   836  			}
   837  			endpoint, err := getRawAPIEndpointFromPodAnnotationWithoutRetry(context.Background(), client, rt.nodeName)
   838  			if err != nil && !rt.expectedErr {
   839  				t.Errorf("got error %v, but wasn't expecting any error", err)
   840  				return
   841  			} else if err == nil && rt.expectedErr {
   842  				t.Error("didn't get any error; but was expecting an error")
   843  				return
   844  			} else if err != nil && rt.expectedErr {
   845  				return
   846  			}
   847  			if endpoint != rt.expectedEndpoint {
   848  				t.Errorf("expected API endpoint: %v; got: %v", rt.expectedEndpoint, endpoint)
   849  			}
   850  		})
   851  	}
   852  }
   853  

View as plain text