From 0f13e82038fb8f2facba6254de57bd83a2d08b4a Mon Sep 17 00:00:00 2001 From: Francesco Pantano Date: Mon, 18 Nov 2024 15:48:47 +0100 Subject: [PATCH] Expose PodAffinity and PodAntiAffinity struct and build overrides This patch introduces a very basic struct to expose Pod Affinity/Antiaffinity interfaces as part of the Topology CR spec and allows to patch the (opinionated) default that is currently applied to the services. Signed-off-by: Francesco Pantano --- modules/common/affinity/affinity.go | 49 +++++++++++++++++- modules/common/affinity/affinity_test.go | 4 +- modules/common/affinity/types.go | 33 ++++++++++++ .../common/affinity/zz_generated.deepcopy.go | 51 +++++++++++++++++++ 4 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 modules/common/affinity/types.go create mode 100644 modules/common/affinity/zz_generated.deepcopy.go diff --git a/modules/common/affinity/affinity.go b/modules/common/affinity/affinity.go index 0e188c47..df9d6e07 100644 --- a/modules/common/affinity/affinity.go +++ b/modules/common/affinity/affinity.go @@ -17,8 +17,12 @@ limitations under the License. package affinity import ( + "encoding/json" + "fmt" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/strategicpatch" ) // DistributePods - returns rule to ensure that two replicas of the same selector @@ -27,8 +31,9 @@ func DistributePods( selectorKey string, selectorValues []string, topologyKey string, + overrides *OverrideSpec, ) *corev1.Affinity { - return &corev1.Affinity{ + defaultAffinity := &corev1.Affinity{ PodAntiAffinity: &corev1.PodAntiAffinity{ // This rule ensures that two replicas of the same selector // should not run if possible on the same worker node @@ -53,4 +58,46 @@ func DistributePods( }, }, } + // patch the default affinity Object with the data passed as input + if overrides != nil { + patchedAffinity, _ := toCoreAffinity(defaultAffinity, overrides) + return patchedAffinity + } + return defaultAffinity +} + +func toCoreAffinity( + affinity *v1.Affinity, + override *OverrideSpec, +) (*v1.Affinity, error) { + + aff := &v1.Affinity{ + PodAntiAffinity: affinity.PodAntiAffinity, + PodAffinity: affinity.PodAffinity, + } + if override != nil { + if override != nil { + origAffinit, err := json.Marshal(affinity) + if err != nil { + return aff, fmt.Errorf("error marshalling Affinity Spec: %w", err) + } + patch, err := json.Marshal(override) + if err != nil { + return aff, fmt.Errorf("error marshalling Affinity Spec: %w", err) + } + + patchedJSON, err := strategicpatch.StrategicMergePatch(origAffinit, patch, v1.Affinity{}) + if err != nil { + return aff, fmt.Errorf("error patching Affinity Spec: %w", err) + } + + patchedSpec := v1.Affinity{} + err = json.Unmarshal(patchedJSON, &patchedSpec) + if err != nil { + return aff, fmt.Errorf("error unmarshalling patched Service Spec: %w", err) + } + aff = &patchedSpec + } + } + return aff, nil } diff --git a/modules/common/affinity/affinity_test.go b/modules/common/affinity/affinity_test.go index 16a142dd..f05ea786 100644 --- a/modules/common/affinity/affinity_test.go +++ b/modules/common/affinity/affinity_test.go @@ -51,9 +51,7 @@ func TestDistributePods(t *testing.T) { t.Run("Default pod distribution", func(t *testing.T) { g := NewWithT(t) - - d := DistributePods("ThisSelector", []string{"selectorValue1", "selectorValue2"}, "ThisTopologyKey") - + d := DistributePods("ThisSelector", []string{"selectorValue1", "selectorValue2"}, "ThisTopologyKey", nil) g.Expect(d).To(BeEquivalentTo(affinityObj)) }) } diff --git a/modules/common/affinity/types.go b/modules/common/affinity/types.go new file mode 100644 index 00000000..f5161adb --- /dev/null +++ b/modules/common/affinity/types.go @@ -0,0 +1,33 @@ +/* +Copyright 2024 Red Hat + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// +kubebuilder:object:generate:=true + +package affinity + +import ( + corev1 "k8s.io/api/core/v1" +) + +// OverrideSpec - +type OverrideSpec struct { + // Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + // +optional + PodAffinity *corev1.PodAffinity `json:"podAffinity,omitempty" protobuf:"bytes,2,opt,name=podAffinity"` + // Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + // +optional + PodAntiAffinity *corev1.PodAntiAffinity `json:"podAntiAffinity,omitempty" protobuf:"bytes,3,opt,name=podAntiAffinity"` +} diff --git a/modules/common/affinity/zz_generated.deepcopy.go b/modules/common/affinity/zz_generated.deepcopy.go new file mode 100644 index 00000000..a642c0aa --- /dev/null +++ b/modules/common/affinity/zz_generated.deepcopy.go @@ -0,0 +1,51 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package affinity + +import ( + "k8s.io/api/core/v1" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OverrideSpec) DeepCopyInto(out *OverrideSpec) { + *out = *in + if in.PodAffinity != nil { + in, out := &in.PodAffinity, &out.PodAffinity + *out = new(v1.PodAffinity) + (*in).DeepCopyInto(*out) + } + if in.PodAntiAffinity != nil { + in, out := &in.PodAntiAffinity, &out.PodAntiAffinity + *out = new(v1.PodAntiAffinity) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OverrideSpec. +func (in *OverrideSpec) DeepCopy() *OverrideSpec { + if in == nil { + return nil + } + out := new(OverrideSpec) + in.DeepCopyInto(out) + return out +}