sigs.k8s.io/cluster-api@v1.7.1/controlplane/kubeadm/internal/webhooks/scale_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 webhooks 18 19 import ( 20 "context" 21 "testing" 22 "time" 23 24 . "github.com/onsi/gomega" 25 admissionv1 "k8s.io/api/admission/v1" 26 corev1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/util/intstr" 30 "k8s.io/apimachinery/pkg/util/uuid" 31 "k8s.io/utils/ptr" 32 "sigs.k8s.io/controller-runtime/pkg/client/fake" 33 "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 34 35 bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" 36 controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" 37 ) 38 39 func init() { 40 scheme = runtime.NewScheme() 41 _ = controlplanev1.AddToScheme(scheme) 42 _ = admissionv1.AddToScheme(scheme) 43 } 44 45 var ( 46 scheme *runtime.Scheme 47 ) 48 49 func TestKubeadmControlPlaneValidateScale(t *testing.T) { 50 kcpManagedEtcd := &controlplanev1.KubeadmControlPlane{ 51 ObjectMeta: metav1.ObjectMeta{ 52 Name: "kcp-managed-etcd", 53 Namespace: "foo", 54 }, 55 Spec: controlplanev1.KubeadmControlPlaneSpec{ 56 MachineTemplate: controlplanev1.KubeadmControlPlaneMachineTemplate{ 57 InfrastructureRef: corev1.ObjectReference{ 58 APIVersion: "test/v1alpha1", 59 Kind: "UnknownInfraMachine", 60 Namespace: "foo", 61 Name: "infraTemplate", 62 }, 63 NodeDrainTimeout: &metav1.Duration{Duration: time.Second}, 64 }, 65 Replicas: ptr.To[int32](1), 66 RolloutStrategy: &controlplanev1.RolloutStrategy{ 67 Type: controlplanev1.RollingUpdateStrategyType, 68 RollingUpdate: &controlplanev1.RollingUpdate{ 69 MaxSurge: &intstr.IntOrString{ 70 IntVal: 1, 71 }, 72 }, 73 }, 74 KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ 75 InitConfiguration: &bootstrapv1.InitConfiguration{ 76 LocalAPIEndpoint: bootstrapv1.APIEndpoint{ 77 AdvertiseAddress: "127.0.0.1", 78 BindPort: int32(443), 79 }, 80 NodeRegistration: bootstrapv1.NodeRegistrationOptions{ 81 Name: "kcp-managed-etcd", 82 }, 83 }, 84 ClusterConfiguration: &bootstrapv1.ClusterConfiguration{ 85 ClusterName: "kcp-managed-etcd", 86 DNS: bootstrapv1.DNS{ 87 ImageMeta: bootstrapv1.ImageMeta{ 88 ImageRepository: "registry.k8s.io/coredns", 89 ImageTag: "1.6.5", 90 }, 91 }, 92 }, 93 JoinConfiguration: &bootstrapv1.JoinConfiguration{ 94 Discovery: bootstrapv1.Discovery{ 95 Timeout: &metav1.Duration{ 96 Duration: 10 * time.Minute, 97 }, 98 }, 99 NodeRegistration: bootstrapv1.NodeRegistrationOptions{ 100 Name: "kcp-managed-etcd", 101 }, 102 }, 103 PreKubeadmCommands: []string{ 104 "kcp-managed-etcd", "foo", 105 }, 106 PostKubeadmCommands: []string{ 107 "kcp-managed-etcd", "foo", 108 }, 109 Files: []bootstrapv1.File{ 110 { 111 Path: "kcp-managed-etcd", 112 }, 113 }, 114 Users: []bootstrapv1.User{ 115 { 116 Name: "user", 117 SSHAuthorizedKeys: []string{ 118 "ssh-rsa foo", 119 }, 120 }, 121 }, 122 NTP: &bootstrapv1.NTP{ 123 Servers: []string{"test-server-1", "test-server-2"}, 124 Enabled: ptr.To(true), 125 }, 126 }, 127 Version: "v1.16.6", 128 }, 129 } 130 131 kcpExternalEtcd := kcpManagedEtcd.DeepCopy() 132 kcpExternalEtcd.ObjectMeta.Name = "kcp-external-etcd" 133 kcpExternalEtcd.Spec.KubeadmConfigSpec.ClusterConfiguration.Etcd.External = &bootstrapv1.ExternalEtcd{} 134 135 tests := []struct { 136 name string 137 admissionRequest admission.Request 138 expectRespAllowed bool 139 expectRespMessage string 140 }{ 141 { 142 name: "should return error when trying to scale to zero", 143 expectRespAllowed: false, 144 expectRespMessage: "replicas cannot be 0", 145 admissionRequest: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{ 146 UID: uuid.NewUUID(), 147 Kind: metav1.GroupVersionKind{Group: "autoscaling", Version: "v1", Kind: "Scale"}, 148 Operation: admissionv1.Update, 149 Object: runtime.RawExtension{Raw: []byte(`{"metadata":{"name":"kcp-managed-etcd","namespace":"foo"},"spec":{"replicas":0}}`)}, 150 }}, 151 }, 152 { 153 name: "should return error when trying to scale to even number of replicas with managed etcd", 154 expectRespAllowed: false, 155 expectRespMessage: "replicas cannot be an even number when etcd is stacked", 156 admissionRequest: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{ 157 UID: uuid.NewUUID(), 158 Kind: metav1.GroupVersionKind{Group: "autoscaling", Version: "v1", Kind: "Scale"}, 159 Operation: admissionv1.Update, 160 Object: runtime.RawExtension{Raw: []byte(`{"metadata":{"name":"kcp-managed-etcd","namespace":"foo"},"spec":{"replicas":2}}`)}, 161 }}, 162 }, 163 { 164 name: "should allow odd number of replicas with managed etcd", 165 expectRespAllowed: true, 166 expectRespMessage: "", 167 admissionRequest: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{ 168 UID: uuid.NewUUID(), 169 Kind: metav1.GroupVersionKind{Group: "autoscaling", Version: "v1", Kind: "Scale"}, 170 Operation: admissionv1.Update, 171 Object: runtime.RawExtension{Raw: []byte(`{"metadata":{"name":"kcp-managed-etcd","namespace":"foo"},"spec":{"replicas":3}}`)}, 172 }}, 173 }, 174 { 175 name: "should allow even number of replicas with external etcd", 176 expectRespAllowed: true, 177 expectRespMessage: "", 178 admissionRequest: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{ 179 UID: uuid.NewUUID(), 180 Kind: metav1.GroupVersionKind{Group: "autoscaling", Version: "v1", Kind: "Scale"}, 181 Operation: admissionv1.Update, 182 Object: runtime.RawExtension{Raw: []byte(`{"metadata":{"name":"kcp-external-etcd","namespace":"foo"},"spec":{"replicas":4}}`)}, 183 }}, 184 }, 185 { 186 name: "should allow odd number of replicas with external etcd", 187 expectRespAllowed: true, 188 expectRespMessage: "", 189 admissionRequest: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{ 190 UID: uuid.NewUUID(), 191 Kind: metav1.GroupVersionKind{Group: "autoscaling", Version: "v1", Kind: "Scale"}, 192 Operation: admissionv1.Update, 193 Object: runtime.RawExtension{Raw: []byte(`{"metadata":{"name":"kcp-external-etcd","namespace":"foo"},"spec":{"replicas":3}}`)}, 194 }}, 195 }, 196 } 197 for _, tt := range tests { 198 t.Run(tt.name, func(t *testing.T) { 199 g := NewWithT(t) 200 201 fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(kcpManagedEtcd, kcpExternalEtcd).Build() 202 203 // Create the webhook and add the fakeClient as its client. 204 scaleHandler := ScaleValidator{ 205 Client: fakeClient, 206 decoder: admission.NewDecoder(scheme), 207 } 208 209 resp := scaleHandler.Handle(context.Background(), tt.admissionRequest) 210 g.Expect(resp.Allowed).Should(Equal(tt.expectRespAllowed)) 211 g.Expect(resp.Result.Message).Should(Equal(tt.expectRespMessage)) 212 }) 213 } 214 }