github.com/kubewharf/katalyst-core@v0.5.3/pkg/webhook/mutating/pod/pod_test.go (about) 1 /* 2 Copyright 2022 The Katalyst 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 pod 18 19 import ( 20 "context" 21 "encoding/json" 22 "testing" 23 24 whcontext "github.com/slok/kubewebhook/pkg/webhook/context" 25 "github.com/stretchr/testify/assert" 26 admissionv1beta1 "k8s.io/api/admission/v1beta1" 27 appsv1 "k8s.io/api/apps/v1" 28 v1 "k8s.io/api/core/v1" 29 "k8s.io/apimachinery/pkg/api/resource" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/runtime/schema" 33 "k8s.io/utils/pointer" 34 35 apis "github.com/kubewharf/katalyst-api/pkg/apis/autoscaling/v1alpha1" 36 workloadapis "github.com/kubewharf/katalyst-api/pkg/apis/workload/v1alpha1" 37 apiconsts "github.com/kubewharf/katalyst-api/pkg/consts" 38 katalystbase "github.com/kubewharf/katalyst-core/cmd/base" 39 "github.com/kubewharf/katalyst-core/pkg/config/generic" 40 webhookconfig "github.com/kubewharf/katalyst-core/pkg/config/webhook" 41 "github.com/kubewharf/katalyst-core/pkg/util/native" 42 ) 43 44 var stsGVK = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "StatefulSet"} 45 46 func getPodJSON(pod *v1.Pod) []byte { 47 bs, _ := json.Marshal(pod) 48 return bs 49 } 50 51 func TestMutatePod(t *testing.T) { 52 t.Parallel() 53 54 container1 := &v1.Container{ 55 Name: "c1", 56 Resources: v1.ResourceRequirements{ 57 Limits: map[v1.ResourceName]resource.Quantity{ 58 v1.ResourceMemory: resource.MustParse("4Gi"), 59 v1.ResourceCPU: resource.MustParse("4"), 60 }, 61 Requests: map[v1.ResourceName]resource.Quantity{ 62 v1.ResourceMemory: resource.MustParse("2Gi"), 63 v1.ResourceCPU: resource.MustParse("2"), 64 }, 65 }, 66 } 67 sts1 := &appsv1.StatefulSet{ 68 TypeMeta: metav1.TypeMeta{ 69 Kind: stsGVK.Kind, 70 APIVersion: stsGVK.GroupVersion().String(), 71 }, 72 ObjectMeta: metav1.ObjectMeta{ 73 Namespace: "default", 74 Name: "sts1", 75 Annotations: map[string]string{ 76 apiconsts.WorkloadAnnotationVPAEnabledKey: apiconsts.WorkloadAnnotationVPAEnabled, 77 }, 78 }, 79 Spec: appsv1.StatefulSetSpec{ 80 Replicas: pointer.Int32(1), 81 Template: v1.PodTemplateSpec{ 82 Spec: v1.PodSpec{ 83 Containers: []v1.Container{*container1}, 84 }, 85 }, 86 Selector: &metav1.LabelSelector{ 87 MatchLabels: map[string]string{ 88 "workload": "sts", 89 }, 90 }, 91 }, 92 Status: appsv1.StatefulSetStatus{}, 93 } 94 95 vpa1 := &apis.KatalystVerticalPodAutoscaler{ 96 ObjectMeta: metav1.ObjectMeta{ 97 Namespace: "default", 98 Name: "vpa1", 99 }, 100 Spec: apis.KatalystVerticalPodAutoscalerSpec{ 101 TargetRef: apis.CrossVersionObjectReference{ 102 Kind: stsGVK.Kind, 103 Name: "sts1", 104 APIVersion: stsGVK.GroupVersion().String(), 105 }, 106 UpdatePolicy: apis.PodUpdatePolicy{ 107 PodUpdatingStrategy: "", 108 PodMatchingStrategy: apis.PodMatchingStrategyAll, 109 PodApplyStrategy: "", 110 }, 111 }, 112 Status: apis.KatalystVerticalPodAutoscalerStatus{ 113 ContainerResources: []apis.ContainerResources{ 114 { 115 ContainerName: pointer.String("c1"), 116 Requests: &apis.ContainerResourceList{ 117 Target: map[v1.ResourceName]resource.Quantity{ 118 v1.ResourceMemory: resource.MustParse("1Gi"), 119 v1.ResourceCPU: resource.MustParse("1"), 120 }, 121 UncappedTarget: map[v1.ResourceName]resource.Quantity{ 122 v1.ResourceMemory: resource.MustParse("1Gi"), 123 v1.ResourceCPU: resource.MustParse("1"), 124 }, 125 }, 126 }, 127 }, 128 }, 129 } 130 131 spd1 := &workloadapis.ServiceProfileDescriptor{ 132 TypeMeta: metav1.TypeMeta{}, 133 ObjectMeta: metav1.ObjectMeta{ 134 Namespace: "default", 135 Name: "spd1", 136 }, 137 Spec: workloadapis.ServiceProfileDescriptorSpec{ 138 TargetRef: apis.CrossVersionObjectReference{ 139 Kind: stsGVK.Kind, 140 Name: "sts1", 141 APIVersion: stsGVK.GroupVersion().String(), 142 }, 143 }, 144 Status: workloadapis.ServiceProfileDescriptorStatus{}, 145 } 146 147 pod1 := &v1.Pod{ 148 TypeMeta: metav1.TypeMeta{}, 149 ObjectMeta: metav1.ObjectMeta{ 150 Namespace: "default", 151 Name: "pod1", 152 Annotations: map[string]string{ 153 apiconsts.WorkloadAnnotationVPAEnabledKey: apiconsts.WorkloadAnnotationVPAEnabled, 154 }, 155 OwnerReferences: []metav1.OwnerReference{ 156 { 157 Name: "sts1", 158 Kind: stsGVK.Kind, 159 APIVersion: stsGVK.GroupVersion().String(), 160 }, 161 }, 162 }, 163 Spec: v1.PodSpec{ 164 Containers: []v1.Container{ 165 *container1, 166 }, 167 }, 168 Status: v1.PodStatus{}, 169 } 170 pod2 := &v1.Pod{ 171 TypeMeta: metav1.TypeMeta{}, 172 ObjectMeta: metav1.ObjectMeta{ 173 Namespace: "default", 174 Name: "pod2", 175 Annotations: map[string]string{ 176 "k1": "v1", 177 }, 178 OwnerReferences: []metav1.OwnerReference{ 179 { 180 Name: "sts1", 181 Kind: stsGVK.Kind, 182 APIVersion: stsGVK.GroupVersion().String(), 183 }, 184 }, 185 }, 186 Spec: v1.PodSpec{ 187 Containers: []v1.Container{ 188 *container1, 189 }, 190 }, 191 Status: v1.PodStatus{}, 192 } 193 194 for _, tc := range []struct { 195 name string 196 object runtime.Object 197 gvr string 198 vpa *apis.KatalystVerticalPodAutoscaler 199 spd *workloadapis.ServiceProfileDescriptor 200 pod *v1.Pod 201 review *admissionv1beta1.AdmissionReview 202 expPatch []string 203 }{ 204 { 205 name: "mutate pod resource and annotation", 206 object: sts1, 207 gvr: "statefulsets.v1.apps", 208 vpa: vpa1, 209 spd: spd1, 210 pod: pod1, 211 review: &admissionv1beta1.AdmissionReview{ 212 Request: &admissionv1beta1.AdmissionRequest{ 213 UID: "test", 214 Object: runtime.RawExtension{ 215 Raw: getPodJSON(pod1), 216 }, 217 }, 218 }, 219 expPatch: []string{ 220 `{"op":"replace","path":"/spec/containers/0/resources/requests/cpu","value":"1"}`, 221 `{"op":"replace","path":"/spec/containers/0/resources/requests/memory","value":"1Gi"}`, 222 }, 223 }, 224 { 225 name: "mutate pod(vpa not enabled) resource and annotation", 226 object: sts1, 227 gvr: "statefulsets.v1.apps", 228 vpa: vpa1, 229 spd: spd1, 230 pod: pod2, 231 review: &admissionv1beta1.AdmissionReview{ 232 Request: &admissionv1beta1.AdmissionRequest{ 233 UID: "test", 234 Object: runtime.RawExtension{ 235 Raw: getPodJSON(pod2), 236 }, 237 }, 238 }, 239 expPatch: []string{ 240 `{"op":"replace","path":"/spec/containers/0/resources/requests/cpu","value":"1"}`, 241 `{"op":"replace","path":"/spec/containers/0/resources/requests/memory","value":"1Gi"}`, 242 }, 243 }, 244 } { 245 tc := tc 246 t.Run(tc.name, func(t *testing.T) { 247 t.Parallel() 248 249 genericConf := &generic.GenericConfiguration{} 250 webhookGenericConf := webhookconfig.NewGenericWebhookConfiguration() 251 webhookGenericConf.DynamicGVResources = []string{ 252 "statefulsets.v1.apps", 253 "replicasets.v1.apps", 254 "deployments.v1.apps", 255 } 256 257 controlCtx, err := katalystbase.GenerateFakeGenericContext(nil, nil, []runtime.Object{tc.object}) 258 assert.NoError(t, err) 259 260 workloadInformers := controlCtx.DynamicResourcesManager.GetDynamicInformers() 261 262 u, err := native.ToUnstructured(tc.object) 263 assert.NoError(t, err) 264 err = workloadInformers[tc.gvr].Informer.Informer().GetStore().Add(u) 265 assert.NoError(t, err) 266 267 wh, _, err := NewWebhookPod(context.TODO(), controlCtx, genericConf, webhookGenericConf, nil) 268 assert.NoError(t, err) 269 270 vpaInformer := controlCtx.InternalInformerFactory.Autoscaling().V1alpha1().KatalystVerticalPodAutoscalers() 271 err = vpaInformer.Informer().GetStore().Add(tc.vpa) 272 assert.NoError(t, err) 273 274 spdInformer := controlCtx.InternalInformerFactory.Workload().V1alpha1().ServiceProfileDescriptors() 275 err = spdInformer.Informer().GetStore().Add(tc.spd) 276 assert.NoError(t, err) 277 278 podInformer := controlCtx.KubeInformerFactory.Core().V1().Pods() 279 err = podInformer.Informer().GetStore().Add(tc.pod) 280 assert.NoError(t, err) 281 282 ar := &admissionv1beta1.AdmissionRequest{ 283 Name: tc.pod.Name, 284 Namespace: "default", 285 } 286 287 ctx := context.TODO() 288 ctx = whcontext.SetAdmissionRequest(ctx, ar) 289 290 gotResponse := wh.Review(ctx, tc.review) 291 292 // Check uid, allowed and patch 293 assert.True(t, gotResponse.Allowed) 294 assert.Equal(t, tc.review.Request.UID, gotResponse.UID) 295 gotPatch := string(gotResponse.Patch) 296 for _, expPatchOp := range tc.expPatch { 297 assert.Contains(t, gotPatch, expPatchOp) 298 } 299 }) 300 } 301 }