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  }