From 92b7d944869906662fa7f562996cebf2b80f0581 Mon Sep 17 00:00:00 2001 From: Atanas Dinov Date: Tue, 10 Sep 2024 11:23:48 +0300 Subject: [PATCH 1/6] Add supported nodes validation tests Signed-off-by: Atanas Dinov --- internal/controller/reconcile_os.go | 2 +- internal/controller/reconcile_os_test.go | 85 ++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 internal/controller/reconcile_os_test.go diff --git a/internal/controller/reconcile_os.go b/internal/controller/reconcile_os.go index b94c0ce..b196297 100644 --- a/internal/controller/reconcile_os.go +++ b/internal/controller/reconcile_os.go @@ -129,7 +129,7 @@ func validateOSArch(nodeList *corev1.NodeList, supportedArchs []lifecyclev1alpha for _, node := range nodeList.Items { nodeArch := node.Status.NodeInfo.Architecture if _, ok := supportedArchMap[nodeArch]; !ok { - return fmt.Errorf("unsuported arch '%s' for '%s' node. Supported archs: %s", nodeArch, node.Name, supportedArchs) + return fmt.Errorf("unsupported arch '%s' for '%s' node. Supported archs: %s", nodeArch, node.Name, supportedArchs) } } diff --git a/internal/controller/reconcile_os_test.go b/internal/controller/reconcile_os_test.go new file mode 100644 index 0000000..392f176 --- /dev/null +++ b/internal/controller/reconcile_os_test.go @@ -0,0 +1,85 @@ +package controller + +import ( + "testing" + + "github.com/stretchr/testify/assert" + lifecyclev1alpha1 "github.com/suse-edge/upgrade-controller/api/v1alpha1" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestValidateOSArch(t *testing.T) { + validArchs := []lifecyclev1alpha1.Arch{ + "x86_64", + "aarch64", + } + + nodes := &corev1.NodeList{ + Items: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{Name: "node1"}, + Status: corev1.NodeStatus{NodeInfo: corev1.NodeSystemInfo{Architecture: "arm64"}}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "node2"}, + Status: corev1.NodeStatus{NodeInfo: corev1.NodeSystemInfo{Architecture: "aarch64"}}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "node3"}, + Status: corev1.NodeStatus{NodeInfo: corev1.NodeSystemInfo{Architecture: "amd64"}}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "node4"}, + Status: corev1.NodeStatus{NodeInfo: corev1.NodeSystemInfo{Architecture: "x86_64"}}, + }, + }} + + assert.NoError(t, validateOSArch(nodes, validArchs)) +} + +func TestValidateOSArch_InvalidArch(t *testing.T) { + archs := []lifecyclev1alpha1.Arch{ + "x86_64", + "risc-v", + } + + assert.PanicsWithValue(t, "unknown arch: risc-v", func() { + _ = validateOSArch(nil, archs) + }) +} + +func TestValidateOSArch_UnsupportedNode(t *testing.T) { + validArchs := []lifecyclev1alpha1.Arch{ + "x86_64", + "aarch64", + } + + nodes := &corev1.NodeList{ + Items: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{Name: "node1"}, + Status: corev1.NodeStatus{NodeInfo: corev1.NodeSystemInfo{Architecture: "arm64"}}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "node2"}, + Status: corev1.NodeStatus{NodeInfo: corev1.NodeSystemInfo{Architecture: "aarch64"}}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "node3"}, + Status: corev1.NodeStatus{NodeInfo: corev1.NodeSystemInfo{Architecture: "amd64"}}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "node4"}, + Status: corev1.NodeStatus{NodeInfo: corev1.NodeSystemInfo{Architecture: "x86_64"}}, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "node5"}, + Status: corev1.NodeStatus{NodeInfo: corev1.NodeSystemInfo{Architecture: "risc-v"}}, + }, + }} + + assert.EqualError(t, validateOSArch(nodes, validArchs), + "unsupported arch 'risc-v' for 'node5' node. Supported archs: [x86_64 aarch64]") +} From 9f1c83502e7c7c3c23d739111c4b6cf5ff287f2f Mon Sep 17 00:00:00 2001 From: Atanas Dinov Date: Tue, 10 Sep 2024 11:25:44 +0300 Subject: [PATCH 2/6] Fix test failures Signed-off-by: Atanas Dinov --- internal/upgrade/os_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/upgrade/os_test.go b/internal/upgrade/os_test.go index b1e7959..a39fc91 100644 --- a/internal/upgrade/os_test.go +++ b/internal/upgrade/os_test.go @@ -80,13 +80,13 @@ func TestOSControlPlanePlan(t *testing.T) { upgradeContainer := upgradePlan.Spec.Upgrade require.NotNil(t, upgradeContainer) - assert.Equal(t, "registry.suse.com/bci/bci-base:15.5", upgradeContainer.Image) + assert.Equal(t, "registry.suse.com/bci/bci-base:15.6", upgradeContainer.Image) assert.Equal(t, []string{"chroot", "/host"}, upgradeContainer.Command) assert.Equal(t, []string{"sh", "/run/system-upgrade/secrets/some-secret/os-upgrade.sh"}, upgradeContainer.Args) assert.Equal(t, "3.1.0", upgradePlan.Spec.Version) assert.EqualValues(t, 1, upgradePlan.Spec.Concurrency) - assert.EqualValues(t, 3600, upgradePlan.Spec.JobActiveDeadlineSecs) + assert.EqualValues(t, 43200, upgradePlan.Spec.JobActiveDeadlineSecs) assert.True(t, upgradePlan.Spec.Cordon) assert.Equal(t, "system-upgrade-controller", upgradePlan.Spec.ServiceAccountName) @@ -149,13 +149,13 @@ func TestOSWorkerPlan(t *testing.T) { upgradeContainer := upgradePlan.Spec.Upgrade require.NotNil(t, upgradeContainer) - assert.Equal(t, "registry.suse.com/bci/bci-base:15.5", upgradeContainer.Image) + assert.Equal(t, "registry.suse.com/bci/bci-base:15.6", upgradeContainer.Image) assert.Equal(t, []string{"chroot", "/host"}, upgradeContainer.Command) assert.Equal(t, []string{"sh", "/run/system-upgrade/secrets/some-secret/os-upgrade.sh"}, upgradeContainer.Args) assert.Equal(t, "3.1.0", upgradePlan.Spec.Version) assert.EqualValues(t, 2, upgradePlan.Spec.Concurrency) - assert.EqualValues(t, 3600, upgradePlan.Spec.JobActiveDeadlineSecs) + assert.EqualValues(t, 43200, upgradePlan.Spec.JobActiveDeadlineSecs) assert.True(t, upgradePlan.Spec.Cordon) assert.Equal(t, "system-upgrade-controller", upgradePlan.Spec.ServiceAccountName) From 9f3b93c3af03593c078fbaf31233a1470e92f913 Mon Sep 17 00:00:00 2001 From: Atanas Dinov Date: Tue, 10 Sep 2024 11:48:49 +0300 Subject: [PATCH 3/6] Add OS upgrade check tests Signed-off-by: Atanas Dinov --- internal/controller/reconcile_os_test.go | 135 +++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/internal/controller/reconcile_os_test.go b/internal/controller/reconcile_os_test.go index 392f176..0f6db3b 100644 --- a/internal/controller/reconcile_os_test.go +++ b/internal/controller/reconcile_os_test.go @@ -8,6 +8,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" ) func TestValidateOSArch(t *testing.T) { @@ -83,3 +84,137 @@ func TestValidateOSArch_UnsupportedNode(t *testing.T) { assert.EqualError(t, validateOSArch(nodes, validArchs), "unsupported arch 'risc-v' for 'node5' node. Supported archs: [x86_64 aarch64]") } + +func TestIsOSUpgraded(t *testing.T) { + const osPrettyName = "SUSE Linux Micro 6.0" + + nodeLabels := map[string]string{ + "node-x": "z", + } + + tests := []struct { + name string + nodes *corev1.NodeList + selector labels.Selector + expectedUpgrade bool + }{ + { + name: "All matching nodes upgraded", + nodes: &corev1.NodeList{ + Items: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{Labels: nodeLabels}, + Spec: corev1.NodeSpec{Unschedulable: false}, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{OSImage: "SUSE Linux Micro 6.0"}}, + }, + { + ObjectMeta: metav1.ObjectMeta{Labels: nodeLabels}, + Spec: corev1.NodeSpec{Unschedulable: false}, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{OSImage: "SUSE Linux Micro 6.0"}}, + }, + { + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{OSImage: "SUSE Linux Enterprise Micro 5.5"}}, + }, + }, + }, + selector: labels.SelectorFromSet(nodeLabels), + expectedUpgrade: true, + }, + { + name: "Unschedulable matching node", + nodes: &corev1.NodeList{ + Items: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{Labels: nodeLabels}, + Spec: corev1.NodeSpec{Unschedulable: true}, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{OSImage: "SUSE Linux Micro 6.0"}}, + }, + { + ObjectMeta: metav1.ObjectMeta{Labels: nodeLabels}, + Spec: corev1.NodeSpec{Unschedulable: false}, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{OSImage: "SUSE Linux Micro 6.0"}}, + }, + { + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{OSImage: "SUSE Linux Enterprise Micro 5.5"}}, + }, + }, + }, + selector: labels.SelectorFromSet(nodeLabels), + expectedUpgrade: false, + }, + { + name: "Not ready matching node", + nodes: &corev1.NodeList{ + Items: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{Labels: nodeLabels}, + Spec: corev1.NodeSpec{Unschedulable: false}, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionFalse}}, + NodeInfo: corev1.NodeSystemInfo{OSImage: "SUSE Linux Micro 6.0"}}, + }, + { + ObjectMeta: metav1.ObjectMeta{Labels: nodeLabels}, + Spec: corev1.NodeSpec{Unschedulable: false}, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{OSImage: "SUSE Linux Micro 6.0"}}, + }, + { + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{OSImage: "SUSE Linux Enterprise Micro 5.5"}}, + }, + }, + }, + selector: labels.SelectorFromSet(nodeLabels), + expectedUpgrade: false, + }, + { + name: "Matching node on older OS", + nodes: &corev1.NodeList{ + Items: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{Labels: nodeLabels}, + Spec: corev1.NodeSpec{Unschedulable: false}, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{OSImage: "SUSE Linux Micro Micro 5.5"}}, + }, + { + ObjectMeta: metav1.ObjectMeta{Labels: nodeLabels}, + Spec: corev1.NodeSpec{Unschedulable: false}, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{OSImage: "SUSE Linux Micro 6.0"}}, + }, + { + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{OSImage: "SUSE Linux Enterprise Micro 5.5"}}, + }, + }, + }, + selector: labels.SelectorFromSet(nodeLabels), + expectedUpgrade: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.expectedUpgrade, isOSUpgraded(test.nodes, test.selector, osPrettyName)) + }) + } +} From faf34276cda086b2dabfa9b1f42afd7b0fb983f7 Mon Sep 17 00:00:00 2001 From: Atanas Dinov Date: Tue, 10 Sep 2024 11:52:18 +0300 Subject: [PATCH 4/6] Add Kubernetes upgraded check tests Signed-off-by: Atanas Dinov --- .../controller/reconcile_kubernetes_test.go | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 internal/controller/reconcile_kubernetes_test.go diff --git a/internal/controller/reconcile_kubernetes_test.go b/internal/controller/reconcile_kubernetes_test.go new file mode 100644 index 0000000..0af3a9e --- /dev/null +++ b/internal/controller/reconcile_kubernetes_test.go @@ -0,0 +1,145 @@ +package controller + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" +) + +func TestIsKubernetesUpgraded(t *testing.T) { + const kubernetesVersion = "v1.30.3+k3s1" + + nodeLabels := map[string]string{ + "node-x": "z", + } + + tests := []struct { + name string + nodes *corev1.NodeList + selector labels.Selector + expectedUpgrade bool + }{ + { + name: "All matching nodes upgraded", + nodes: &corev1.NodeList{ + Items: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{Labels: nodeLabels}, + Spec: corev1.NodeSpec{Unschedulable: false}, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{KubeletVersion: "v1.30.3+k3s1"}}, + }, + { + ObjectMeta: metav1.ObjectMeta{Labels: nodeLabels}, + Spec: corev1.NodeSpec{Unschedulable: false}, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{KubeletVersion: "v1.30.3+k3s1"}}, + }, + { + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{KubeletVersion: "v1.28.12+k3s1"}}, + }, + }, + }, + selector: labels.SelectorFromSet(nodeLabels), + expectedUpgrade: true, + }, + { + name: "Unschedulable matching node", + nodes: &corev1.NodeList{ + Items: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{Labels: nodeLabels}, + Spec: corev1.NodeSpec{Unschedulable: true}, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{KubeletVersion: "v1.30.3+k3s1"}}, + }, + { + ObjectMeta: metav1.ObjectMeta{Labels: nodeLabels}, + Spec: corev1.NodeSpec{Unschedulable: false}, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{KubeletVersion: "v1.30.3+k3s1"}}, + }, + { + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{KubeletVersion: "v1.28.12+k3s1"}}, + }, + }, + }, + selector: labels.SelectorFromSet(nodeLabels), + expectedUpgrade: false, + }, + { + name: "Not ready matching node", + nodes: &corev1.NodeList{ + Items: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{Labels: nodeLabels}, + Spec: corev1.NodeSpec{Unschedulable: false}, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionFalse}}, + NodeInfo: corev1.NodeSystemInfo{KubeletVersion: "v1.30.3+k3s1"}}, + }, + { + ObjectMeta: metav1.ObjectMeta{Labels: nodeLabels}, + Spec: corev1.NodeSpec{Unschedulable: false}, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{KubeletVersion: "v1.30.3+k3s1"}}, + }, + { + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{KubeletVersion: "v1.28.12+k3s1"}}, + }, + }, + }, + selector: labels.SelectorFromSet(nodeLabels), + expectedUpgrade: false, + }, + { + name: "Matching node on older Kubernetes version", + nodes: &corev1.NodeList{ + Items: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{Labels: nodeLabels}, + Spec: corev1.NodeSpec{Unschedulable: false}, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{KubeletVersion: "v1.28.12+k3s1"}}, + }, + { + ObjectMeta: metav1.ObjectMeta{Labels: nodeLabels}, + Spec: corev1.NodeSpec{Unschedulable: false}, + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{KubeletVersion: "v1.30.3+k3s1"}}, + }, + { + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady, Status: corev1.ConditionTrue}}, + NodeInfo: corev1.NodeSystemInfo{KubeletVersion: "v1.28.12+k3s1"}}, + }, + }, + }, + selector: labels.SelectorFromSet(nodeLabels), + expectedUpgrade: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.expectedUpgrade, isKubernetesUpgraded(test.nodes, test.selector, kubernetesVersion)) + }) + } +} From cb463043b75f3eb1bb1b8e3a00e4e7fe8f87387d Mon Sep 17 00:00:00 2001 From: Atanas Dinov Date: Tue, 10 Sep 2024 11:56:11 +0300 Subject: [PATCH 5/6] Add control plane check tests Signed-off-by: Atanas Dinov --- .../controller/reconcile_kubernetes_test.go | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/internal/controller/reconcile_kubernetes_test.go b/internal/controller/reconcile_kubernetes_test.go index 0af3a9e..a17f039 100644 --- a/internal/controller/reconcile_kubernetes_test.go +++ b/internal/controller/reconcile_kubernetes_test.go @@ -143,3 +143,32 @@ func TestIsKubernetesUpgraded(t *testing.T) { }) } } + +func TestControlPlaneOnlyCluster(t *testing.T) { + assert.True(t, controlPlaneOnlyCluster(&corev1.NodeList{ + Items: []corev1.Node{ + {ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"node-role.kubernetes.io/control-plane": "true"}}}, + {ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"node-role.kubernetes.io/control-plane": "true"}}}, + }, + })) + + assert.False(t, controlPlaneOnlyCluster(&corev1.NodeList{ + Items: []corev1.Node{ + {ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"node-role.kubernetes.io/control-plane": "true"}}}, + {ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"node-role.kubernetes.io/control-plane": "false"}}}, + }, + })) + + assert.False(t, controlPlaneOnlyCluster(&corev1.NodeList{ + Items: []corev1.Node{ + {ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"node-role.kubernetes.io/control-plane": "true"}}}, + {ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{}}}, + }, + })) + + assert.False(t, controlPlaneOnlyCluster(&corev1.NodeList{ + Items: []corev1.Node{ + {ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{}}}, + }, + })) +} From 0e8e7ee81db80f3b2461951f7766d2b5adce7848 Mon Sep 17 00:00:00 2001 From: Atanas Dinov Date: Tue, 10 Sep 2024 12:04:32 +0300 Subject: [PATCH 6/6] Add target kubernetes version tests Signed-off-by: Atanas Dinov --- .../controller/reconcile_kubernetes_test.go | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/internal/controller/reconcile_kubernetes_test.go b/internal/controller/reconcile_kubernetes_test.go index a17f039..df1e803 100644 --- a/internal/controller/reconcile_kubernetes_test.go +++ b/internal/controller/reconcile_kubernetes_test.go @@ -4,6 +4,8 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + lifecyclev1alpha1 "github.com/suse-edge/upgrade-controller/api/v1alpha1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -172,3 +174,62 @@ func TestControlPlaneOnlyCluster(t *testing.T) { }, })) } + +func TestTargetKubernetesVersion(t *testing.T) { + kubernetes := &lifecyclev1alpha1.Kubernetes{ + K3S: lifecyclev1alpha1.KubernetesDistribution{ + Version: "v1.30.3+k3s1", + }, + RKE2: lifecyclev1alpha1.KubernetesDistribution{ + Version: "v1.30.3+rke2r1", + }, + } + + tests := []struct { + name string + nodes *corev1.NodeList + expectedVersion string + expectedError string + }{ + { + name: "Empty node list", + nodes: &corev1.NodeList{}, + expectedError: "unable to determine current kubernetes version due to empty node list", + }, + { + name: "Unsupported Kubernetes version", + nodes: &corev1.NodeList{ + Items: []corev1.Node{{Status: corev1.NodeStatus{NodeInfo: corev1.NodeSystemInfo{KubeletVersion: "v1.30.3"}}}}, + }, + expectedError: "upgrading from kubernetes version v1.30.3 is not supported", + }, + { + name: "Target k3s version", + nodes: &corev1.NodeList{ + Items: []corev1.Node{{Status: corev1.NodeStatus{NodeInfo: corev1.NodeSystemInfo{KubeletVersion: "v1.28.12+k3s1"}}}}, + }, + expectedVersion: "v1.30.3+k3s1", + }, + { + name: "Target RKE2 version", + nodes: &corev1.NodeList{ + Items: []corev1.Node{{Status: corev1.NodeStatus{NodeInfo: corev1.NodeSystemInfo{KubeletVersion: "v1.28.12+rke2r1"}}}}, + }, + expectedVersion: "v1.30.3+rke2r1", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + version, err := targetKubernetesVersion(test.nodes, kubernetes) + if test.expectedError != "" { + require.Error(t, err) + assert.EqualError(t, err, test.expectedError) + assert.Empty(t, version) + } else { + require.NoError(t, err) + assert.Equal(t, test.expectedVersion, version) + } + }) + } +}