sigs.k8s.io/cluster-api@v1.7.1/controlplane/kubeadm/internal/controllers/upgrade_test.go (about) 1 /* 2 Copyright 2020 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package controllers 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 "time" 24 25 . "github.com/onsi/gomega" 26 corev1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/types" 29 "k8s.io/client-go/tools/record" 30 "k8s.io/utils/ptr" 31 ctrl "sigs.k8s.io/controller-runtime" 32 "sigs.k8s.io/controller-runtime/pkg/client" 33 34 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 35 bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" 36 "sigs.k8s.io/cluster-api/controlplane/kubeadm/internal" 37 "sigs.k8s.io/cluster-api/internal/test/builder" 38 "sigs.k8s.io/cluster-api/internal/util/ssa" 39 "sigs.k8s.io/cluster-api/util" 40 "sigs.k8s.io/cluster-api/util/collections" 41 ) 42 43 const UpdatedVersion string = "v1.17.4" 44 const Host string = "nodomain.example.com" 45 46 func TestKubeadmControlPlaneReconciler_RolloutStrategy_ScaleUp(t *testing.T) { 47 setup := func(t *testing.T, g *WithT) *corev1.Namespace { 48 t.Helper() 49 50 t.Log("Creating the namespace") 51 ns, err := env.CreateNamespace(ctx, "test-kcp-reconciler-rollout-scaleup") 52 g.Expect(err).ToNot(HaveOccurred()) 53 54 return ns 55 } 56 57 teardown := func(t *testing.T, g *WithT, ns *corev1.Namespace) { 58 t.Helper() 59 60 t.Log("Deleting the namespace") 61 g.Expect(env.Delete(ctx, ns)).To(Succeed()) 62 } 63 64 g := NewWithT(t) 65 namespace := setup(t, g) 66 defer teardown(t, g, namespace) 67 68 timeout := 30 * time.Second 69 70 cluster, kcp, genericInfrastructureMachineTemplate := createClusterWithControlPlane(namespace.Name) 71 g.Expect(env.Create(ctx, genericInfrastructureMachineTemplate, client.FieldOwner("manager"))).To(Succeed()) 72 cluster.UID = types.UID(util.RandomString(10)) 73 cluster.Spec.ControlPlaneEndpoint.Host = Host 74 cluster.Spec.ControlPlaneEndpoint.Port = 6443 75 cluster.Status.InfrastructureReady = true 76 kcp.UID = types.UID(util.RandomString(10)) 77 kcp.Spec.KubeadmConfigSpec.ClusterConfiguration = nil 78 kcp.Spec.Replicas = ptr.To[int32](1) 79 setKCPHealthy(kcp) 80 81 r := &KubeadmControlPlaneReconciler{ 82 Client: env, 83 SecretCachingClient: secretCachingClient, 84 recorder: record.NewFakeRecorder(32), 85 managementCluster: &fakeManagementCluster{ 86 Management: &internal.Management{Client: env}, 87 Workload: fakeWorkloadCluster{ 88 Status: internal.ClusterStatus{Nodes: 1}, 89 }, 90 }, 91 managementClusterUncached: &fakeManagementCluster{ 92 Management: &internal.Management{Client: env}, 93 Workload: fakeWorkloadCluster{ 94 Status: internal.ClusterStatus{Nodes: 1}, 95 }, 96 }, 97 ssaCache: ssa.NewCache(), 98 } 99 controlPlane := &internal.ControlPlane{ 100 KCP: kcp, 101 Cluster: cluster, 102 Machines: nil, 103 } 104 controlPlane.InjectTestManagementCluster(r.managementCluster) 105 106 result, err := r.initializeControlPlane(ctx, controlPlane) 107 g.Expect(result).To(BeComparableTo(ctrl.Result{Requeue: true})) 108 g.Expect(err).ToNot(HaveOccurred()) 109 110 // initial setup 111 initialMachine := &clusterv1.MachineList{} 112 g.Eventually(func(g Gomega) { 113 // Nb. This Eventually block also forces the cache to update so that subsequent 114 // reconcile and upgradeControlPlane calls use the updated cache and avoids flakiness in the test. 115 g.Expect(env.List(ctx, initialMachine, client.InNamespace(cluster.Namespace))).To(Succeed()) 116 g.Expect(initialMachine.Items).To(HaveLen(1)) 117 }, timeout).Should(Succeed()) 118 for i := range initialMachine.Items { 119 setMachineHealthy(&initialMachine.Items[i]) 120 } 121 122 // change the KCP spec so the machine becomes outdated 123 kcp.Spec.Version = UpdatedVersion 124 125 // run upgrade the first time, expect we scale up 126 needingUpgrade := collections.FromMachineList(initialMachine) 127 controlPlane.Machines = needingUpgrade 128 result, err = r.upgradeControlPlane(ctx, controlPlane, needingUpgrade) 129 g.Expect(result).To(BeComparableTo(ctrl.Result{Requeue: true})) 130 g.Expect(err).ToNot(HaveOccurred()) 131 bothMachines := &clusterv1.MachineList{} 132 g.Eventually(func(g Gomega) { 133 g.Expect(env.List(ctx, bothMachines, client.InNamespace(cluster.Namespace))).To(Succeed()) 134 g.Expect(bothMachines.Items).To(HaveLen(2)) 135 }, timeout).Should(Succeed()) 136 137 // run upgrade a second time, simulate that the node has not appeared yet but the machine exists 138 139 // Unhealthy control plane will be detected during reconcile loop and upgrade will never be called. 140 controlPlane = &internal.ControlPlane{ 141 KCP: kcp, 142 Cluster: cluster, 143 Machines: collections.FromMachineList(bothMachines), 144 } 145 controlPlane.InjectTestManagementCluster(r.managementCluster) 146 147 result, err = r.reconcile(context.Background(), controlPlane) 148 g.Expect(err).ToNot(HaveOccurred()) 149 g.Expect(result).To(BeComparableTo(ctrl.Result{RequeueAfter: preflightFailedRequeueAfter})) 150 g.Eventually(func(g Gomega) { 151 g.Expect(env.List(context.Background(), bothMachines, client.InNamespace(cluster.Namespace))).To(Succeed()) 152 g.Expect(bothMachines.Items).To(HaveLen(2)) 153 }, timeout).Should(Succeed()) 154 155 // manually increase number of nodes, make control plane healthy again 156 r.managementCluster.(*fakeManagementCluster).Workload.Status.Nodes++ 157 for i := range bothMachines.Items { 158 setMachineHealthy(&bothMachines.Items[i]) 159 } 160 controlPlane.Machines = collections.FromMachineList(bothMachines) 161 162 machinesRequireUpgrade := collections.Machines{} 163 for i := range bothMachines.Items { 164 if bothMachines.Items[i].Spec.Version != nil && *bothMachines.Items[i].Spec.Version != UpdatedVersion { 165 machinesRequireUpgrade[bothMachines.Items[i].Name] = &bothMachines.Items[i] 166 } 167 } 168 169 // run upgrade the second time, expect we scale down 170 result, err = r.upgradeControlPlane(ctx, controlPlane, machinesRequireUpgrade) 171 g.Expect(err).ToNot(HaveOccurred()) 172 g.Expect(result).To(BeComparableTo(ctrl.Result{Requeue: true})) 173 finalMachine := &clusterv1.MachineList{} 174 g.Eventually(func(g Gomega) { 175 g.Expect(env.List(ctx, finalMachine, client.InNamespace(cluster.Namespace))).To(Succeed()) 176 g.Expect(finalMachine.Items).To(HaveLen(1)) 177 // assert that the deleted machine is the initial machine 178 g.Expect(finalMachine.Items[0].Name).ToNot(Equal(initialMachine.Items[0].Name)) 179 }, timeout).Should(Succeed()) 180 } 181 182 func TestKubeadmControlPlaneReconciler_RolloutStrategy_ScaleDown(t *testing.T) { 183 version := "v1.17.3" 184 g := NewWithT(t) 185 186 cluster, kcp, tmpl := createClusterWithControlPlane(metav1.NamespaceDefault) 187 cluster.Spec.ControlPlaneEndpoint.Host = "nodomain.example.com1" 188 cluster.Spec.ControlPlaneEndpoint.Port = 6443 189 kcp.Spec.Replicas = ptr.To[int32](3) 190 kcp.Spec.RolloutStrategy.RollingUpdate.MaxSurge.IntVal = 0 191 setKCPHealthy(kcp) 192 193 fmc := &fakeManagementCluster{ 194 Machines: collections.Machines{}, 195 Workload: fakeWorkloadCluster{ 196 Status: internal.ClusterStatus{Nodes: 3}, 197 }, 198 } 199 objs := []client.Object{builder.GenericInfrastructureMachineTemplateCRD, cluster.DeepCopy(), kcp.DeepCopy(), tmpl.DeepCopy()} 200 for i := 0; i < 3; i++ { 201 name := fmt.Sprintf("test-%d", i) 202 m := &clusterv1.Machine{ 203 ObjectMeta: metav1.ObjectMeta{ 204 Namespace: cluster.Namespace, 205 Name: name, 206 Labels: internal.ControlPlaneMachineLabelsForCluster(kcp, cluster.Name), 207 }, 208 Spec: clusterv1.MachineSpec{ 209 Bootstrap: clusterv1.Bootstrap{ 210 ConfigRef: &corev1.ObjectReference{ 211 APIVersion: bootstrapv1.GroupVersion.String(), 212 Kind: "KubeadmConfig", 213 Name: name, 214 }, 215 }, 216 Version: &version, 217 }, 218 } 219 cfg := &bootstrapv1.KubeadmConfig{ 220 ObjectMeta: metav1.ObjectMeta{ 221 Namespace: cluster.Namespace, 222 Name: name, 223 }, 224 } 225 objs = append(objs, m, cfg) 226 fmc.Machines.Insert(m) 227 } 228 fakeClient := newFakeClient(objs...) 229 fmc.Reader = fakeClient 230 r := &KubeadmControlPlaneReconciler{ 231 Client: fakeClient, 232 SecretCachingClient: fakeClient, 233 managementCluster: fmc, 234 managementClusterUncached: fmc, 235 } 236 237 controlPlane := &internal.ControlPlane{ 238 KCP: kcp, 239 Cluster: cluster, 240 Machines: nil, 241 } 242 controlPlane.InjectTestManagementCluster(r.managementCluster) 243 244 result, err := r.reconcile(ctx, controlPlane) 245 g.Expect(result).To(BeComparableTo(ctrl.Result{})) 246 g.Expect(err).ToNot(HaveOccurred()) 247 248 machineList := &clusterv1.MachineList{} 249 g.Expect(fakeClient.List(ctx, machineList, client.InNamespace(cluster.Namespace))).To(Succeed()) 250 g.Expect(machineList.Items).To(HaveLen(3)) 251 for i := range machineList.Items { 252 setMachineHealthy(&machineList.Items[i]) 253 } 254 255 // change the KCP spec so the machine becomes outdated 256 kcp.Spec.Version = UpdatedVersion 257 258 // run upgrade, expect we scale down 259 needingUpgrade := collections.FromMachineList(machineList) 260 controlPlane.Machines = needingUpgrade 261 262 result, err = r.upgradeControlPlane(ctx, controlPlane, needingUpgrade) 263 g.Expect(result).To(BeComparableTo(ctrl.Result{Requeue: true})) 264 g.Expect(err).ToNot(HaveOccurred()) 265 remainingMachines := &clusterv1.MachineList{} 266 g.Expect(fakeClient.List(ctx, remainingMachines, client.InNamespace(cluster.Namespace))).To(Succeed()) 267 g.Expect(remainingMachines.Items).To(HaveLen(2)) 268 } 269 270 type machineOpt func(*clusterv1.Machine) 271 272 func machine(name string, opts ...machineOpt) *clusterv1.Machine { 273 m := &clusterv1.Machine{ 274 ObjectMeta: metav1.ObjectMeta{ 275 Name: name, 276 Namespace: metav1.NamespaceDefault, 277 }, 278 } 279 for _, opt := range opts { 280 opt(m) 281 } 282 return m 283 }