Skip to content

Commit a2c8ea4

Browse files
committed
Expose PodAffinity and PodAntiAffinity struct and build overrides
This patch introduces a very basic struct to expose Pod Affinity/Antiaffinity and NodeAffinity interfaces as part of the Topology CR spec and it allows to patch the (opinionated) default that is currently applied to the services through DistributePods function call. Signed-off-by: Francesco Pantano <[email protected]>
1 parent d172b3a commit a2c8ea4

File tree

4 files changed

+212
-4
lines changed

4 files changed

+212
-4
lines changed

modules/common/affinity/affinity.go

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@ limitations under the License.
1717
package affinity
1818

1919
import (
20+
"encoding/json"
21+
"fmt"
2022
corev1 "k8s.io/api/core/v1"
23+
v1 "k8s.io/api/core/v1"
2124
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25+
"k8s.io/apimachinery/pkg/util/strategicpatch"
2226
)
2327

2428
// DistributePods - returns rule to ensure that two replicas of the same selector
@@ -27,8 +31,9 @@ func DistributePods(
2731
selectorKey string,
2832
selectorValues []string,
2933
topologyKey string,
30-
) *corev1.Affinity {
31-
return &corev1.Affinity{
34+
overrides *OverrideSpec,
35+
) (*corev1.Affinity, error) {
36+
defaultAffinity := &corev1.Affinity{
3237
PodAntiAffinity: &corev1.PodAntiAffinity{
3338
// This rule ensures that two replicas of the same selector
3439
// should not run if possible on the same worker node
@@ -53,4 +58,46 @@ func DistributePods(
5358
},
5459
},
5560
}
61+
// patch the default affinity Object with the data passed as input
62+
if overrides != nil {
63+
patchedAffinity, err := toCoreAffinity(defaultAffinity, overrides)
64+
return patchedAffinity, err
65+
}
66+
return defaultAffinity, nil
67+
}
68+
69+
func toCoreAffinity(
70+
affinity *v1.Affinity,
71+
override *OverrideSpec,
72+
) (*v1.Affinity, error) {
73+
74+
aff := &v1.Affinity{
75+
PodAntiAffinity: affinity.PodAntiAffinity,
76+
PodAffinity: affinity.PodAffinity,
77+
}
78+
if override != nil {
79+
if override != nil {
80+
origAffinit, err := json.Marshal(affinity)
81+
if err != nil {
82+
return aff, fmt.Errorf("error marshalling Affinity Spec: %w", err)
83+
}
84+
patch, err := json.Marshal(override)
85+
if err != nil {
86+
return aff, fmt.Errorf("error marshalling Affinity Spec: %w", err)
87+
}
88+
89+
patchedJSON, err := strategicpatch.StrategicMergePatch(origAffinit, patch, v1.Affinity{})
90+
if err != nil {
91+
return aff, fmt.Errorf("error patching Affinity Spec: %w", err)
92+
}
93+
94+
patchedSpec := v1.Affinity{}
95+
err = json.Unmarshal(patchedJSON, &patchedSpec)
96+
if err != nil {
97+
return aff, fmt.Errorf("error unmarshalling patched Service Spec: %w", err)
98+
}
99+
aff = &patchedSpec
100+
}
101+
}
102+
return aff, nil
56103
}

modules/common/affinity/affinity_test.go

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,82 @@ var affinityObj = &corev1.Affinity{
4747
},
4848
}
4949

50+
// weightedPodAffinityTermOverride represents an Override passed to the Affinity
51+
// tests
52+
var weightedPodAffinityTermOverride = []corev1.WeightedPodAffinityTerm{
53+
{
54+
PodAffinityTerm: corev1.PodAffinityTerm{
55+
LabelSelector: &metav1.LabelSelector{
56+
MatchExpressions: []metav1.LabelSelectorRequirement{
57+
{
58+
Key: "CustomKeySelector",
59+
Operator: metav1.LabelSelectorOpIn,
60+
Values: []string{
61+
"selectorValue1",
62+
"selectorValue2",
63+
"selectorValue3",
64+
},
65+
},
66+
},
67+
},
68+
TopologyKey: "CustomTopologyKey",
69+
},
70+
Weight: 80,
71+
},
72+
}
73+
5074
func TestDistributePods(t *testing.T) {
5175

5276
t.Run("Default pod distribution", func(t *testing.T) {
5377
g := NewWithT(t)
78+
d, _ := DistributePods("ThisSelector", []string{"selectorValue1", "selectorValue2"}, "ThisTopologyKey", nil)
79+
g.Expect(d).To(BeEquivalentTo(affinityObj))
80+
})
5481

55-
d := DistributePods("ThisSelector", []string{"selectorValue1", "selectorValue2"}, "ThisTopologyKey")
82+
// Override the default AntiAffinity
83+
t.Run("Pod distribution with overrides", func(t *testing.T) {
84+
// The resulting affinity that should be assigned to the Pod
85+
var expectedAffinity = &corev1.Affinity{
86+
PodAffinity: nil,
87+
NodeAffinity: nil,
88+
PodAntiAffinity: &corev1.PodAntiAffinity{
89+
PreferredDuringSchedulingIgnoredDuringExecution: weightedPodAffinityTermOverride,
90+
},
91+
}
92+
affinityOverride := &OverrideSpec{
93+
PodAffinity: nil,
94+
PodAntiAffinity: &corev1.PodAntiAffinity{
95+
PreferredDuringSchedulingIgnoredDuringExecution: weightedPodAffinityTermOverride,
96+
},
97+
NodeAffinity: nil,
98+
}
99+
g := NewWithT(t)
100+
d, _ := DistributePods("ThisSelector", []string{"selectorValue1", "selectorValue2"}, "ThisTopologyKey", affinityOverride)
101+
g.Expect(d).To(BeEquivalentTo(expectedAffinity))
102+
})
56103

57-
g.Expect(d).To(BeEquivalentTo(affinityObj))
104+
// Override the Affinity but keep the default AntiAffinity
105+
t.Run("Pod distribution with overrides", func(t *testing.T) {
106+
// The resulting affinity that should be assigned to the Pod
107+
var expectedAffinity = &corev1.Affinity{
108+
// the default PodAntiAffinity defined in the DistributePods function
109+
// is applied, while PodAffinity is the result of the override passed
110+
// as input
111+
PodAntiAffinity: affinityObj.PodAntiAffinity,
112+
NodeAffinity: nil,
113+
PodAffinity: &corev1.PodAffinity{
114+
PreferredDuringSchedulingIgnoredDuringExecution: weightedPodAffinityTermOverride,
115+
},
116+
}
117+
affinityOverride := &OverrideSpec{
118+
PodAntiAffinity: nil,
119+
PodAffinity: &corev1.PodAffinity{
120+
PreferredDuringSchedulingIgnoredDuringExecution: weightedPodAffinityTermOverride,
121+
},
122+
NodeAffinity: nil,
123+
}
124+
g := NewWithT(t)
125+
d, _ := DistributePods("ThisSelector", []string{"selectorValue1", "selectorValue2"}, "ThisTopologyKey", affinityOverride)
126+
g.Expect(d).To(BeEquivalentTo(expectedAffinity))
58127
})
59128
}

modules/common/affinity/types.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
Copyright 2024 Red Hat
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+
// +kubebuilder:object:generate:=true
18+
19+
package affinity
20+
21+
import (
22+
corev1 "k8s.io/api/core/v1"
23+
)
24+
25+
// OverrideSpec -
26+
type OverrideSpec struct {
27+
// Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)).
28+
// +optional
29+
PodAffinity *corev1.PodAffinity `json:"podAffinity,omitempty" protobuf:"bytes,2,opt,name=podAffinity"`
30+
// Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)).
31+
// +optional
32+
PodAntiAffinity *corev1.PodAntiAffinity `json:"podAntiAffinity,omitempty" protobuf:"bytes,3,opt,name=podAntiAffinity"`
33+
// Describes node affinity scheduling rules for the pod.
34+
// +optional
35+
NodeAffinity *corev1.NodeAffinity `json:"nodeAffinity,omitempty" protobuf:"bytes,1,opt,name=nodeAffinity"`
36+
}

modules/common/affinity/zz_generated.deepcopy.go

Lines changed: 56 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)