github.com/ironcore-dev/gardener-extension-provider-ironcore@v0.3.2-0.20240314231816-8336447fb9a0/pkg/webhook/controlplane/ensurer_test.go (about) 1 // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors 2 // SPDX-License-Identifier: Apache-2.0 3 4 package controlplane 5 6 import ( 7 "context" 8 "testing" 9 10 "github.com/Masterminds/semver/v3" 11 "github.com/coreos/go-systemd/v22/unit" 12 extensionscontroller "github.com/gardener/gardener/extensions/pkg/controller" 13 extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook" 14 gcontext "github.com/gardener/gardener/extensions/pkg/webhook/context" 15 "github.com/gardener/gardener/extensions/pkg/webhook/controlplane/genericmutator" 16 gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1" 17 v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants" 18 imagevectorutils "github.com/gardener/gardener/pkg/utils/imagevector" 19 testutils "github.com/gardener/gardener/pkg/utils/test" 20 . "github.com/onsi/ginkgo/v2" 21 . "github.com/onsi/gomega" 22 "go.uber.org/mock/gomock" 23 appsv1 "k8s.io/api/apps/v1" 24 corev1 "k8s.io/api/core/v1" 25 "k8s.io/apimachinery/pkg/api/resource" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/util/intstr" 28 vpaautoscalingv1 "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1" 29 "k8s.io/utils/ptr" 30 ) 31 32 const namespace = "test" 33 34 func TestController(t *testing.T) { 35 RegisterFailHandler(Fail) 36 RunSpecs(t, "ControlPlane Webhook Suite") 37 } 38 39 var _ = Describe("Ensurer", func() { 40 var ( 41 ctx = context.TODO() 42 43 ctrl *gomock.Controller 44 45 ensurer genericmutator.Ensurer 46 47 dummyContext = gcontext.NewGardenContext(nil, nil) 48 49 eContextK8s = gcontext.NewInternalGardenContext( 50 &extensionscontroller.Cluster{ 51 Shoot: &gardencorev1beta1.Shoot{ 52 Spec: gardencorev1beta1.ShootSpec{ 53 Kubernetes: gardencorev1beta1.Kubernetes{ 54 Version: "1.26.0", 55 }, 56 }, 57 }, 58 }, 59 ) 60 ) 61 62 BeforeEach(func() { 63 ctrl = gomock.NewController(GinkgoT()) 64 ensurer = NewEnsurer(logger, false) 65 }) 66 67 AfterEach(func() { 68 ctrl.Finish() 69 }) 70 71 Describe("#EnsureKubeAPIServerDeployment", func() { 72 var dep *appsv1.Deployment 73 74 BeforeEach(func() { 75 dep = &appsv1.Deployment{ 76 ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: v1beta1constants.DeploymentNameKubeAPIServer}, 77 Spec: appsv1.DeploymentSpec{ 78 Template: corev1.PodTemplateSpec{ 79 Spec: corev1.PodSpec{ 80 Containers: []corev1.Container{ 81 { 82 Name: "kube-apiserver", 83 }, 84 }, 85 }, 86 }, 87 }, 88 } 89 }) 90 91 It("should add missing elements to kube-apiserver deployment", func() { 92 err := ensurer.EnsureKubeAPIServerDeployment(ctx, eContextK8s, dep, nil) 93 Expect(err).To(Not(HaveOccurred())) 94 95 checkKubeAPIServerDeployment(dep) 96 }) 97 98 It("should modify existing elements of kube-apiserver deployment", func() { 99 dep = &appsv1.Deployment{ 100 ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: v1beta1constants.DeploymentNameKubeAPIServer}, 101 Spec: appsv1.DeploymentSpec{ 102 Template: corev1.PodTemplateSpec{ 103 Spec: corev1.PodSpec{ 104 Containers: []corev1.Container{ 105 { 106 Name: "kube-apiserver", 107 Command: []string{ 108 "--cloud-provider=?", 109 "--cloud-config=?", 110 }, 111 }, 112 }, 113 }, 114 }, 115 }, 116 } 117 118 err := ensurer.EnsureKubeAPIServerDeployment(ctx, eContextK8s, dep, nil) 119 Expect(err).To(Not(HaveOccurred())) 120 121 checkKubeAPIServerDeployment(dep) 122 }) 123 }) 124 125 Describe("#EnsureKubeControllerManagerDeployment", func() { 126 var dep *appsv1.Deployment 127 128 BeforeEach(func() { 129 dep = &appsv1.Deployment{ 130 ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: v1beta1constants.DeploymentNameKubeControllerManager}, 131 Spec: appsv1.DeploymentSpec{ 132 Template: corev1.PodTemplateSpec{ 133 Spec: corev1.PodSpec{ 134 Containers: []corev1.Container{ 135 { 136 Name: "kube-controller-manager", 137 }, 138 }, 139 }, 140 }, 141 }, 142 } 143 }) 144 145 It("should add missing elements to kube-controller-manager deployment", func() { 146 err := ensurer.EnsureKubeControllerManagerDeployment(ctx, eContextK8s, dep, nil) 147 Expect(err).To(Not(HaveOccurred())) 148 149 checkKubeControllerManagerDeployment(dep) 150 }) 151 152 It("should modify existing elements of kube-controller-manager deployment", func() { 153 dep = &appsv1.Deployment{ 154 ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: v1beta1constants.DeploymentNameKubeControllerManager}, 155 Spec: appsv1.DeploymentSpec{ 156 Template: corev1.PodTemplateSpec{ 157 ObjectMeta: metav1.ObjectMeta{ 158 Labels: map[string]string{ 159 v1beta1constants.LabelNetworkPolicyToBlockedCIDRs: v1beta1constants.LabelNetworkPolicyAllowed, 160 }, 161 }, 162 Spec: corev1.PodSpec{ 163 Containers: []corev1.Container{ 164 { 165 Name: "kube-controller-manager", 166 Command: []string{ 167 "--cloud-provider=?", 168 "--cloud-config=?", 169 }, 170 }, 171 }, 172 }, 173 }, 174 }, 175 } 176 177 err := ensurer.EnsureKubeControllerManagerDeployment(ctx, eContextK8s, dep, nil) 178 Expect(err).To(Not(HaveOccurred())) 179 180 checkKubeControllerManagerDeployment(dep) 181 }) 182 }) 183 184 Describe("#EnsureKubeletServiceUnitOptions", func() { 185 var ( 186 oldUnitOptions []*unit.UnitOption 187 hostnamectlUnitOption *unit.UnitOption 188 ) 189 190 BeforeEach(func() { 191 oldUnitOptions = []*unit.UnitOption{ 192 { 193 Section: "Service", 194 Name: "ExecStart", 195 Value: `/opt/bin/hyperkube kubelet \ 196 --config=/var/lib/kubelet/config/kubelet`, 197 }, 198 } 199 200 hostnamectlUnitOption = &unit.UnitOption{ 201 Section: "Service", 202 Name: "ExecStartPre", 203 Value: `/bin/sh -c 'hostnamectl set-hostname $(hostname -f)'`, 204 } 205 }) 206 207 It("should modify existing elements of kubelet.service unit options", 208 func() { 209 newUnitOptions := []*unit.UnitOption{ 210 { 211 Section: "Service", 212 Name: "ExecStart", 213 Value: "/opt/bin/hyperkube kubelet \\\n --config=/var/lib/kubelet/config/kubelet \\\n --cloud-provider=external", 214 }, 215 hostnamectlUnitOption, 216 } 217 218 opts, err := ensurer.EnsureKubeletServiceUnitOptions(ctx, dummyContext, semver.MustParse("1.23.0"), oldUnitOptions, nil) 219 Expect(err).To(Not(HaveOccurred())) 220 Expect(opts).To(Equal(newUnitOptions)) 221 }, 222 ) 223 }) 224 225 Describe("#EnsureMachineControllerManagerDeployment", func() { 226 var ( 227 ensurer genericmutator.Ensurer 228 deployment *appsv1.Deployment 229 ) 230 231 BeforeEach(func() { 232 deployment = &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Namespace: "foo"}} 233 }) 234 235 Context("when gardenlet manages MCM", func() { 236 BeforeEach(func() { 237 ensurer = NewEnsurer(logger, true) 238 DeferCleanup(testutils.WithVar(&ImageVector, imagevectorutils.ImageVector{{ 239 Name: "machine-controller-manager-provider-ironcore", 240 Repository: "foo", 241 Tag: ptr.To[string]("bar"), 242 }})) 243 }) 244 245 It("should inject the sidecar container", func() { 246 Expect(deployment.Spec.Template.Spec.Containers).To(BeEmpty()) 247 Expect(ensurer.EnsureMachineControllerManagerDeployment(ctx, nil, deployment, nil)).To(Succeed()) 248 Expect(deployment.Spec.Template.Spec.Containers).To(ConsistOf(corev1.Container{ 249 Name: "machine-controller-manager-provider-ironcore", 250 Image: "foo:bar", 251 ImagePullPolicy: corev1.PullIfNotPresent, 252 Command: []string{ 253 "./machine-controller", 254 "--control-kubeconfig=inClusterConfig", 255 "--machine-creation-timeout=20m", 256 "--machine-drain-timeout=2h", 257 "--machine-health-timeout=10m", 258 "--machine-safety-apiserver-statuscheck-timeout=30s", 259 "--machine-safety-apiserver-statuscheck-period=1m", 260 "--machine-safety-orphan-vms-period=30m", 261 "--namespace=" + deployment.Namespace, 262 "--port=10259", 263 "--target-kubeconfig=/var/run/secrets/gardener.cloud/shoot/generic-kubeconfig/kubeconfig", 264 "--v=3", 265 "--ironcore-kubeconfig=/etc/ironcore/kubeconfig", 266 }, 267 LivenessProbe: &corev1.Probe{ 268 ProbeHandler: corev1.ProbeHandler{ 269 HTTPGet: &corev1.HTTPGetAction{ 270 Path: "/healthz", 271 Port: intstr.FromInt(10259), 272 Scheme: "HTTP", 273 }, 274 }, 275 InitialDelaySeconds: 30, 276 TimeoutSeconds: 5, 277 PeriodSeconds: 10, 278 SuccessThreshold: 1, 279 FailureThreshold: 3, 280 }, 281 VolumeMounts: []corev1.VolumeMount{ 282 { 283 Name: "kubeconfig", 284 MountPath: "/var/run/secrets/gardener.cloud/shoot/generic-kubeconfig", 285 ReadOnly: true, 286 }, 287 { 288 Name: "cloudprovider", 289 MountPath: "/etc/ironcore", 290 ReadOnly: true, 291 }, 292 }, 293 })) 294 Expect(deployment.Spec.Template.Spec.Volumes).To(ContainElement(corev1.Volume{ 295 Name: "cloudprovider", 296 VolumeSource: corev1.VolumeSource{ 297 Secret: &corev1.SecretVolumeSource{ 298 SecretName: "cloudprovider", 299 }, 300 }, 301 })) 302 }) 303 }) 304 }) 305 306 Describe("#EnsureMachineControllerManagerVPA", func() { 307 var ( 308 ensurer genericmutator.Ensurer 309 vpa *vpaautoscalingv1.VerticalPodAutoscaler 310 ) 311 312 BeforeEach(func() { 313 vpa = &vpaautoscalingv1.VerticalPodAutoscaler{} 314 }) 315 316 Context("when gardenlet manages MCM", func() { 317 BeforeEach(func() { 318 ensurer = NewEnsurer(logger, true) 319 }) 320 321 It("should inject the sidecar container policy", func() { 322 Expect(vpa.Spec.ResourcePolicy).To(BeNil()) 323 Expect(ensurer.EnsureMachineControllerManagerVPA(ctx, nil, vpa, nil)).To(Succeed()) 324 325 ccv := vpaautoscalingv1.ContainerControlledValuesRequestsOnly 326 Expect(vpa.Spec.ResourcePolicy.ContainerPolicies).To(ConsistOf(vpaautoscalingv1.ContainerResourcePolicy{ 327 ContainerName: "machine-controller-manager-provider-ironcore", 328 ControlledValues: &ccv, 329 MinAllowed: corev1.ResourceList{ 330 corev1.ResourceCPU: resource.MustParse("30m"), 331 corev1.ResourceMemory: resource.MustParse("64Mi"), 332 }, 333 MaxAllowed: corev1.ResourceList{ 334 corev1.ResourceCPU: resource.MustParse("2"), 335 corev1.ResourceMemory: resource.MustParse("5G"), 336 }, 337 })) 338 }) 339 }) 340 }) 341 }) 342 343 func checkKubeAPIServerDeployment(dep *appsv1.Deployment) { 344 // Check that the kube-apiserver container still exists and contains all needed command line args, 345 c := extensionswebhook.ContainerWithName(dep.Spec.Template.Spec.Containers, "kube-apiserver") 346 Expect(c).To(Not(BeNil())) 347 348 Expect(c.Command).NotTo(ContainElement("--cloud-provider=onemtal")) 349 Expect(c.Command).NotTo(ContainElement("--cloud-config=/etc/kubernetes/cloudprovider/cloudprovider.conf")) 350 } 351 352 func checkKubeControllerManagerDeployment(dep *appsv1.Deployment) { 353 // Check that the kube-controller-manager container still exists and contains all needed command line args, 354 c := extensionswebhook.ContainerWithName(dep.Spec.Template.Spec.Containers, "kube-controller-manager") 355 Expect(c).To(Not(BeNil())) 356 357 Expect(c.Command).To(ContainElement("--cloud-provider=external")) 358 Expect(c.Command).NotTo(ContainElement("--cloud-config=/etc/kubernetes/cloudprovider/cloudprovider.conf")) 359 }