@@ -15,17 +15,24 @@ limitations under the License.
1515package admission
1616
1717import (
18+ "bytes"
1819 "context"
1920 "maps"
2021 "net/http"
22+ "testing"
2123
2224 . "github.com/onsi/ginkgo/v2"
2325 . "github.com/onsi/gomega"
2426 "gomodules.xyz/jsonpatch/v2"
25-
2627 admissionv1 "k8s.io/api/admission/v1"
28+ corev1 "k8s.io/api/core/v1"
29+ "k8s.io/apimachinery/pkg/api/resource"
30+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2731 "k8s.io/apimachinery/pkg/runtime"
2832 "k8s.io/apimachinery/pkg/runtime/schema"
33+ "k8s.io/apimachinery/pkg/runtime/serializer/json"
34+ "k8s.io/apimachinery/pkg/util/intstr"
35+ "k8s.io/utils/ptr"
2936)
3037
3138var _ = Describe ("Defaulter Handler" , func () {
@@ -167,3 +174,174 @@ func (d *TestCustomDefaulter) Default(ctx context.Context, obj runtime.Object) e
167174 o .TotalReplicas = 0
168175 return nil
169176}
177+
178+ func BenchmarkDefaulter (b * testing.B ) {
179+ scheme := runtime .NewScheme ()
180+ _ = corev1 .AddToScheme (scheme )
181+
182+ b .Run ("noop" , func (b * testing.B ) {
183+ req := createTestRequest ("test-hostname" )
184+ handler := WithDefaulter (scheme , & PodDefaulter {
185+ defaultHostname : "test-hostname" , // defaulting will be no-op as hostname is already "test-hostname"
186+ })
187+ b .ResetTimer ()
188+ b .ReportAllocs ()
189+ for i := 0 ; i < b .N ; i ++ {
190+ _ = handler .Handle (b .Context (), req )
191+ }
192+ })
193+
194+ b .Run ("with changes" , func (b * testing.B ) {
195+ req := createTestRequest ("" )
196+ handler := WithDefaulter (scheme , & PodDefaulter {
197+ defaultHostname : "test-hostname" , // will be used to default hostname
198+ })
199+ b .ResetTimer ()
200+ b .ReportAllocs ()
201+ for i := 0 ; i < b .N ; i ++ {
202+ _ = handler .Handle (b .Context (), req )
203+ }
204+ })
205+ }
206+
207+ type PodDefaulter struct {
208+ defaultHostname string
209+ }
210+
211+ func (p PodDefaulter ) Default (_ context.Context , pod * corev1.Pod ) error {
212+ if pod .Spec .Hostname == "" && p .defaultHostname != "" {
213+ pod .Spec .Hostname = p .defaultHostname
214+ }
215+ return nil
216+ }
217+
218+ func createTestRequest (hostname string ) Request {
219+ return Request {
220+ AdmissionRequest : admissionv1.AdmissionRequest {
221+ UID : "12345" ,
222+ Kind : metav1.GroupVersionKind {
223+ Group : "" ,
224+ Version : "v1" ,
225+ Kind : "Pod" ,
226+ },
227+ Object : runtime.RawExtension {Raw : mustEncodeLikeAPIServer (createSuperPod (hostname ))},
228+ Operation : admissionv1 .Create ,
229+ },
230+ }
231+ }
232+
233+ func createSuperPod (hostname string ) * corev1.Pod {
234+ return & corev1.Pod {
235+ ObjectMeta : metav1.ObjectMeta {
236+ Name : "monolith-service-7f8d9b" ,
237+ Namespace : "prod" ,
238+ Labels : map [string ]string {
239+ "app" : "processor" , "env" : "prod" , "version" : "1.4.2" ,
240+ "pci-compliant" : "true" , "team" : "data-eng" ,
241+ },
242+ Annotations : map [string ]string {
243+ "agent-inject" : "true" ,
244+ "checksum/config" : "e3b0c23238fc1c149afbf4c8996fb92427ae" ,
245+ },
246+ Finalizers : []string {"cleanup.finalizer.my.io" },
247+ },
248+ Spec : corev1.PodSpec {
249+ Hostname : hostname ,
250+ InitContainers : []corev1.Container {
251+ {
252+ Name : "vault-init" , Image : "vault:1.13.1" ,
253+ SecurityContext : & corev1.SecurityContext {RunAsUser : ptr.To [int64 ](1000 )},
254+ },
255+ },
256+ Containers : []corev1.Container {
257+ {
258+ Name : "main-app" ,
259+ Image : "my-priv-reg.io/analytics:v1.4.2" ,
260+ Env : []corev1.EnvVar {
261+ {Name : "DB_URL" , Value : "postgres://db:5432" },
262+ {
263+ Name : "POD_IP" ,
264+ ValueFrom : & corev1.EnvVarSource {
265+ FieldRef : & corev1.ObjectFieldSelector {FieldPath : "status.podIP" },
266+ },
267+ },
268+ },
269+ Resources : corev1.ResourceRequirements {
270+ Limits : corev1.ResourceList {corev1 .ResourceCPU : resource .MustParse ("2" )},
271+ Requests : corev1.ResourceList {corev1 .ResourceCPU : resource .MustParse ("1" )},
272+ },
273+ LivenessProbe : & corev1.Probe {
274+ ProbeHandler : corev1.ProbeHandler {
275+ HTTPGet : & corev1.HTTPGetAction {Path : "/health" , Port : intstr .FromInt (8080 )},
276+ },
277+ InitialDelaySeconds : 30 ,
278+ },
279+ },
280+ {
281+ Name : "log-shipper" ,
282+ Image : "fluentbit:2.1.4" ,
283+ Args : []string {"--config" , "/etc/fluent-bit/fluent-bit.conf" },
284+ },
285+ },
286+ Affinity : & corev1.Affinity {
287+ NodeAffinity : & corev1.NodeAffinity {
288+ RequiredDuringSchedulingIgnoredDuringExecution : & corev1.NodeSelector {
289+ NodeSelectorTerms : []corev1.NodeSelectorTerm {
290+ {
291+ MatchExpressions : []corev1.NodeSelectorRequirement {
292+ {Key : "topology.kubernetes.io/zone" , Operator : corev1 .NodeSelectorOpIn , Values : []string {"us-east-1a" , "us-east-1b" }},
293+ },
294+ },
295+ },
296+ },
297+ },
298+ },
299+ TopologySpreadConstraints : []corev1.TopologySpreadConstraint {
300+ {
301+ MaxSkew : 1 ,
302+ TopologyKey : "kubernetes.io/hostname" ,
303+ WhenUnsatisfiable : corev1 .DoNotSchedule ,
304+ LabelSelector : & metav1.LabelSelector {MatchLabels : map [string ]string {"app" : "processor" }},
305+ },
306+ },
307+ SecurityContext : & corev1.PodSecurityContext {
308+ RunAsNonRoot : ptr .To (true ),
309+ SeccompProfile : & corev1.SeccompProfile {Type : corev1 .SeccompProfileTypeRuntimeDefault },
310+ },
311+ TerminationGracePeriodSeconds : ptr.To [int64 ](60 ),
312+ ReadinessGates : []corev1.PodReadinessGate {
313+ {ConditionType : "target-health.lbv3.k8s.cloud/my-tg" },
314+ },
315+ Volumes : []corev1.Volume {
316+ {
317+ Name : "config" ,
318+ VolumeSource : corev1.VolumeSource {
319+ ConfigMap : & corev1.ConfigMapVolumeSource {
320+ LocalObjectReference : corev1.LocalObjectReference {Name : "app-config" },
321+ },
322+ },
323+ },
324+ },
325+ },
326+ }
327+ }
328+
329+ func mustEncodeLikeAPIServer (obj runtime.Object ) []byte {
330+ // 1. Create a NewSerializer
331+ // Options: false (not pretty printed), false (not YAML)
332+ s := json .NewSerializerWithOptions (
333+ json .DefaultMetaFactory ,
334+ nil ,
335+ nil ,
336+ json.SerializerOptions {Yaml : false , Pretty : false , Strict : false },
337+ )
338+
339+ // 2. Use a Buffer for efficiency
340+ buf := new (bytes.Buffer )
341+ err := s .Encode (obj , buf )
342+ if err != nil {
343+ panic (err )
344+ }
345+
346+ return buf .Bytes ()
347+ }
0 commit comments