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