From f9c3148af5096292b3196472d1e85db1177c1457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20K=C3=A4ldstr=C3=B6m?= Date: Fri, 25 Aug 2017 14:00:39 +0300 Subject: [PATCH] Add unit tests for kubeadm upgrade|plan --- cmd/kubeadm/app/cmd/upgrade/apply_test.go | 194 ++++++++++++ cmd/kubeadm/app/cmd/upgrade/common_test.go | 124 ++++++++ cmd/kubeadm/app/cmd/upgrade/plan_test.go | 329 +++++++++++++++++++++ 3 files changed, 647 insertions(+) create mode 100644 cmd/kubeadm/app/cmd/upgrade/apply_test.go create mode 100644 cmd/kubeadm/app/cmd/upgrade/common_test.go create mode 100644 cmd/kubeadm/app/cmd/upgrade/plan_test.go diff --git a/cmd/kubeadm/app/cmd/upgrade/apply_test.go b/cmd/kubeadm/app/cmd/upgrade/apply_test.go new file mode 100644 index 00000000000..8f8b1584535 --- /dev/null +++ b/cmd/kubeadm/app/cmd/upgrade/apply_test.go @@ -0,0 +1,194 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package upgrade + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/util/version" +) + +func TestSetImplicitFlags(t *testing.T) { + var tests = []struct { + flags *applyFlags + expectedFlags applyFlags + errExpected bool + }{ + { // if not dryRun or force is set; the nonInteractiveMode field should not be touched + flags: &applyFlags{ + newK8sVersionStr: "v1.8.0", + dryRun: false, + force: false, + nonInteractiveMode: false, + }, + expectedFlags: applyFlags{ + newK8sVersionStr: "v1.8.0", + newK8sVersion: version.MustParseSemantic("v1.8.0"), + dryRun: false, + force: false, + nonInteractiveMode: false, + }, + }, + { // if not dryRun or force is set; the nonInteractiveMode field should not be touched + flags: &applyFlags{ + newK8sVersionStr: "v1.8.0", + dryRun: false, + force: false, + nonInteractiveMode: true, + }, + expectedFlags: applyFlags{ + newK8sVersionStr: "v1.8.0", + newK8sVersion: version.MustParseSemantic("v1.8.0"), + dryRun: false, + force: false, + nonInteractiveMode: true, + }, + }, + { // if dryRun or force is set; the nonInteractiveMode field should be set to true + flags: &applyFlags{ + newK8sVersionStr: "v1.8.0", + dryRun: true, + force: false, + nonInteractiveMode: false, + }, + expectedFlags: applyFlags{ + newK8sVersionStr: "v1.8.0", + newK8sVersion: version.MustParseSemantic("v1.8.0"), + dryRun: true, + force: false, + nonInteractiveMode: true, + }, + }, + { // if dryRun or force is set; the nonInteractiveMode field should be set to true + flags: &applyFlags{ + newK8sVersionStr: "v1.8.0", + dryRun: false, + force: true, + nonInteractiveMode: false, + }, + expectedFlags: applyFlags{ + newK8sVersionStr: "v1.8.0", + newK8sVersion: version.MustParseSemantic("v1.8.0"), + dryRun: false, + force: true, + nonInteractiveMode: true, + }, + }, + { // if dryRun or force is set; the nonInteractiveMode field should be set to true + flags: &applyFlags{ + newK8sVersionStr: "v1.8.0", + dryRun: true, + force: true, + nonInteractiveMode: false, + }, + expectedFlags: applyFlags{ + newK8sVersionStr: "v1.8.0", + newK8sVersion: version.MustParseSemantic("v1.8.0"), + dryRun: true, + force: true, + nonInteractiveMode: true, + }, + }, + { // if dryRun or force is set; the nonInteractiveMode field should be set to true + flags: &applyFlags{ + newK8sVersionStr: "v1.8.0", + dryRun: true, + force: true, + nonInteractiveMode: true, + }, + expectedFlags: applyFlags{ + newK8sVersionStr: "v1.8.0", + newK8sVersion: version.MustParseSemantic("v1.8.0"), + dryRun: true, + force: true, + nonInteractiveMode: true, + }, + }, + { // if the new version is empty; it should error out + flags: &applyFlags{ + newK8sVersionStr: "", + }, + expectedFlags: applyFlags{ + newK8sVersionStr: "", + }, + errExpected: true, + }, + { // if the new version is invalid; it should error out + flags: &applyFlags{ + newK8sVersionStr: "foo", + }, + expectedFlags: applyFlags{ + newK8sVersionStr: "foo", + }, + errExpected: true, + }, + { // if the new version is valid but without the "v" prefix; it parse and prepend v + flags: &applyFlags{ + newK8sVersionStr: "1.8.0", + }, + expectedFlags: applyFlags{ + newK8sVersionStr: "v1.8.0", + newK8sVersion: version.MustParseSemantic("v1.8.0"), + }, + errExpected: false, + }, + { // valid version should succeed + flags: &applyFlags{ + newK8sVersionStr: "v1.8.1", + }, + expectedFlags: applyFlags{ + newK8sVersionStr: "v1.8.1", + newK8sVersion: version.MustParseSemantic("v1.8.1"), + }, + errExpected: false, + }, + { // valid version should succeed + flags: &applyFlags{ + newK8sVersionStr: "1.8.0-alpha.3", + }, + expectedFlags: applyFlags{ + newK8sVersionStr: "v1.8.0-alpha.3", + newK8sVersion: version.MustParseSemantic("v1.8.0-alpha.3"), + }, + errExpected: false, + }, + } + for _, rt := range tests { + actualErr := SetImplicitFlags(rt.flags) + + // If an error was returned; make newK8sVersion nil so it's easy to match using reflect.DeepEqual later (instead of a random pointer) + if actualErr != nil { + rt.flags.newK8sVersion = nil + } + + if !reflect.DeepEqual(*rt.flags, rt.expectedFlags) { + t.Errorf( + "failed SetImplicitFlags:\n\texpected flags: %v\n\t actual: %v", + rt.expectedFlags, + *rt.flags, + ) + } + if (actualErr != nil) != rt.errExpected { + t.Errorf( + "failed SetImplicitFlags:\n\texpected error: %t\n\t actual: %t", + rt.errExpected, + (actualErr != nil), + ) + } + } +} diff --git a/cmd/kubeadm/app/cmd/upgrade/common_test.go b/cmd/kubeadm/app/cmd/upgrade/common_test.go new file mode 100644 index 00000000000..4bf791d3009 --- /dev/null +++ b/cmd/kubeadm/app/cmd/upgrade/common_test.go @@ -0,0 +1,124 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package upgrade + +import ( + "bytes" + "testing" + + kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" +) + +func TestPrintConfiguration(t *testing.T) { + var tests = []struct { + cfg *kubeadmapiext.MasterConfiguration + buf *bytes.Buffer + expectedBytes []byte + }{ + { + cfg: nil, + expectedBytes: []byte(""), + }, + { + cfg: &kubeadmapiext.MasterConfiguration{ + KubernetesVersion: "v1.7.1", + }, + expectedBytes: []byte(`[upgrade/config] Configuration used: + api: + advertiseAddress: "" + bindPort: 0 + apiServerCertSANs: null + apiServerExtraArgs: null + authorizationModes: null + certificatesDir: "" + cloudProvider: "" + controllerManagerExtraArgs: null + etcd: + caFile: "" + certFile: "" + dataDir: "" + endpoints: null + extraArgs: null + image: "" + keyFile: "" + featureFlags: null + imageRepository: "" + kubernetesVersion: v1.7.1 + networking: + dnsDomain: "" + podSubnet: "" + serviceSubnet: "" + nodeName: "" + schedulerExtraArgs: null + token: "" + tokenTTL: 0 + unifiedControlPlaneImage: "" +`), + }, + { + cfg: &kubeadmapiext.MasterConfiguration{ + KubernetesVersion: "v1.7.1", + Networking: kubeadmapiext.Networking{ + ServiceSubnet: "10.96.0.1/12", + }, + }, + expectedBytes: []byte(`[upgrade/config] Configuration used: + api: + advertiseAddress: "" + bindPort: 0 + apiServerCertSANs: null + apiServerExtraArgs: null + authorizationModes: null + certificatesDir: "" + cloudProvider: "" + controllerManagerExtraArgs: null + etcd: + caFile: "" + certFile: "" + dataDir: "" + endpoints: null + extraArgs: null + image: "" + keyFile: "" + featureFlags: null + imageRepository: "" + kubernetesVersion: v1.7.1 + networking: + dnsDomain: "" + podSubnet: "" + serviceSubnet: 10.96.0.1/12 + nodeName: "" + schedulerExtraArgs: null + token: "" + tokenTTL: 0 + unifiedControlPlaneImage: "" +`), + }, + } + for _, rt := range tests { + rt.buf = bytes.NewBufferString("") + printConfiguration(rt.cfg, rt.buf) + actualBytes := rt.buf.Bytes() + if !bytes.Equal(actualBytes, rt.expectedBytes) { + t.Errorf( + "failed PrintConfiguration:\n\texpected: %q\n\t actual: %q", + string(rt.expectedBytes), + string(actualBytes), + ) + } + } +} diff --git a/cmd/kubeadm/app/cmd/upgrade/plan_test.go b/cmd/kubeadm/app/cmd/upgrade/plan_test.go new file mode 100644 index 00000000000..bddef5c393c --- /dev/null +++ b/cmd/kubeadm/app/cmd/upgrade/plan_test.go @@ -0,0 +1,329 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package upgrade + +import ( + "bytes" + "reflect" + "testing" + + "k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade" +) + +func TestSortedSliceFromStringIntMap(t *testing.T) { + var tests = []struct { + strMap map[string]uint16 + expectedSlice []string + }{ // The returned slice should be alphabetically sorted based on the string keys in the map + { + strMap: map[string]uint16{"foo": 1, "bar": 2}, + expectedSlice: []string{"bar", "foo"}, + }, + { // The int value should not affect this func + strMap: map[string]uint16{"foo": 2, "bar": 1}, + expectedSlice: []string{"bar", "foo"}, + }, + { + strMap: map[string]uint16{"b": 2, "a": 1, "cb": 0, "ca": 1000}, + expectedSlice: []string{"a", "b", "ca", "cb"}, + }, + { // This should work for version numbers as well; and the lowest version should come first + strMap: map[string]uint16{"v1.7.0": 1, "v1.6.1": 1, "v1.6.2": 1, "v1.8.0": 1, "v1.8.0-alpha.1": 1}, + expectedSlice: []string{"v1.6.1", "v1.6.2", "v1.7.0", "v1.8.0", "v1.8.0-alpha.1"}, + }, + } + for _, rt := range tests { + actualSlice := sortedSliceFromStringIntMap(rt.strMap) + if !reflect.DeepEqual(actualSlice, rt.expectedSlice) { + t.Errorf( + "failed SortedSliceFromStringIntMap:\n\texpected: %v\n\t actual: %v", + rt.expectedSlice, + actualSlice, + ) + } + } +} + +// TODO Think about modifying this test to be less verbose checking b/c it can be brittle. +func TestPrintAvailableUpgrades(t *testing.T) { + var tests = []struct { + upgrades []upgrade.Upgrade + buf *bytes.Buffer + expectedBytes []byte + }{ + { + upgrades: []upgrade.Upgrade{}, + expectedBytes: []byte(`Awesome, you're up-to-date! Enjoy! +`), + }, + { + upgrades: []upgrade.Upgrade{ + { + Description: "version in the v1.7 series", + Before: upgrade.ClusterState{ + KubeVersion: "v1.7.1", + KubeletVersions: map[string]uint16{ + "v1.7.1": 1, + }, + KubeadmVersion: "v1.7.2", + DNSVersion: "1.14.4", + }, + After: upgrade.ClusterState{ + KubeVersion: "v1.7.3", + KubeadmVersion: "v1.7.3", + DNSVersion: "1.14.4", + }, + }, + }, + expectedBytes: []byte(`Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply': +COMPONENT CURRENT AVAILABLE +Kubelet 1 x v1.7.1 v1.7.3 + +Upgrade to the latest version in the v1.7 series: + +COMPONENT CURRENT AVAILABLE +API Server v1.7.1 v1.7.3 +Controller Manager v1.7.1 v1.7.3 +Scheduler v1.7.1 v1.7.3 +Kube Proxy v1.7.1 v1.7.3 +Kube DNS 1.14.4 1.14.4 + +You can now apply the upgrade by executing the following command: + + kubeadm upgrade apply v1.7.3 + +Note: Before you do can perform this upgrade, you have to update kubeadm to v1.7.3 + +_____________________________________________________________________ + +`), + }, + { + upgrades: []upgrade.Upgrade{ + { + Description: "stable version", + Before: upgrade.ClusterState{ + KubeVersion: "v1.7.3", + KubeletVersions: map[string]uint16{ + "v1.7.3": 1, + }, + KubeadmVersion: "v1.8.0", + DNSVersion: "1.14.4", + }, + After: upgrade.ClusterState{ + KubeVersion: "v1.8.0", + KubeadmVersion: "v1.8.0", + DNSVersion: "1.14.4", + }, + }, + }, + expectedBytes: []byte(`Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply': +COMPONENT CURRENT AVAILABLE +Kubelet 1 x v1.7.3 v1.8.0 + +Upgrade to the latest stable version: + +COMPONENT CURRENT AVAILABLE +API Server v1.7.3 v1.8.0 +Controller Manager v1.7.3 v1.8.0 +Scheduler v1.7.3 v1.8.0 +Kube Proxy v1.7.3 v1.8.0 +Kube DNS 1.14.4 1.14.4 + +You can now apply the upgrade by executing the following command: + + kubeadm upgrade apply v1.8.0 + +_____________________________________________________________________ + +`), + }, + { + upgrades: []upgrade.Upgrade{ + { + Description: "version in the v1.7 series", + Before: upgrade.ClusterState{ + KubeVersion: "v1.7.3", + KubeletVersions: map[string]uint16{ + "v1.7.3": 1, + }, + KubeadmVersion: "v1.8.1", + DNSVersion: "1.14.4", + }, + After: upgrade.ClusterState{ + KubeVersion: "v1.7.5", + KubeadmVersion: "v1.8.1", + DNSVersion: "1.14.4", + }, + }, + { + Description: "stable version", + Before: upgrade.ClusterState{ + KubeVersion: "v1.7.3", + KubeletVersions: map[string]uint16{ + "v1.7.3": 1, + }, + KubeadmVersion: "v1.8.1", + DNSVersion: "1.14.4", + }, + After: upgrade.ClusterState{ + KubeVersion: "v1.8.2", + KubeadmVersion: "v1.8.2", + DNSVersion: "1.14.4", + }, + }, + }, + expectedBytes: []byte(`Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply': +COMPONENT CURRENT AVAILABLE +Kubelet 1 x v1.7.3 v1.7.5 + +Upgrade to the latest version in the v1.7 series: + +COMPONENT CURRENT AVAILABLE +API Server v1.7.3 v1.7.5 +Controller Manager v1.7.3 v1.7.5 +Scheduler v1.7.3 v1.7.5 +Kube Proxy v1.7.3 v1.7.5 +Kube DNS 1.14.4 1.14.4 + +You can now apply the upgrade by executing the following command: + + kubeadm upgrade apply v1.7.5 + +_____________________________________________________________________ + +Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply': +COMPONENT CURRENT AVAILABLE +Kubelet 1 x v1.7.3 v1.8.2 + +Upgrade to the latest stable version: + +COMPONENT CURRENT AVAILABLE +API Server v1.7.3 v1.8.2 +Controller Manager v1.7.3 v1.8.2 +Scheduler v1.7.3 v1.8.2 +Kube Proxy v1.7.3 v1.8.2 +Kube DNS 1.14.4 1.14.4 + +You can now apply the upgrade by executing the following command: + + kubeadm upgrade apply v1.8.2 + +Note: Before you do can perform this upgrade, you have to update kubeadm to v1.8.2 + +_____________________________________________________________________ + +`), + }, + { + upgrades: []upgrade.Upgrade{ + { + Description: "experimental version", + Before: upgrade.ClusterState{ + KubeVersion: "v1.7.5", + KubeletVersions: map[string]uint16{ + "v1.7.5": 1, + }, + KubeadmVersion: "v1.7.5", + DNSVersion: "1.14.4", + }, + After: upgrade.ClusterState{ + KubeVersion: "v1.8.0-beta.1", + KubeadmVersion: "v1.8.0-beta.1", + DNSVersion: "1.14.4", + }, + }, + }, + expectedBytes: []byte(`Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply': +COMPONENT CURRENT AVAILABLE +Kubelet 1 x v1.7.5 v1.8.0-beta.1 + +Upgrade to the latest experimental version: + +COMPONENT CURRENT AVAILABLE +API Server v1.7.5 v1.8.0-beta.1 +Controller Manager v1.7.5 v1.8.0-beta.1 +Scheduler v1.7.5 v1.8.0-beta.1 +Kube Proxy v1.7.5 v1.8.0-beta.1 +Kube DNS 1.14.4 1.14.4 + +You can now apply the upgrade by executing the following command: + + kubeadm upgrade apply v1.8.0-beta.1 + +Note: Before you do can perform this upgrade, you have to update kubeadm to v1.8.0-beta.1 + +_____________________________________________________________________ + +`), + }, + { + upgrades: []upgrade.Upgrade{ + { + Description: "release candidate version", + Before: upgrade.ClusterState{ + KubeVersion: "v1.7.5", + KubeletVersions: map[string]uint16{ + "v1.7.5": 1, + }, + KubeadmVersion: "v1.7.5", + DNSVersion: "1.14.4", + }, + After: upgrade.ClusterState{ + KubeVersion: "v1.8.0-rc.1", + KubeadmVersion: "v1.8.0-rc.1", + DNSVersion: "1.14.4", + }, + }, + }, + expectedBytes: []byte(`Components that must be upgraded manually after you've upgraded the control plane with 'kubeadm upgrade apply': +COMPONENT CURRENT AVAILABLE +Kubelet 1 x v1.7.5 v1.8.0-rc.1 + +Upgrade to the latest release candidate version: + +COMPONENT CURRENT AVAILABLE +API Server v1.7.5 v1.8.0-rc.1 +Controller Manager v1.7.5 v1.8.0-rc.1 +Scheduler v1.7.5 v1.8.0-rc.1 +Kube Proxy v1.7.5 v1.8.0-rc.1 +Kube DNS 1.14.4 1.14.4 + +You can now apply the upgrade by executing the following command: + + kubeadm upgrade apply v1.8.0-rc.1 + +Note: Before you do can perform this upgrade, you have to update kubeadm to v1.8.0-rc.1 + +_____________________________________________________________________ + +`), + }, + } + for _, rt := range tests { + rt.buf = bytes.NewBufferString("") + printAvailableUpgrades(rt.upgrades, rt.buf) + actualBytes := rt.buf.Bytes() + if !bytes.Equal(actualBytes, rt.expectedBytes) { + t.Errorf( + "failed PrintAvailableUpgrades:\n\texpected: %q\n\t actual: %q", + string(rt.expectedBytes), + string(actualBytes), + ) + } + } +}