...

Source file src/k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod/utils_test.go

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

     1  /*
     2  Copyright 2017 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 staticpod
    18  
    19  import (
    20  	"io"
    21  	"os"
    22  	"path/filepath"
    23  	"reflect"
    24  	"sort"
    25  	"strconv"
    26  	"strings"
    27  	"testing"
    28  
    29  	v1 "k8s.io/api/core/v1"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  
    32  	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
    33  	kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
    34  	testutil "k8s.io/kubernetes/cmd/kubeadm/test"
    35  )
    36  
    37  func TestComponentResources(t *testing.T) {
    38  	a := ComponentResources("250m")
    39  	if a.Requests == nil {
    40  		t.Errorf(
    41  			"failed componentResources, return value was nil",
    42  		)
    43  	}
    44  }
    45  
    46  func TestGetAPIServerProbeAddress(t *testing.T) {
    47  	tests := []struct {
    48  		desc     string
    49  		endpoint *kubeadmapi.APIEndpoint
    50  		expected string
    51  	}{
    52  		{
    53  			desc:     "nil endpoint returns 127.0.0.1",
    54  			expected: "127.0.0.1",
    55  		},
    56  		{
    57  			desc:     "empty AdvertiseAddress endpoint returns 127.0.0.1",
    58  			endpoint: &kubeadmapi.APIEndpoint{},
    59  			expected: "127.0.0.1",
    60  		},
    61  		{
    62  			desc: "filled in AdvertiseAddress endpoint returns it",
    63  			endpoint: &kubeadmapi.APIEndpoint{
    64  				AdvertiseAddress: "10.10.10.10",
    65  			},
    66  			expected: "10.10.10.10",
    67  		},
    68  		{
    69  			desc: "filled in ipv6 AdvertiseAddress endpoint returns it",
    70  			endpoint: &kubeadmapi.APIEndpoint{
    71  				AdvertiseAddress: "2001:abcd:bcda::1",
    72  			},
    73  			expected: "2001:abcd:bcda::1",
    74  		},
    75  		{
    76  			desc: "filled in 0.0.0.0 AdvertiseAddress endpoint returns empty",
    77  			endpoint: &kubeadmapi.APIEndpoint{
    78  				AdvertiseAddress: "0.0.0.0",
    79  			},
    80  			expected: "",
    81  		},
    82  		{
    83  			desc: "filled in :: AdvertiseAddress endpoint returns empty",
    84  			endpoint: &kubeadmapi.APIEndpoint{
    85  				AdvertiseAddress: "::",
    86  			},
    87  			expected: "",
    88  		},
    89  	}
    90  
    91  	for _, test := range tests {
    92  		t.Run(test.desc, func(t *testing.T) {
    93  			actual := GetAPIServerProbeAddress(test.endpoint)
    94  			if actual != test.expected {
    95  				t.Errorf("Unexpected result from GetAPIServerProbeAddress:\n\texpected: %s\n\tactual: %s", test.expected, actual)
    96  			}
    97  		})
    98  	}
    99  }
   100  
   101  func TestGetControllerManagerProbeAddress(t *testing.T) {
   102  	tests := []struct {
   103  		desc     string
   104  		cfg      *kubeadmapi.ClusterConfiguration
   105  		expected string
   106  	}{
   107  		{
   108  			desc: "no controller manager extra args leads to 127.0.0.1 being used",
   109  			cfg: &kubeadmapi.ClusterConfiguration{
   110  				ControllerManager: kubeadmapi.ControlPlaneComponent{
   111  					ExtraArgs: []kubeadmapi.Arg{},
   112  				},
   113  			},
   114  			expected: "127.0.0.1",
   115  		},
   116  		{
   117  			desc: "setting controller manager extra address arg to something acknowledges it",
   118  			cfg: &kubeadmapi.ClusterConfiguration{
   119  				ControllerManager: kubeadmapi.ControlPlaneComponent{
   120  					ExtraArgs: []kubeadmapi.Arg{
   121  						{Name: kubeControllerManagerBindAddressArg, Value: "10.10.10.10"},
   122  					},
   123  				},
   124  			},
   125  			expected: "10.10.10.10",
   126  		},
   127  		{
   128  			desc: "setting controller manager extra ipv6 address arg to something acknowledges it",
   129  			cfg: &kubeadmapi.ClusterConfiguration{
   130  				ControllerManager: kubeadmapi.ControlPlaneComponent{
   131  					ExtraArgs: []kubeadmapi.Arg{
   132  						{Name: kubeControllerManagerBindAddressArg, Value: "2001:abcd:bcda::1"},
   133  					},
   134  				},
   135  			},
   136  			expected: "2001:abcd:bcda::1",
   137  		},
   138  		{
   139  			desc: "setting controller manager extra address arg to 0.0.0.0 returns empty",
   140  			cfg: &kubeadmapi.ClusterConfiguration{
   141  				ControllerManager: kubeadmapi.ControlPlaneComponent{
   142  					ExtraArgs: []kubeadmapi.Arg{
   143  						{Name: kubeControllerManagerBindAddressArg, Value: "0.0.0.0"},
   144  					},
   145  				},
   146  			},
   147  			expected: "",
   148  		},
   149  		{
   150  			desc: "setting controller manager extra ipv6 address arg to :: returns empty",
   151  			cfg: &kubeadmapi.ClusterConfiguration{
   152  				ControllerManager: kubeadmapi.ControlPlaneComponent{
   153  					ExtraArgs: []kubeadmapi.Arg{
   154  						{Name: kubeControllerManagerBindAddressArg, Value: "::"},
   155  					},
   156  				},
   157  			},
   158  			expected: "",
   159  		},
   160  	}
   161  
   162  	for _, test := range tests {
   163  		t.Run(test.desc, func(t *testing.T) {
   164  			actual := GetControllerManagerProbeAddress(test.cfg)
   165  			if actual != test.expected {
   166  				t.Errorf("Unexpected result from GetControllerManagerProbeAddress:\n\texpected: %s\n\tactual: %s", test.expected, actual)
   167  			}
   168  		})
   169  	}
   170  }
   171  
   172  func TestGetSchedulerProbeAddress(t *testing.T) {
   173  	tests := []struct {
   174  		desc     string
   175  		cfg      *kubeadmapi.ClusterConfiguration
   176  		expected string
   177  	}{
   178  		{
   179  			desc: "no scheduler extra args leads to 127.0.0.1 being used",
   180  			cfg: &kubeadmapi.ClusterConfiguration{
   181  				Scheduler: kubeadmapi.ControlPlaneComponent{
   182  					ExtraArgs: []kubeadmapi.Arg{},
   183  				},
   184  			},
   185  			expected: "127.0.0.1",
   186  		},
   187  		{
   188  			desc: "setting scheduler extra address arg to something acknowledges it",
   189  			cfg: &kubeadmapi.ClusterConfiguration{
   190  				Scheduler: kubeadmapi.ControlPlaneComponent{
   191  					ExtraArgs: []kubeadmapi.Arg{
   192  						{Name: kubeSchedulerBindAddressArg, Value: "10.10.10.10"},
   193  					},
   194  				},
   195  			},
   196  			expected: "10.10.10.10",
   197  		},
   198  		{
   199  			desc: "setting scheduler extra ipv6 address arg to something acknowledges it",
   200  			cfg: &kubeadmapi.ClusterConfiguration{
   201  				Scheduler: kubeadmapi.ControlPlaneComponent{
   202  					ExtraArgs: []kubeadmapi.Arg{
   203  						{Name: kubeSchedulerBindAddressArg, Value: "2001:abcd:bcda::1"},
   204  					},
   205  				},
   206  			},
   207  			expected: "2001:abcd:bcda::1",
   208  		},
   209  		{
   210  			desc: "setting scheduler extra ipv6 address arg to 0.0.0.0 returns empty",
   211  			cfg: &kubeadmapi.ClusterConfiguration{
   212  				Scheduler: kubeadmapi.ControlPlaneComponent{
   213  					ExtraArgs: []kubeadmapi.Arg{
   214  						{Name: kubeSchedulerBindAddressArg, Value: "0.0.0.0"},
   215  					},
   216  				},
   217  			},
   218  			expected: "",
   219  		},
   220  		{
   221  			desc: "setting scheduler extra ipv6 address arg to :: returns empty",
   222  			cfg: &kubeadmapi.ClusterConfiguration{
   223  				Scheduler: kubeadmapi.ControlPlaneComponent{
   224  					ExtraArgs: []kubeadmapi.Arg{
   225  						{Name: kubeSchedulerBindAddressArg, Value: "::"},
   226  					},
   227  				},
   228  			},
   229  			expected: "",
   230  		},
   231  	}
   232  
   233  	for _, test := range tests {
   234  		t.Run(test.desc, func(t *testing.T) {
   235  			actual := GetSchedulerProbeAddress(test.cfg)
   236  			if actual != test.expected {
   237  				t.Errorf("Unexpected result from GetSchedulerProbeAddress:\n\texpected: %s\n\tactual: %s", test.expected, actual)
   238  			}
   239  		})
   240  	}
   241  }
   242  func TestGetEtcdProbeEndpoint(t *testing.T) {
   243  	var tests = []struct {
   244  		name             string
   245  		cfg              *kubeadmapi.Etcd
   246  		isIPv6           bool
   247  		expectedHostname string
   248  		expectedPort     int32
   249  		expectedScheme   v1.URIScheme
   250  	}{
   251  		{
   252  			name: "etcd probe URL from two URLs",
   253  			cfg: &kubeadmapi.Etcd{
   254  				Local: &kubeadmapi.LocalEtcd{
   255  					ExtraArgs: []kubeadmapi.Arg{
   256  						{Name: "listen-metrics-urls", Value: "https://1.2.3.4:1234,https://4.3.2.1:2381"},
   257  					},
   258  				},
   259  			},
   260  			isIPv6:           false,
   261  			expectedHostname: "1.2.3.4",
   262  			expectedPort:     1234,
   263  			expectedScheme:   v1.URISchemeHTTPS,
   264  		},
   265  		{
   266  			name: "etcd probe URL with HTTP scheme",
   267  			cfg: &kubeadmapi.Etcd{
   268  				Local: &kubeadmapi.LocalEtcd{
   269  					ExtraArgs: []kubeadmapi.Arg{
   270  						{Name: "listen-metrics-urls", Value: "http://1.2.3.4:1234"},
   271  					},
   272  				},
   273  			},
   274  			isIPv6:           false,
   275  			expectedHostname: "1.2.3.4",
   276  			expectedPort:     1234,
   277  			expectedScheme:   v1.URISchemeHTTP,
   278  		},
   279  		{
   280  			name: "etcd probe URL without scheme should result in defaults",
   281  			cfg: &kubeadmapi.Etcd{
   282  				Local: &kubeadmapi.LocalEtcd{
   283  					ExtraArgs: []kubeadmapi.Arg{
   284  						{Name: "listen-metrics-urls", Value: "1.2.3.4"},
   285  					},
   286  				},
   287  			},
   288  			isIPv6:           false,
   289  			expectedHostname: "127.0.0.1",
   290  			expectedPort:     kubeadmconstants.EtcdMetricsPort,
   291  			expectedScheme:   v1.URISchemeHTTP,
   292  		},
   293  		{
   294  			name: "etcd probe URL without port",
   295  			cfg: &kubeadmapi.Etcd{
   296  				Local: &kubeadmapi.LocalEtcd{
   297  					ExtraArgs: []kubeadmapi.Arg{
   298  						{Name: "listen-metrics-urls", Value: "https://1.2.3.4"},
   299  					},
   300  				},
   301  			},
   302  			isIPv6:           false,
   303  			expectedHostname: "1.2.3.4",
   304  			expectedPort:     kubeadmconstants.EtcdMetricsPort,
   305  			expectedScheme:   v1.URISchemeHTTPS,
   306  		},
   307  		{
   308  			name: "etcd probe URL from two IPv6 URLs",
   309  			cfg: &kubeadmapi.Etcd{
   310  				Local: &kubeadmapi.LocalEtcd{
   311  					ExtraArgs: []kubeadmapi.Arg{
   312  						{Name: "listen-metrics-urls", Value: "https://[2001:abcd:bcda::1]:1234,https://[2001:abcd:bcda::2]:2381"},
   313  					},
   314  				},
   315  			},
   316  			isIPv6:           true,
   317  			expectedHostname: "2001:abcd:bcda::1",
   318  			expectedPort:     1234,
   319  			expectedScheme:   v1.URISchemeHTTPS,
   320  		},
   321  		{
   322  			name: "etcd probe localhost IPv6 URL with HTTP scheme",
   323  			cfg: &kubeadmapi.Etcd{
   324  				Local: &kubeadmapi.LocalEtcd{
   325  					ExtraArgs: []kubeadmapi.Arg{
   326  						{Name: "listen-metrics-urls", Value: "http://[::1]:1234"},
   327  					},
   328  				},
   329  			},
   330  			isIPv6:           true,
   331  			expectedHostname: "::1",
   332  			expectedPort:     1234,
   333  			expectedScheme:   v1.URISchemeHTTP,
   334  		},
   335  		{
   336  			name: "etcd probe IPv6 URL with HTTP scheme",
   337  			cfg: &kubeadmapi.Etcd{
   338  				Local: &kubeadmapi.LocalEtcd{
   339  					ExtraArgs: []kubeadmapi.Arg{
   340  						{Name: "listen-metrics-urls", Value: "http://[2001:abcd:bcda::1]:1234"},
   341  					},
   342  				},
   343  			},
   344  			isIPv6:           true,
   345  			expectedHostname: "2001:abcd:bcda::1",
   346  			expectedPort:     1234,
   347  			expectedScheme:   v1.URISchemeHTTP,
   348  		},
   349  		{
   350  			name: "etcd probe IPv6 URL without port",
   351  			cfg: &kubeadmapi.Etcd{
   352  				Local: &kubeadmapi.LocalEtcd{
   353  					ExtraArgs: []kubeadmapi.Arg{
   354  						{Name: "listen-metrics-urls", Value: "https://[2001:abcd:bcda::1]"},
   355  					},
   356  				},
   357  			},
   358  			isIPv6:           true,
   359  			expectedHostname: "2001:abcd:bcda::1",
   360  			expectedPort:     kubeadmconstants.EtcdMetricsPort,
   361  			expectedScheme:   v1.URISchemeHTTPS,
   362  		},
   363  		{
   364  			name: "etcd probe URL from defaults",
   365  			cfg: &kubeadmapi.Etcd{
   366  				Local: &kubeadmapi.LocalEtcd{},
   367  			},
   368  			isIPv6:           false,
   369  			expectedHostname: "127.0.0.1",
   370  			expectedPort:     kubeadmconstants.EtcdMetricsPort,
   371  			expectedScheme:   v1.URISchemeHTTP,
   372  		},
   373  		{
   374  			name: "etcd probe URL from defaults if IPv6",
   375  			cfg: &kubeadmapi.Etcd{
   376  				Local: &kubeadmapi.LocalEtcd{},
   377  			},
   378  			isIPv6:           true,
   379  			expectedHostname: "::1",
   380  			expectedPort:     kubeadmconstants.EtcdMetricsPort,
   381  			expectedScheme:   v1.URISchemeHTTP,
   382  		},
   383  	}
   384  	for _, rt := range tests {
   385  		t.Run(rt.name, func(t *testing.T) {
   386  			hostname, port, scheme := GetEtcdProbeEndpoint(rt.cfg, rt.isIPv6)
   387  			if hostname != rt.expectedHostname {
   388  				t.Errorf("%q test case failed:\n\texpected hostname: %s\n\tgot: %s",
   389  					rt.name, rt.expectedHostname, hostname)
   390  			}
   391  			if port != rt.expectedPort {
   392  				t.Errorf("%q test case failed:\n\texpected port: %d\n\tgot: %d",
   393  					rt.name, rt.expectedPort, port)
   394  			}
   395  			if scheme != rt.expectedScheme {
   396  				t.Errorf("%q test case failed:\n\texpected scheme: %v\n\tgot: %v",
   397  					rt.name, rt.expectedScheme, scheme)
   398  			}
   399  		})
   400  	}
   401  }
   402  
   403  func TestComponentPod(t *testing.T) {
   404  	// priority value for system-node-critical class
   405  	priority := int32(2000001000)
   406  	var tests = []struct {
   407  		name     string
   408  		expected v1.Pod
   409  	}{
   410  		{
   411  			name: "foo",
   412  			expected: v1.Pod{
   413  				TypeMeta: metav1.TypeMeta{
   414  					APIVersion: "v1",
   415  					Kind:       "Pod",
   416  				},
   417  				ObjectMeta: metav1.ObjectMeta{
   418  					Name:      "foo",
   419  					Namespace: "kube-system",
   420  					Labels:    map[string]string{"component": "foo", "tier": "control-plane"},
   421  				},
   422  				Spec: v1.PodSpec{
   423  					SecurityContext: &v1.PodSecurityContext{
   424  						SeccompProfile: &v1.SeccompProfile{
   425  							Type: v1.SeccompProfileTypeRuntimeDefault,
   426  						},
   427  					},
   428  					Containers: []v1.Container{
   429  						{
   430  							Name: "foo",
   431  						},
   432  					},
   433  					Priority:          &priority,
   434  					PriorityClassName: "system-node-critical",
   435  					HostNetwork:       true,
   436  					Volumes:           []v1.Volume{},
   437  				},
   438  			},
   439  		},
   440  	}
   441  
   442  	for _, rt := range tests {
   443  		t.Run(rt.name, func(t *testing.T) {
   444  			c := v1.Container{Name: rt.name}
   445  			actual := ComponentPod(c, map[string]v1.Volume{}, nil)
   446  			if !reflect.DeepEqual(rt.expected, actual) {
   447  				t.Errorf(
   448  					"failed componentPod:\n\texpected: %v\n\t  actual: %v",
   449  					rt.expected,
   450  					actual,
   451  				)
   452  			}
   453  		})
   454  	}
   455  }
   456  
   457  func TestNewVolume(t *testing.T) {
   458  	hostPathDirectoryOrCreate := v1.HostPathDirectoryOrCreate
   459  	var tests = []struct {
   460  		name     string
   461  		path     string
   462  		expected v1.Volume
   463  		pathType *v1.HostPathType
   464  	}{
   465  		{
   466  			name: "foo",
   467  			path: "/etc/foo",
   468  			expected: v1.Volume{
   469  				Name: "foo",
   470  				VolumeSource: v1.VolumeSource{
   471  					HostPath: &v1.HostPathVolumeSource{
   472  						Path: "/etc/foo",
   473  						Type: &hostPathDirectoryOrCreate,
   474  					},
   475  				},
   476  			},
   477  			pathType: &hostPathDirectoryOrCreate,
   478  		},
   479  	}
   480  
   481  	for _, rt := range tests {
   482  		t.Run(rt.name, func(t *testing.T) {
   483  			actual := NewVolume(rt.name, rt.path, rt.pathType)
   484  			if !reflect.DeepEqual(actual, rt.expected) {
   485  				t.Errorf(
   486  					"failed newVolume:\n\texpected: %v\n\t  actual: %v",
   487  					rt.expected,
   488  					actual,
   489  				)
   490  			}
   491  		})
   492  	}
   493  }
   494  
   495  func TestNewVolumeMount(t *testing.T) {
   496  	var tests = []struct {
   497  		name     string
   498  		path     string
   499  		ro       bool
   500  		expected v1.VolumeMount
   501  	}{
   502  		{
   503  			name: "foo",
   504  			path: "/etc/foo",
   505  			ro:   false,
   506  			expected: v1.VolumeMount{
   507  				Name:      "foo",
   508  				MountPath: "/etc/foo",
   509  				ReadOnly:  false,
   510  			},
   511  		},
   512  		{
   513  			name: "bar",
   514  			path: "/etc/foo/bar",
   515  			ro:   true,
   516  			expected: v1.VolumeMount{
   517  				Name:      "bar",
   518  				MountPath: "/etc/foo/bar",
   519  				ReadOnly:  true,
   520  			},
   521  		},
   522  	}
   523  
   524  	for _, rt := range tests {
   525  		t.Run(rt.name, func(t *testing.T) {
   526  			actual := NewVolumeMount(rt.name, rt.path, rt.ro)
   527  			if !reflect.DeepEqual(actual, rt.expected) {
   528  				t.Errorf(
   529  					"failed newVolumeMount:\n\texpected: %v\n\t  actual: %v",
   530  					rt.expected,
   531  					actual,
   532  				)
   533  			}
   534  		})
   535  	}
   536  }
   537  func TestVolumeMapToSlice(t *testing.T) {
   538  	testVolumes := map[string]v1.Volume{
   539  		"foo": {
   540  			Name: "foo",
   541  		},
   542  		"bar": {
   543  			Name: "bar",
   544  		},
   545  	}
   546  	volumeSlice := VolumeMapToSlice(testVolumes)
   547  	if len(volumeSlice) != 2 {
   548  		t.Errorf("Expected slice length of 1, got %d", len(volumeSlice))
   549  	}
   550  	if volumeSlice[0].Name != "bar" {
   551  		t.Errorf("Expected first volume name \"bar\", got %s", volumeSlice[0].Name)
   552  	}
   553  	if volumeSlice[1].Name != "foo" {
   554  		t.Errorf("Expected second volume name \"foo\", got %s", volumeSlice[1].Name)
   555  	}
   556  }
   557  
   558  func TestVolumeMountMapToSlice(t *testing.T) {
   559  	testVolumeMounts := map[string]v1.VolumeMount{
   560  		"foo": {
   561  			Name: "foo",
   562  		},
   563  		"bar": {
   564  			Name: "bar",
   565  		},
   566  	}
   567  	volumeMountSlice := VolumeMountMapToSlice(testVolumeMounts)
   568  	if len(volumeMountSlice) != 2 {
   569  		t.Errorf("Expected slice length of 1, got %d", len(volumeMountSlice))
   570  	}
   571  	if volumeMountSlice[0].Name != "bar" {
   572  		t.Errorf("Expected first volume mount name \"bar\", got %s", volumeMountSlice[0].Name)
   573  	}
   574  	if volumeMountSlice[1].Name != "foo" {
   575  		t.Errorf("Expected second volume name \"foo\", got %s", volumeMountSlice[1].Name)
   576  	}
   577  }
   578  
   579  func TestGetExtraParameters(t *testing.T) {
   580  	var tests = []struct {
   581  		name      string
   582  		overrides map[string]string
   583  		defaults  map[string]string
   584  		expected  []string
   585  	}{
   586  		{
   587  			name: "with admission-control default NamespaceLifecycle",
   588  			overrides: map[string]string{
   589  				"admission-control": "NamespaceLifecycle,LimitRanger",
   590  			},
   591  			defaults: map[string]string{
   592  				"admission-control": "NamespaceLifecycle",
   593  				"allow-privileged":  "true",
   594  			},
   595  			expected: []string{
   596  				"--admission-control=NamespaceLifecycle,LimitRanger",
   597  				"--allow-privileged=true",
   598  			},
   599  		},
   600  		{
   601  			name: "without admission-control default",
   602  			overrides: map[string]string{
   603  				"admission-control": "NamespaceLifecycle,LimitRanger",
   604  			},
   605  			defaults: map[string]string{
   606  				"allow-privileged": "true",
   607  			},
   608  			expected: []string{
   609  				"--admission-control=NamespaceLifecycle,LimitRanger",
   610  				"--allow-privileged=true",
   611  			},
   612  		},
   613  	}
   614  
   615  	for _, rt := range tests {
   616  		t.Run(rt.name, func(t *testing.T) {
   617  			actual := GetExtraParameters(rt.overrides, rt.defaults)
   618  			sort.Strings(actual)
   619  			sort.Strings(rt.expected)
   620  			if !reflect.DeepEqual(actual, rt.expected) {
   621  				t.Errorf("failed getExtraParameters:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
   622  			}
   623  		})
   624  	}
   625  }
   626  
   627  const (
   628  	validPod = `
   629  apiVersion: v1
   630  kind: Pod
   631  metadata:
   632    labels:
   633      component: etcd
   634      tier: control-plane
   635    name: etcd
   636    namespace: kube-system
   637  spec:
   638    containers:
   639    - image: gcr.io/google_containers/etcd-amd64:3.1.11
   640  status: {}
   641  `
   642  	validPodWithDifferentFieldsOrder = `
   643  apiVersion: v1
   644  kind: Pod
   645  metadata:
   646    labels:
   647      tier: control-plane
   648      component: etcd
   649    name: etcd
   650    namespace: kube-system
   651  spec:
   652    containers:
   653    - image: gcr.io/google_containers/etcd-amd64:3.1.11
   654  status: {}
   655  `
   656  	invalidWithDefaultFields = `
   657  apiVersion: v1
   658  kind: Pod
   659  metadata:
   660    labels:
   661      tier: control-plane
   662      component: etcd
   663    name: etcd
   664    namespace: kube-system
   665  spec:
   666    containers:
   667    - image: gcr.io/google_containers/etcd-amd64:3.1.11
   668    restartPolicy: "Always"
   669  status: {}
   670  `
   671  
   672  	validPod2 = `
   673  apiVersion: v1
   674  kind: Pod
   675  metadata:
   676    labels:
   677      component: etcd
   678      tier: control-plane
   679    name: etcd
   680    namespace: kube-system
   681  spec:
   682    containers:
   683    - image: gcr.io/google_containers/etcd-amd64:3.1.12
   684  status: {}
   685  `
   686  
   687  	invalidPod = `---{ broken yaml @@@`
   688  )
   689  
   690  func TestReadStaticPodFromDisk(t *testing.T) {
   691  	tests := []struct {
   692  		description   string
   693  		podYaml       string
   694  		expectErr     bool
   695  		writeManifest bool
   696  	}{
   697  		{
   698  			description:   "valid pod is marshaled",
   699  			podYaml:       validPod,
   700  			writeManifest: true,
   701  			expectErr:     false,
   702  		},
   703  		{
   704  			description:   "invalid pod fails to unmarshal",
   705  			podYaml:       invalidPod,
   706  			writeManifest: true,
   707  			expectErr:     true,
   708  		},
   709  		{
   710  			description:   "non-existent file returns error",
   711  			podYaml:       ``,
   712  			writeManifest: false,
   713  			expectErr:     true,
   714  		},
   715  	}
   716  
   717  	for _, rt := range tests {
   718  		t.Run(rt.description, func(t *testing.T) {
   719  			tmpdir := testutil.SetupTempDir(t)
   720  			defer os.RemoveAll(tmpdir)
   721  
   722  			manifestPath := filepath.Join(tmpdir, "pod.yaml")
   723  			if rt.writeManifest {
   724  				err := os.WriteFile(manifestPath, []byte(rt.podYaml), 0644)
   725  				if err != nil {
   726  					t.Fatalf("Failed to write pod manifest\n%s\n\tfatal error: %v", rt.description, err)
   727  				}
   728  			}
   729  
   730  			_, actualErr := ReadStaticPodFromDisk(manifestPath)
   731  			if (actualErr != nil) != rt.expectErr {
   732  				t.Errorf(
   733  					"ReadStaticPodFromDisk failed\n%s\n\texpected error: %t\n\tgot: %t\n\tactual error: %v",
   734  					rt.description,
   735  					rt.expectErr,
   736  					(actualErr != nil),
   737  					actualErr,
   738  				)
   739  			}
   740  		})
   741  	}
   742  }
   743  
   744  func TestManifestFilesAreEqual(t *testing.T) {
   745  	var tests = []struct {
   746  		description    string
   747  		podYamls       []string
   748  		expectedResult bool
   749  		expectedDiff   string
   750  		expectErr      bool
   751  	}{
   752  		{
   753  			description:    "manifests are equal",
   754  			podYamls:       []string{validPod, validPod},
   755  			expectedResult: true,
   756  			expectErr:      false,
   757  		},
   758  		{
   759  			description:    "manifests are equal, ignore different fields order",
   760  			podYamls:       []string{validPod, validPodWithDifferentFieldsOrder},
   761  			expectedResult: true,
   762  			expectErr:      false,
   763  		},
   764  		{
   765  			description:    "manifests are not equal",
   766  			podYamls:       []string{validPod, validPod2},
   767  			expectedResult: false,
   768  			expectErr:      false,
   769  			expectedDiff: `@@ -12 +12 @@
   770  -  - image: gcr.io/google_containers/etcd-amd64:3.1.11
   771  +  - image: gcr.io/google_containers/etcd-amd64:3.1.12
   772  `,
   773  		},
   774  		{
   775  			description:    "manifests are not equal for adding new defaults",
   776  			podYamls:       []string{validPod, invalidWithDefaultFields},
   777  			expectedResult: false,
   778  			expectErr:      false,
   779  			expectedDiff: `@@ -14,0 +15 @@
   780  +  restartPolicy: Always
   781  `,
   782  		},
   783  		{
   784  			description:    "first manifest doesn't exist",
   785  			podYamls:       []string{validPod, ""},
   786  			expectedResult: false,
   787  			expectErr:      true,
   788  		},
   789  		{
   790  			description:    "second manifest doesn't exist",
   791  			podYamls:       []string{"", validPod},
   792  			expectedResult: false,
   793  			expectErr:      true,
   794  		},
   795  	}
   796  
   797  	for _, rt := range tests {
   798  		t.Run(rt.description, func(t *testing.T) {
   799  			tmpdir := testutil.SetupTempDir(t)
   800  			defer os.RemoveAll(tmpdir)
   801  
   802  			// write 2 manifests
   803  			for i := 0; i < 2; i++ {
   804  				if rt.podYamls[i] != "" {
   805  					manifestPath := filepath.Join(tmpdir, strconv.Itoa(i)+".yaml")
   806  					err := os.WriteFile(manifestPath, []byte(rt.podYamls[i]), 0644)
   807  					if err != nil {
   808  						t.Fatalf("Failed to write manifest file\n%s\n\tfatal error: %v", rt.description, err)
   809  					}
   810  				}
   811  			}
   812  
   813  			// compare them
   814  			result, diff, actualErr := ManifestFilesAreEqual(filepath.Join(tmpdir, "0.yaml"), filepath.Join(tmpdir, "1.yaml"))
   815  			if result != rt.expectedResult {
   816  				t.Errorf(
   817  					"ManifestFilesAreEqual failed\n%s\nexpected result: %t\nactual result: %t",
   818  					rt.description,
   819  					rt.expectedResult,
   820  					result,
   821  				)
   822  			}
   823  			if (actualErr != nil) != rt.expectErr {
   824  				t.Errorf(
   825  					"ManifestFilesAreEqual failed\n%s\n\texpected error: %t\n\tgot: %t\n\tactual error: %v",
   826  					rt.description,
   827  					rt.expectErr,
   828  					(actualErr != nil),
   829  					actualErr,
   830  				)
   831  			}
   832  			if !strings.Contains(diff, rt.expectedDiff) {
   833  				t.Errorf(
   834  					"ManifestFilesAreEqual diff doesn't expected\n%s\n\texpected diff: %s\n\tactual diff: %s",
   835  					rt.description,
   836  					rt.expectedDiff,
   837  					diff,
   838  				)
   839  			}
   840  		})
   841  	}
   842  }
   843  
   844  func TestPatchStaticPod(t *testing.T) {
   845  	type file struct {
   846  		name string
   847  		data string
   848  	}
   849  
   850  	tests := []struct {
   851  		name          string
   852  		files         []*file
   853  		pod           *v1.Pod
   854  		expectedPod   *v1.Pod
   855  		expectedError bool
   856  	}{
   857  		{
   858  			name: "valid: patch a kube-apiserver target using a couple of ordered patches",
   859  			pod: &v1.Pod{
   860  				ObjectMeta: metav1.ObjectMeta{
   861  					Name:      "kube-apiserver",
   862  					Namespace: "foo",
   863  				},
   864  			},
   865  			expectedPod: &v1.Pod{
   866  				ObjectMeta: metav1.ObjectMeta{
   867  					Name:      "kube-apiserver",
   868  					Namespace: "bar2",
   869  				},
   870  			},
   871  			files: []*file{
   872  				{
   873  					name: "kube-apiserver1+merge.json",
   874  					data: `{"metadata":{"namespace":"bar2"}}`,
   875  				},
   876  				{
   877  					name: "kube-apiserver0+json.json",
   878  					data: `[{"op": "replace", "path": "/metadata/namespace", "value": "bar1"}]`,
   879  				},
   880  			},
   881  		},
   882  		{
   883  			name: "invalid: unknown patch target name",
   884  			pod: &v1.Pod{
   885  				ObjectMeta: metav1.ObjectMeta{
   886  					Name:      "foo",
   887  					Namespace: "bar",
   888  				},
   889  			},
   890  			expectedError: true,
   891  		},
   892  	}
   893  
   894  	for _, tc := range tests {
   895  		t.Run(tc.name, func(t *testing.T) {
   896  			tempDir, err := os.MkdirTemp("", "patch-files")
   897  			if err != nil {
   898  				t.Fatal(err)
   899  			}
   900  			defer os.RemoveAll(tempDir)
   901  
   902  			for _, file := range tc.files {
   903  				filePath := filepath.Join(tempDir, file.name)
   904  				err := os.WriteFile(filePath, []byte(file.data), 0644)
   905  				if err != nil {
   906  					t.Fatalf("could not write temporary file %q", filePath)
   907  				}
   908  			}
   909  
   910  			pod, err := PatchStaticPod(tc.pod, tempDir, io.Discard)
   911  			if (err != nil) != tc.expectedError {
   912  				t.Fatalf("expected error: %v, got: %v, error: %v", tc.expectedError, (err != nil), err)
   913  			}
   914  			if err != nil {
   915  				return
   916  			}
   917  
   918  			if tc.expectedPod.String() != pod.String() {
   919  				t.Fatalf("expected object:\n%s\ngot:\n%s", tc.expectedPod.String(), pod.String())
   920  			}
   921  		})
   922  	}
   923  }
   924  

View as plain text