cluster_test.go 32 KB

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