config_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  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 cmd
  14. import (
  15. "bytes"
  16. "fmt"
  17. "io"
  18. "io/ioutil"
  19. "os"
  20. "path/filepath"
  21. "reflect"
  22. "sort"
  23. "strings"
  24. "testing"
  25. "text/template"
  26. "github.com/lithammer/dedent"
  27. "github.com/spf13/cobra"
  28. kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2"
  29. outputapischeme "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/scheme"
  30. "k8s.io/kubernetes/cmd/kubeadm/app/constants"
  31. kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
  32. configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
  33. "k8s.io/kubernetes/cmd/kubeadm/app/util/output"
  34. utilruntime "k8s.io/kubernetes/cmd/kubeadm/app/util/runtime"
  35. "k8s.io/utils/exec"
  36. fakeexec "k8s.io/utils/exec/testing"
  37. )
  38. const (
  39. defaultNumberOfImages = 8
  40. )
  41. var (
  42. // dummyKubernetesVersion and dummyKubernetesVersionStr are just used for unit testing, in order to not make
  43. // kubeadm lookup dl.k8s.io to resolve what the latest stable release is
  44. dummyKubernetesVersion = constants.MinimumControlPlaneVersion
  45. dummyKubernetesVersionStr = dummyKubernetesVersion.String()
  46. )
  47. func TestNewCmdConfigImagesList(t *testing.T) {
  48. var output bytes.Buffer
  49. mockK8sVersion := dummyKubernetesVersionStr
  50. images := NewCmdConfigImagesList(&output, &mockK8sVersion)
  51. if err := images.RunE(nil, nil); err != nil {
  52. t.Fatalf("Error from running the images command: %v", err)
  53. }
  54. actual := strings.Split(output.String(), "\n")
  55. if len(actual) != defaultNumberOfImages {
  56. t.Fatalf("Expected %v but found %v images", defaultNumberOfImages, len(actual))
  57. }
  58. }
  59. func TestImagesListRunWithCustomConfigPath(t *testing.T) {
  60. testcases := []struct {
  61. name string
  62. expectedImageCount int
  63. // each string provided here must appear in at least one image returned by Run
  64. expectedImageSubstrings []string
  65. configContents []byte
  66. }{
  67. {
  68. name: "set k8s version",
  69. expectedImageCount: defaultNumberOfImages,
  70. expectedImageSubstrings: []string{
  71. constants.CurrentKubernetesVersion.String(),
  72. },
  73. configContents: []byte(dedent.Dedent(fmt.Sprintf(`
  74. apiVersion: kubeadm.k8s.io/v1beta2
  75. kind: ClusterConfiguration
  76. kubernetesVersion: %s
  77. `, constants.CurrentKubernetesVersion))),
  78. },
  79. {
  80. name: "use coredns",
  81. expectedImageCount: defaultNumberOfImages,
  82. expectedImageSubstrings: []string{
  83. "coredns",
  84. },
  85. configContents: []byte(dedent.Dedent(fmt.Sprintf(`
  86. apiVersion: kubeadm.k8s.io/v1beta2
  87. kind: ClusterConfiguration
  88. kubernetesVersion: %s
  89. `, constants.MinimumControlPlaneVersion))),
  90. },
  91. }
  92. outputFlags := output.NewOutputFlags(&imageTextPrintFlags{}).WithTypeSetter(outputapischeme.Scheme).WithDefaultOutput(output.TextOutput)
  93. printer, err := outputFlags.ToPrinter()
  94. if err != nil {
  95. t.Fatalf("can't create printer for the output format %s: %+v", output.TextOutput, err)
  96. }
  97. for _, tc := range testcases {
  98. t.Run(tc.name, func(t *testing.T) {
  99. tmpDir, err := ioutil.TempDir("", "kubeadm-images-test")
  100. if err != nil {
  101. t.Fatalf("Unable to create temporary directory: %v", err)
  102. }
  103. defer os.RemoveAll(tmpDir)
  104. configFilePath := filepath.Join(tmpDir, "test-config-file")
  105. if err := ioutil.WriteFile(configFilePath, tc.configContents, 0644); err != nil {
  106. t.Fatalf("Failed writing a config file: %v", err)
  107. }
  108. i, err := NewImagesList(configFilePath, &kubeadmapiv1beta2.ClusterConfiguration{
  109. KubernetesVersion: dummyKubernetesVersionStr,
  110. })
  111. if err != nil {
  112. t.Fatalf("Failed getting the kubeadm images command: %v", err)
  113. }
  114. var output bytes.Buffer
  115. if err = i.Run(&output, printer); err != nil {
  116. t.Fatalf("Error from running the images command: %v", err)
  117. }
  118. actual := strings.Split(output.String(), "\n")
  119. if len(actual) != tc.expectedImageCount {
  120. t.Fatalf("did not get the same number of images: actual: %v expected: %v. Actual value: %v", len(actual), tc.expectedImageCount, actual)
  121. }
  122. for _, substring := range tc.expectedImageSubstrings {
  123. if !strings.Contains(output.String(), substring) {
  124. t.Errorf("Expected to find %v but did not in this list of images: %v", substring, actual)
  125. }
  126. }
  127. })
  128. }
  129. }
  130. func TestConfigImagesListRunWithoutPath(t *testing.T) {
  131. testcases := []struct {
  132. name string
  133. cfg kubeadmapiv1beta2.ClusterConfiguration
  134. expectedImages int
  135. }{
  136. {
  137. name: "empty config",
  138. expectedImages: defaultNumberOfImages,
  139. cfg: kubeadmapiv1beta2.ClusterConfiguration{
  140. KubernetesVersion: dummyKubernetesVersionStr,
  141. },
  142. },
  143. {
  144. name: "external etcd configuration",
  145. cfg: kubeadmapiv1beta2.ClusterConfiguration{
  146. Etcd: kubeadmapiv1beta2.Etcd{
  147. External: &kubeadmapiv1beta2.ExternalEtcd{
  148. Endpoints: []string{"https://some.etcd.com:2379"},
  149. },
  150. },
  151. KubernetesVersion: dummyKubernetesVersionStr,
  152. },
  153. expectedImages: defaultNumberOfImages - 1,
  154. },
  155. {
  156. name: "coredns enabled",
  157. cfg: kubeadmapiv1beta2.ClusterConfiguration{
  158. KubernetesVersion: dummyKubernetesVersionStr,
  159. },
  160. expectedImages: defaultNumberOfImages,
  161. },
  162. {
  163. name: "kube-dns enabled",
  164. cfg: kubeadmapiv1beta2.ClusterConfiguration{
  165. KubernetesVersion: dummyKubernetesVersionStr,
  166. DNS: kubeadmapiv1beta2.DNS{
  167. Type: kubeadmapiv1beta2.KubeDNS,
  168. },
  169. },
  170. expectedImages: defaultNumberOfImages + 2,
  171. },
  172. }
  173. outputFlags := output.NewOutputFlags(&imageTextPrintFlags{}).WithTypeSetter(outputapischeme.Scheme).WithDefaultOutput(output.TextOutput)
  174. printer, err := outputFlags.ToPrinter()
  175. if err != nil {
  176. t.Fatalf("can't create printer for the output format %s: %+v", output.TextOutput, err)
  177. }
  178. for _, tc := range testcases {
  179. t.Run(tc.name, func(t *testing.T) {
  180. i, err := NewImagesList("", &tc.cfg)
  181. if err != nil {
  182. t.Fatalf("did not expect an error while creating the Images command: %v", err)
  183. }
  184. var output bytes.Buffer
  185. if err = i.Run(&output, printer); err != nil {
  186. t.Fatalf("did not expect an error running the Images command: %v", err)
  187. }
  188. actual := strings.Split(output.String(), "\n")
  189. if len(actual) != tc.expectedImages {
  190. t.Fatalf("expected %v images but got %v", tc.expectedImages, actual)
  191. }
  192. })
  193. }
  194. }
  195. func TestConfigImagesListOutput(t *testing.T) {
  196. etcdVersion, ok := constants.SupportedEtcdVersion[uint8(dummyKubernetesVersion.Minor())]
  197. if !ok {
  198. t.Fatalf("cannot determine etcd version for Kubernetes version %s", dummyKubernetesVersionStr)
  199. }
  200. versionMapping := struct {
  201. EtcdVersion string
  202. KubeVersion string
  203. PauseVersion string
  204. CoreDNSVersion string
  205. }{
  206. EtcdVersion: etcdVersion,
  207. KubeVersion: "v" + dummyKubernetesVersionStr,
  208. PauseVersion: constants.PauseVersion,
  209. CoreDNSVersion: constants.CoreDNSVersion,
  210. }
  211. testcases := []struct {
  212. name string
  213. cfg kubeadmapiv1beta2.ClusterConfiguration
  214. outputFormat string
  215. expectedOutput string
  216. }{
  217. {
  218. name: "text output",
  219. cfg: kubeadmapiv1beta2.ClusterConfiguration{
  220. KubernetesVersion: dummyKubernetesVersionStr,
  221. },
  222. outputFormat: "text",
  223. expectedOutput: `k8s.gcr.io/kube-apiserver:{{.KubeVersion}}
  224. k8s.gcr.io/kube-controller-manager:{{.KubeVersion}}
  225. k8s.gcr.io/kube-scheduler:{{.KubeVersion}}
  226. k8s.gcr.io/kube-proxy:{{.KubeVersion}}
  227. k8s.gcr.io/pause:{{.PauseVersion}}
  228. k8s.gcr.io/etcd:{{.EtcdVersion}}
  229. k8s.gcr.io/coredns:{{.CoreDNSVersion}}
  230. `,
  231. },
  232. {
  233. name: "JSON output",
  234. cfg: kubeadmapiv1beta2.ClusterConfiguration{
  235. KubernetesVersion: dummyKubernetesVersionStr,
  236. },
  237. outputFormat: "json",
  238. expectedOutput: `{
  239. "kind": "Images",
  240. "apiVersion": "output.kubeadm.k8s.io/v1alpha1",
  241. "images": [
  242. "k8s.gcr.io/kube-apiserver:{{.KubeVersion}}",
  243. "k8s.gcr.io/kube-controller-manager:{{.KubeVersion}}",
  244. "k8s.gcr.io/kube-scheduler:{{.KubeVersion}}",
  245. "k8s.gcr.io/kube-proxy:{{.KubeVersion}}",
  246. "k8s.gcr.io/pause:{{.PauseVersion}}",
  247. "k8s.gcr.io/etcd:{{.EtcdVersion}}",
  248. "k8s.gcr.io/coredns:{{.CoreDNSVersion}}"
  249. ]
  250. }
  251. `,
  252. },
  253. {
  254. name: "YAML output",
  255. cfg: kubeadmapiv1beta2.ClusterConfiguration{
  256. KubernetesVersion: dummyKubernetesVersionStr,
  257. },
  258. outputFormat: "yaml",
  259. expectedOutput: `apiVersion: output.kubeadm.k8s.io/v1alpha1
  260. images:
  261. - k8s.gcr.io/kube-apiserver:{{.KubeVersion}}
  262. - k8s.gcr.io/kube-controller-manager:{{.KubeVersion}}
  263. - k8s.gcr.io/kube-scheduler:{{.KubeVersion}}
  264. - k8s.gcr.io/kube-proxy:{{.KubeVersion}}
  265. - k8s.gcr.io/pause:{{.PauseVersion}}
  266. - k8s.gcr.io/etcd:{{.EtcdVersion}}
  267. - k8s.gcr.io/coredns:{{.CoreDNSVersion}}
  268. kind: Images
  269. `,
  270. },
  271. {
  272. name: "go-template output",
  273. cfg: kubeadmapiv1beta2.ClusterConfiguration{
  274. KubernetesVersion: dummyKubernetesVersionStr,
  275. },
  276. outputFormat: `go-template={{range .images}}{{.}}{{"\n"}}{{end}}`,
  277. expectedOutput: `k8s.gcr.io/kube-apiserver:{{.KubeVersion}}
  278. k8s.gcr.io/kube-controller-manager:{{.KubeVersion}}
  279. k8s.gcr.io/kube-scheduler:{{.KubeVersion}}
  280. k8s.gcr.io/kube-proxy:{{.KubeVersion}}
  281. k8s.gcr.io/pause:{{.PauseVersion}}
  282. k8s.gcr.io/etcd:{{.EtcdVersion}}
  283. k8s.gcr.io/coredns:{{.CoreDNSVersion}}
  284. `,
  285. },
  286. {
  287. name: "JSONPATH output",
  288. cfg: kubeadmapiv1beta2.ClusterConfiguration{
  289. KubernetesVersion: dummyKubernetesVersionStr,
  290. },
  291. outputFormat: `jsonpath={range.images[*]}{@} {end}`,
  292. expectedOutput: "k8s.gcr.io/kube-apiserver:{{.KubeVersion}} k8s.gcr.io/kube-controller-manager:{{.KubeVersion}} k8s.gcr.io/kube-scheduler:{{.KubeVersion}} " +
  293. "k8s.gcr.io/kube-proxy:{{.KubeVersion}} k8s.gcr.io/pause:{{.PauseVersion}} k8s.gcr.io/etcd:{{.EtcdVersion}} k8s.gcr.io/coredns:{{.CoreDNSVersion}} ",
  294. },
  295. }
  296. for _, tc := range testcases {
  297. outputFlags := output.NewOutputFlags(&imageTextPrintFlags{}).WithTypeSetter(outputapischeme.Scheme).WithDefaultOutput(tc.outputFormat)
  298. printer, err := outputFlags.ToPrinter()
  299. if err != nil {
  300. t.Fatalf("can't create printer for the output format %s: %+v", tc.outputFormat, err)
  301. }
  302. t.Run(tc.name, func(t *testing.T) {
  303. i, err := NewImagesList("", &tc.cfg)
  304. if err != nil {
  305. t.Fatalf("did not expect an error while creating the Images command: %v", err)
  306. }
  307. var output, expectedOutput bytes.Buffer
  308. if err = i.Run(&output, printer); err != nil {
  309. t.Fatalf("did not expect an error running the Images command: %v", err)
  310. }
  311. tmpl, err := template.New("test").Parse(tc.expectedOutput)
  312. if err != nil {
  313. t.Fatalf("could not create template: %v", err)
  314. }
  315. if err = tmpl.Execute(&expectedOutput, versionMapping); err != nil {
  316. t.Fatalf("could not execute template: %v", err)
  317. }
  318. if output.String() != expectedOutput.String() {
  319. t.Fatalf("unexpected output:\n|%s|\nexpected:\n|%s|\n", output.String(), tc.expectedOutput)
  320. }
  321. })
  322. }
  323. }
  324. func TestImagesPull(t *testing.T) {
  325. fcmd := fakeexec.FakeCmd{
  326. CombinedOutputScript: []fakeexec.FakeAction{
  327. func() ([]byte, []byte, error) { return nil, nil, nil },
  328. func() ([]byte, []byte, error) { return nil, nil, nil },
  329. func() ([]byte, []byte, error) { return nil, nil, nil },
  330. func() ([]byte, []byte, error) { return nil, nil, nil },
  331. func() ([]byte, []byte, error) { return nil, nil, nil },
  332. },
  333. }
  334. fexec := fakeexec.FakeExec{
  335. CommandScript: []fakeexec.FakeCommandAction{
  336. func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
  337. func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
  338. func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
  339. func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
  340. func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
  341. },
  342. LookPathFunc: func(cmd string) (string, error) { return "/usr/bin/docker", nil },
  343. }
  344. containerRuntime, err := utilruntime.NewContainerRuntime(&fexec, constants.DefaultDockerCRISocket)
  345. if err != nil {
  346. t.Errorf("unexpected NewContainerRuntime error: %v", err)
  347. }
  348. images := []string{"a", "b", "c", "d", "a"}
  349. for _, image := range images {
  350. if err := containerRuntime.PullImage(image); err != nil {
  351. t.Fatalf("expected nil but found %v", err)
  352. }
  353. fmt.Printf("[config/images] Pulled %s\n", image)
  354. }
  355. if fcmd.CombinedOutputCalls != len(images) {
  356. t.Errorf("expected %d calls, got %d", len(images), fcmd.CombinedOutputCalls)
  357. }
  358. }
  359. func TestMigrate(t *testing.T) {
  360. cfg := []byte(dedent.Dedent(`
  361. # This is intentionally testing an old API version. Sometimes this may be the latest version (if no old configs are supported).
  362. apiVersion: kubeadm.k8s.io/v1beta1
  363. kind: InitConfiguration
  364. `))
  365. configFile, cleanup := tempConfig(t, cfg)
  366. defer cleanup()
  367. var output bytes.Buffer
  368. command := NewCmdConfigMigrate(&output)
  369. if err := command.Flags().Set("old-config", configFile); err != nil {
  370. t.Fatalf("failed to set old-config flag")
  371. }
  372. newConfigPath := filepath.Join(filepath.Dir(configFile), "new-migrated-config")
  373. if err := command.Flags().Set("new-config", newConfigPath); err != nil {
  374. t.Fatalf("failed to set new-config flag")
  375. }
  376. if err := command.RunE(nil, nil); err != nil {
  377. t.Fatalf("Error from running the migrate command: %v", err)
  378. }
  379. if _, err := configutil.LoadInitConfigurationFromFile(newConfigPath); err != nil {
  380. t.Fatalf("Could not read output back into internal type: %v", err)
  381. }
  382. }
  383. // Returns the name of the file created and a cleanup callback
  384. func tempConfig(t *testing.T, config []byte) (string, func()) {
  385. t.Helper()
  386. tmpDir, err := ioutil.TempDir("", "kubeadm-migration-test")
  387. if err != nil {
  388. t.Fatalf("Unable to create temporary directory: %v", err)
  389. }
  390. configFilePath := filepath.Join(tmpDir, "test-config-file")
  391. if err := ioutil.WriteFile(configFilePath, config, 0644); err != nil {
  392. os.RemoveAll(tmpDir)
  393. t.Fatalf("Failed writing a config file: %v", err)
  394. }
  395. return configFilePath, func() {
  396. os.RemoveAll(tmpDir)
  397. }
  398. }
  399. func TestNewCmdConfigPrintActionDefaults(t *testing.T) {
  400. tests := []struct {
  401. name string
  402. expectedKinds []string // need to be sorted
  403. componentConfigs string
  404. cmdProc func(out io.Writer) *cobra.Command
  405. }{
  406. {
  407. name: "InitConfiguration: No component configs",
  408. expectedKinds: []string{
  409. constants.ClusterConfigurationKind,
  410. constants.InitConfigurationKind,
  411. },
  412. cmdProc: NewCmdConfigPrintInitDefaults,
  413. },
  414. {
  415. name: "InitConfiguration: KubeProxyConfiguration",
  416. expectedKinds: []string{
  417. constants.ClusterConfigurationKind,
  418. constants.InitConfigurationKind,
  419. "KubeProxyConfiguration",
  420. },
  421. componentConfigs: "KubeProxyConfiguration",
  422. cmdProc: NewCmdConfigPrintInitDefaults,
  423. },
  424. {
  425. name: "InitConfiguration: KubeProxyConfiguration and KubeletConfiguration",
  426. expectedKinds: []string{
  427. constants.ClusterConfigurationKind,
  428. constants.InitConfigurationKind,
  429. "KubeProxyConfiguration",
  430. "KubeletConfiguration",
  431. },
  432. componentConfigs: "KubeProxyConfiguration,KubeletConfiguration",
  433. cmdProc: NewCmdConfigPrintInitDefaults,
  434. },
  435. {
  436. name: "JoinConfiguration: No component configs",
  437. expectedKinds: []string{
  438. constants.JoinConfigurationKind,
  439. },
  440. cmdProc: NewCmdConfigPrintJoinDefaults,
  441. },
  442. {
  443. name: "JoinConfiguration: KubeProxyConfiguration",
  444. expectedKinds: []string{
  445. constants.JoinConfigurationKind,
  446. "KubeProxyConfiguration",
  447. },
  448. componentConfigs: "KubeProxyConfiguration",
  449. cmdProc: NewCmdConfigPrintJoinDefaults,
  450. },
  451. {
  452. name: "JoinConfiguration: KubeProxyConfiguration and KubeletConfiguration",
  453. expectedKinds: []string{
  454. constants.JoinConfigurationKind,
  455. "KubeProxyConfiguration",
  456. "KubeletConfiguration",
  457. },
  458. componentConfigs: "KubeProxyConfiguration,KubeletConfiguration",
  459. cmdProc: NewCmdConfigPrintJoinDefaults,
  460. },
  461. }
  462. for _, test := range tests {
  463. t.Run(test.name, func(t *testing.T) {
  464. var output bytes.Buffer
  465. command := test.cmdProc(&output)
  466. if err := command.Flags().Set("component-configs", test.componentConfigs); err != nil {
  467. t.Fatalf("failed to set component-configs flag")
  468. }
  469. if err := command.RunE(nil, nil); err != nil {
  470. t.Fatalf("Error from running the print command: %v", err)
  471. }
  472. gvkmap, err := kubeadmutil.SplitYAMLDocuments(output.Bytes())
  473. if err != nil {
  474. t.Fatalf("unexpected failure of SplitYAMLDocuments: %v", err)
  475. }
  476. gotKinds := []string{}
  477. for gvk := range gvkmap {
  478. gotKinds = append(gotKinds, gvk.Kind)
  479. }
  480. sort.Strings(gotKinds)
  481. if !reflect.DeepEqual(gotKinds, test.expectedKinds) {
  482. t.Fatalf("kinds not matching:\n\texpectedKinds: %v\n\tgotKinds: %v\n", test.expectedKinds, gotKinds)
  483. }
  484. })
  485. }
  486. }