From 346dbf3df20a650ff9491f70a7a692fc9093d3e5 Mon Sep 17 00:00:00 2001 From: willie-yao Date: Wed, 24 Jan 2024 00:10:20 +0000 Subject: [PATCH] Add unit tests for MPs in topology/scope package Co-authored-by: razashahid107 --- .../topology/cluster/scope/state_test.go | 150 ++++- .../cluster/scope/upgradetracker_test.go | 5 +- internal/test/builder/builders.go | 30 + .../test/builder/zz_generated.deepcopy.go | 16 + internal/topology/check/compatibility_test.go | 608 +++++++++++++++++- 5 files changed, 803 insertions(+), 6 deletions(-) diff --git a/internal/controllers/topology/cluster/scope/state_test.go b/internal/controllers/topology/cluster/scope/state_test.go index 40a7a9d97a6c..ffbcd1f4b758 100644 --- a/internal/controllers/topology/cluster/scope/state_test.go +++ b/internal/controllers/topology/cluster/scope/state_test.go @@ -21,15 +21,17 @@ import ( "testing" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" "sigs.k8s.io/cluster-api/internal/test/builder" ) -func TestIsUpgrading(t *testing.T) { +func TestMDIsUpgrading(t *testing.T) { g := NewWithT(t) scheme := runtime.NewScheme() g.Expect(clusterv1.AddToScheme(scheme)).To(Succeed()) @@ -115,7 +117,7 @@ func TestIsUpgrading(t *testing.T) { } } -func TestUpgrading(t *testing.T) { +func TestMDUpgrading(t *testing.T) { g := NewWithT(t) scheme := runtime.NewScheme() g.Expect(clusterv1.AddToScheme(scheme)).To(Succeed()) @@ -155,3 +157,147 @@ func TestUpgrading(t *testing.T) { g.Expect(got).To(BeComparableTo(want)) }) } + +func TestMPIsUpgrading(t *testing.T) { + g := NewWithT(t) + scheme := runtime.NewScheme() + g.Expect(expv1.AddToScheme(scheme)).To(Succeed()) + g.Expect(corev1.AddToScheme(scheme)).To(Succeed()) + + tests := []struct { + name string + mp *expv1.MachinePool + nodes []*corev1.Node + want bool + wantErr bool + }{ + { + name: "should return false if all the nodes of MachinePool have the same version as the MachinePool", + mp: builder.MachinePool("ns", "mp1"). + WithClusterName("cluster1"). + WithVersion("v1.2.3"). + Build(), + nodes: []*corev1.Node{ + builder.Node("node1"). + WithStatus(corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + KubeletVersion: "v1.2.3", + }, + }). + Build(), + }, + want: false, + wantErr: false, + }, + { + name: "should return true if the MachinePool's node has a different kubelet version", + mp: builder.MachinePool("ns", "mp1"). + WithClusterName("cluster1"). + WithVersion("v1.2.3"). + WithStatus(expv1.MachinePoolStatus{ + NodeRefs: []corev1.ObjectReference{ + { + Name: "node1", + }, + }, + }). + Build(), + nodes: []*corev1.Node{ + builder.Node("node1"). + WithStatus(corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + KubeletVersion: "v1.2.2", + }, + }). + Build(), + }, + want: true, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + ctx := context.Background() + objs := []client.Object{} + objs = append(objs, tt.mp) + for _, n := range tt.nodes { + objs = append(objs, n) + } + fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objs...).Build() + mpState := &MachinePoolState{ + Object: tt.mp, + } + got, err := mpState.IsUpgrading(ctx, fakeClient) + if tt.wantErr { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(got).To(Equal(tt.want)) + } + }) + } +} + +func TestMPUpgrading(t *testing.T) { + g := NewWithT(t) + scheme := runtime.NewScheme() + g.Expect(expv1.AddToScheme(scheme)).To(Succeed()) + g.Expect(corev1.AddToScheme(scheme)).To(Succeed()) + + ctx := context.Background() + + t.Run("should return the names of the upgrading MachinePools", func(t *testing.T) { + stableMP := builder.MachinePool("ns", "stableMP"). + WithClusterName("cluster1"). + WithVersion("v1.2.3"). + WithStatus(expv1.MachinePoolStatus{ + NodeRefs: []corev1.ObjectReference{ + { + Name: "stableMP-node1", + }, + }, + }). + Build() + stableMPNode := builder.Node("stableMP-node1"). + WithStatus(corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + KubeletVersion: "v1.2.3", + }, + }). + Build() + + upgradingMP := builder.MachinePool("ns", "upgradingMP"). + WithClusterName("cluster2"). + WithVersion("v1.2.3"). + WithStatus(expv1.MachinePoolStatus{ + NodeRefs: []corev1.ObjectReference{ + { + Name: "upgradingMP-node1", + }, + }, + }). + Build() + upgradingMPNode := builder.Node("upgradingMP-node1"). + WithStatus(corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + KubeletVersion: "v1.2.2", + }, + }). + Build() + + objs := []client.Object{stableMP, stableMPNode, upgradingMP, upgradingMPNode} + fakeClient := fake.NewClientBuilder().WithObjects(objs...).WithScheme(scheme).Build() + + mpsStateMap := MachinePoolsStateMap{ + "stableMP": {Object: stableMP}, + "upgradingMP": {Object: upgradingMP}, + } + want := []string{"upgradingMP"} + + got, err := mpsStateMap.Upgrading(ctx, fakeClient) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(got).To(BeComparableTo(want)) + }) +} diff --git a/internal/controllers/topology/cluster/scope/upgradetracker_test.go b/internal/controllers/topology/cluster/scope/upgradetracker_test.go index 0bb13dc88a08..fd91e5347aa1 100644 --- a/internal/controllers/topology/cluster/scope/upgradetracker_test.go +++ b/internal/controllers/topology/cluster/scope/upgradetracker_test.go @@ -36,12 +36,12 @@ func TestNewUpgradeTracker(t *testing.T) { }, { name: "should set the value of 1 if given concurrency is less than 1", - options: []UpgradeTrackerOption{MaxMDUpgradeConcurrency(0)}, + options: []UpgradeTrackerOption{MaxMDUpgradeConcurrency(0), MaxMPUpgradeConcurrency(0)}, want: 1, }, { name: "should set the value to the given concurrency if the value is greater than 0", - options: []UpgradeTrackerOption{MaxMDUpgradeConcurrency(2)}, + options: []UpgradeTrackerOption{MaxMDUpgradeConcurrency(2), MaxMPUpgradeConcurrency(2)}, want: 2, }, } @@ -50,6 +50,7 @@ func TestNewUpgradeTracker(t *testing.T) { g := NewWithT(t) got := NewUpgradeTracker(tt.options...) g.Expect(got.MachineDeployments.maxUpgradeConcurrency).To(Equal(tt.want)) + g.Expect(got.MachinePools.maxUpgradeConcurrency).To(Equal(tt.want)) } }) } diff --git a/internal/test/builder/builders.go b/internal/test/builder/builders.go index 0b9cb6567e6b..115e5be1e5f3 100644 --- a/internal/test/builder/builders.go +++ b/internal/test/builder/builders.go @@ -1467,6 +1467,36 @@ func (c *TestControlPlaneBuilder) Build() *unstructured.Unstructured { return c.obj } +// NodeBuilder holds the variables required to build a Node. +type NodeBuilder struct { + name string + status corev1.NodeStatus +} + +// Node returns a NodeBuilder. +func Node(name string) *NodeBuilder { + return &NodeBuilder{ + name: name, + } +} + +// WithStatus adds Status to the NodeBuilder. +func (n *NodeBuilder) WithStatus(status corev1.NodeStatus) *NodeBuilder { + n.status = status + return n +} + +// Build produces a new Node from the information passed to the NodeBuilder. +func (n *NodeBuilder) Build() *corev1.Node { + obj := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: n.name, + }, + Status: n.status, + } + return obj +} + // MachinePoolBuilder holds the variables and objects needed to build a generic MachinePool. type MachinePoolBuilder struct { namespace string diff --git a/internal/test/builder/zz_generated.deepcopy.go b/internal/test/builder/zz_generated.deepcopy.go index 4ca1c64caf97..73cee564d01a 100644 --- a/internal/test/builder/zz_generated.deepcopy.go +++ b/internal/test/builder/zz_generated.deepcopy.go @@ -793,6 +793,22 @@ func (in *MachineSetBuilder) DeepCopy() *MachineSetBuilder { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeBuilder) DeepCopyInto(out *NodeBuilder) { + *out = *in + in.status.DeepCopyInto(&out.status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeBuilder. +func (in *NodeBuilder) DeepCopy() *NodeBuilder { + if in == nil { + return nil + } + out := new(NodeBuilder) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TestBootstrapConfigBuilder) DeepCopyInto(out *TestBootstrapConfigBuilder) { *out = *in diff --git a/internal/topology/check/compatibility_test.go b/internal/topology/check/compatibility_test.go index 4db1c1aeb624..6a91166ed768 100644 --- a/internal/topology/check/compatibility_test.go +++ b/internal/topology/check/compatibility_test.go @@ -465,6 +465,13 @@ func TestClusterClassesAreCompatible(t *testing.T) { WithBootstrapTemplate( builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). Build()). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("bb"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build()). Build(), desired: builder.ClusterClass(metav1.NamespaceDefault, "class1"). WithInfrastructureClusterTemplate( @@ -480,6 +487,13 @@ func TestClusterClassesAreCompatible(t *testing.T) { WithBootstrapTemplate( builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). Build()). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("bb"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build()). Build(), wantErr: false, }, @@ -499,6 +513,13 @@ func TestClusterClassesAreCompatible(t *testing.T) { WithBootstrapTemplate( builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). Build()). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("bb"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build()). Build(), desired: builder.ClusterClass(metav1.NamespaceDefault, "class1"). WithInfrastructureClusterTemplate( @@ -514,6 +535,13 @@ func TestClusterClassesAreCompatible(t *testing.T) { WithBootstrapTemplate( builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). Build()). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("bb"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build()). Build(), wantErr: true, }, @@ -549,6 +577,38 @@ func TestClusterClassesAreCompatible(t *testing.T) { Build(), wantErr: false, }, + { + name: "pass for incompatible ref in MachinePoolClass bootstrapTemplate clusterClasses", + current: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + refToUnstructured(ref)). + WithControlPlaneInfrastructureMachineTemplate( + refToUnstructured(ref)). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + refToUnstructured(ref)).Build()). + Build(), + desired: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + refToUnstructured(ref)). + WithControlPlaneInfrastructureMachineTemplate( + refToUnstructured(ref)). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + refToUnstructured(incompatibleRef)).Build()). + Build(), + wantErr: false, + }, { name: "pass if machineDeploymentClass is removed from ClusterClass", current: builder.ClusterClass(metav1.NamespaceDefault, "class1"). @@ -588,6 +648,45 @@ func TestClusterClassesAreCompatible(t *testing.T) { Build(), wantErr: false, }, + { + name: "pass if machinePoolClass is removed from ClusterClass", + current: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + refToUnstructured(ref)). + WithControlPlaneInfrastructureMachineTemplate( + refToUnstructured(ref)). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build(), + *builder.MachinePoolClass("bb"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build()). + Build(), + desired: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + refToUnstructured(ref)). + WithControlPlaneInfrastructureMachineTemplate( + refToUnstructured(ref)). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + refToUnstructured(incompatibleRef)).Build()). + Build(), + wantErr: false, + }, } for _, tt := range tests { g := NewWithT(t) @@ -748,6 +847,152 @@ func TestMachineDeploymentClassesAreCompatible(t *testing.T) { } } +func TestMachinePoolClassesAreCompatible(t *testing.T) { + ref := &corev1.ObjectReference{ + APIVersion: "group.test.io/foo", + Kind: "barTemplate", + Name: "baz", + Namespace: "default", + } + compatibleRef := &corev1.ObjectReference{ + APIVersion: "group.test.io/another-foo", + Kind: "barTemplate", + Name: "another-baz", + Namespace: "default", + } + incompatibleRef := &corev1.ObjectReference{ + APIVersion: "group.test.io/foo", + Kind: "another-barTemplate", + Name: "baz", + Namespace: "default", + } + + tests := []struct { + name string + current *clusterv1.ClusterClass + desired *clusterv1.ClusterClass + wantErr bool + }{ + { + name: "pass if MachinePoolClasses are compatible", + current: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + refToUnstructured(ref)). + WithControlPlaneInfrastructureMachineTemplate( + refToUnstructured(ref)). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + refToUnstructured(ref)). + WithBootstrapTemplate( + refToUnstructured(ref)). + Build()). + Build(), + desired: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + refToUnstructured(ref)). + WithControlPlaneInfrastructureMachineTemplate( + refToUnstructured(ref)). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + refToUnstructured(compatibleRef)). + WithBootstrapTemplate( + refToUnstructured(incompatibleRef)).Build()). + Build(), + wantErr: false, + }, + { + name: "pass if MachinePoolClass is removed from ClusterClass", + current: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + refToUnstructured(ref)). + WithControlPlaneInfrastructureMachineTemplate( + refToUnstructured(ref)). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build(), + *builder.MachinePoolClass("bb"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build()). + Build(), + desired: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + refToUnstructured(ref)). + WithControlPlaneInfrastructureMachineTemplate( + refToUnstructured(ref)). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + refToUnstructured(incompatibleRef)).Build()). + Build(), + wantErr: false, + }, + { + name: "error if MachinePoolClass has multiple incompatible references", + current: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + refToUnstructured(ref)). + WithControlPlaneInfrastructureMachineTemplate( + refToUnstructured(ref)). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + refToUnstructured(ref)). + WithBootstrapTemplate( + refToUnstructured(ref)). + Build(), + ). + Build(), + desired: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + refToUnstructured(incompatibleRef)). + WithControlPlaneInfrastructureMachineTemplate( + refToUnstructured(incompatibleRef)). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + refToUnstructured(incompatibleRef)). + WithBootstrapTemplate( + refToUnstructured(compatibleRef)).Build()). + Build(), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + allErrs := MachinePoolClassesAreCompatible(tt.current, tt.desired) + if tt.wantErr { + g.Expect(allErrs).ToNot(BeEmpty()) + return + } + g.Expect(allErrs).To(BeEmpty()) + }) + } +} + func TestMachineDeploymentClassesAreUnique(t *testing.T) { tests := []struct { name string @@ -856,6 +1101,114 @@ func TestMachineDeploymentClassesAreUnique(t *testing.T) { } } +func TestMachinePoolClassesAreUnique(t *testing.T) { + tests := []struct { + name string + clusterClass *clusterv1.ClusterClass + wantErr bool + }{ + { + name: "pass if MachinePoolClasses are unique", + clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()). + WithControlPlaneInfrastructureMachineTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "cpinfra1").Build()). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build(), + *builder.MachinePoolClass("bb"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build()). + Build(), + wantErr: false, + }, + { + name: "fail if MachinePoolClasses are duplicated", + clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()). + WithControlPlaneInfrastructureMachineTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "cpinfra1").Build()). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build(), + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build()). + Build(), + wantErr: true, + }, + { + name: "fail if multiple MachinePoolClasses are identical", + clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()). + WithControlPlaneInfrastructureMachineTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "cpinfra1").Build()). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build(), + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build(), + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build(), + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build(), + ). + Build(), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + allErrs := MachinePoolClassesAreUnique(tt.clusterClass) + if tt.wantErr { + g.Expect(allErrs).ToNot(BeEmpty()) + return + } + g.Expect(allErrs).To(BeEmpty()) + }) + } +} + func TestMachineDeploymentTopologiesAreUniqueAndDefinedInClusterClass(t *testing.T) { tests := []struct { name string @@ -1063,6 +1416,213 @@ func TestMachineDeploymentTopologiesAreUniqueAndDefinedInClusterClass(t *testing } } +func TestMachinePoolTopologiesAreUniqueAndDefinedInClusterClass(t *testing.T) { + tests := []struct { + name string + clusterClass *clusterv1.ClusterClass + cluster *clusterv1.Cluster + wantErr bool + }{ + { + name: "fail if MachinePoolTopologies name is empty", + clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build()). + Build(), + cluster: builder.Cluster("fooboo", "cluster1"). + WithTopology(builder.ClusterTopology(). + WithClass("foo"). + WithVersion("v1.19.1"). + WithMachinePool( + // The name should not be empty. + builder.MachinePoolTopology(""). + WithClass("aa"). + Build()). + Build()). + Build(), + wantErr: true, + }, + { + name: "pass if MachinePoolTopologies are unique and defined in ClusterClass", + clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()). + WithControlPlaneInfrastructureMachineTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "cpinfra1").Build()). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build()). + Build(), + cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). + WithTopology( + builder.ClusterTopology(). + WithClass("class1"). + WithVersion("v1.22.2"). + WithMachinePool( + builder.MachinePoolTopology("workers1"). + WithClass("aa"). + Build()). + Build()). + Build(), + wantErr: false, + }, + { + name: "fail if MachinePoolTopologies are unique but not defined in ClusterClass", + clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()). + WithControlPlaneInfrastructureMachineTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "cpinfra1").Build()). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build()). + Build(), + cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). + WithTopology( + builder.ClusterTopology(). + WithClass("class1"). + WithVersion("v1.22.2"). + WithMachinePool( + builder.MachinePoolTopology("workers1"). + WithClass("bb"). + Build()). + Build()). + Build(), + wantErr: true, + }, + { + name: "fail if MachinePoolTopologies are not unique but is defined in ClusterClass", + clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()). + WithControlPlaneInfrastructureMachineTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "cpinfra1").Build()). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build()). + Build(), + cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). + WithTopology( + builder.ClusterTopology(). + WithClass("class1"). + WithVersion("v1.22.2"). + WithMachinePool( + builder.MachinePoolTopology("workers1"). + WithClass("aa"). + Build()). + WithMachinePool( + builder.MachinePoolTopology("workers1"). + WithClass("aa"). + Build()). + Build()). + Build(), + wantErr: true, + }, + { + name: "pass if MachinePoolTopologies are unique and share a class that is defined in ClusterClass", + clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()). + WithControlPlaneInfrastructureMachineTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "cpinfra1").Build()). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build()). + Build(), + cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). + WithTopology( + builder.ClusterTopology(). + WithClass("class1"). + WithVersion("v1.22.2"). + WithMachinePool( + builder.MachinePoolTopology("workers1"). + WithClass("aa"). + Build()). + WithMachinePool( + builder.MachinePoolTopology("workers2"). + WithClass("aa"). + Build()). + Build()). + Build(), + wantErr: false, + }, + { + name: "fail if MachinePoolTopology name is longer than 63 characters", + clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithControlPlaneTemplate( + builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()). + WithControlPlaneInfrastructureMachineTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "cpinfra1").Build()). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()). + WithBootstrapTemplate( + builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()). + Build()). + Build(), + cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1"). + WithTopology( + builder.ClusterTopology(). + WithClass("class1"). + WithVersion("v1.22.2"). + WithMachinePool( + builder.MachinePoolTopology("machine-deployment-topology-name-that-has-longerthan63characterlooooooooooooooooooooooongname"). + WithClass("aa"). + Build()). + Build()). + Build(), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + allErrs := MachinePoolTopologiesAreValidAndDefinedInClusterClass(tt.cluster, tt.clusterClass) + if tt.wantErr { + g.Expect(allErrs).ToNot(BeEmpty()) + return + } + g.Expect(allErrs).To(BeEmpty()) + }) + } +} + func TestClusterClassReferencesAreValid(t *testing.T) { ref := &corev1.ObjectReference{ APIVersion: "group.test.io/foo", @@ -1098,11 +1658,18 @@ func TestClusterClassReferencesAreValid(t *testing.T) { WithBootstrapTemplate( refToUnstructured(ref)). Build()). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + refToUnstructured(ref)). + WithBootstrapTemplate( + refToUnstructured(ref)). + Build()). Build(), wantErr: false, }, { - name: "error if clusterClass has multiple invalid refs", + name: "error if clusterClass has multiple invalid md refs", clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). WithInfrastructureClusterTemplate( refToUnstructured(invalidRef)). @@ -1132,7 +1699,37 @@ func TestClusterClassReferencesAreValid(t *testing.T) { Build(), wantErr: true, }, - + { + name: "error if clusterClass has multiple invalid mp refs", + clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). + WithInfrastructureClusterTemplate( + refToUnstructured(invalidRef)). + WithControlPlaneTemplate( + refToUnstructured(invalidRef)). + WithControlPlaneInfrastructureMachineTemplate( + refToUnstructured(invalidRef)). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("a"). + WithInfrastructureTemplate( + refToUnstructured(invalidRef)). + WithBootstrapTemplate( + refToUnstructured(invalidRef)). + Build(), + *builder.MachinePoolClass("b"). + WithInfrastructureTemplate( + refToUnstructured(invalidRef)). + WithBootstrapTemplate( + refToUnstructured(invalidRef)). + Build(), + *builder.MachinePoolClass("c"). + WithInfrastructureTemplate( + refToUnstructured(invalidRef)). + WithBootstrapTemplate( + refToUnstructured(invalidRef)). + Build()). + Build(), + wantErr: true, + }, { name: "error if clusterClass has invalid ControlPlane ref", clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1"). @@ -1149,6 +1746,13 @@ func TestClusterClassReferencesAreValid(t *testing.T) { WithBootstrapTemplate( refToUnstructured(ref)). Build()). + WithWorkerMachinePoolClasses( + *builder.MachinePoolClass("aa"). + WithInfrastructureTemplate( + refToUnstructured(ref)). + WithBootstrapTemplate( + refToUnstructured(ref)). + Build()). Build(), wantErr: true, },