Skip to content

Commit

Permalink
salt: Add execution module to "compute" affinity
Browse files Browse the repository at this point in the history
Since we want to expose a really simple way to setup Pod affinity we do
not use the exact same syntax as what need to provided in the Kubernetes
objects, in order to convert from "simple syntax" to "kubernetes syntax"
we add this execution module

See: #3574
  • Loading branch information
TeddyAndrieux committed Nov 24, 2021
1 parent 1d534b8 commit 5c69302
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 1 deletion.
63 changes: 63 additions & 0 deletions salt/_modules/metalk8s_service_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,66 @@ def get_service_conf(
)

return merged_config


def get_pod_affinity(affinities, label_selector, namespaces):
"""Convert human readable affinity to real Kubernetes affinity
NOTE: This function only handle podAntiAffinity for the moment
Input look like
```yaml
podAntiAffinity:
hard:
- topologyKey: kubernetes.io/hostname
soft:
- topologyKey: my.topology.key/important
weight: 100
- topologyKey: my.topology.key/a.bit.less.important
```
Args:
value (dict): Simple dict affinity
label_selector (dict): Dict for label selector to match Pods for this affinity
namespaces (list): List of namespaces where those Pods sits
Returns:
A dict that can be used as `affinity` in a Kubernetes object
"""
if not isinstance(namespaces, list):
namespaces = [namespaces]

result = {}
res_pod_anti_affinity = {}

pod_anti_affinity = (affinities or {}).get("podAntiAffinity") or {}

for soft_affinity in pod_anti_affinity.get("soft") or []:
res_pod_anti_affinity.setdefault(
"preferredDuringSchedulingIgnoredDuringExecution", []
).append(
{
"weight": soft_affinity.get("weight", 1),
"podAffinityTerm": {
"labelSelector": {"matchLabels": label_selector},
"namespaces": namespaces,
"topologyKey": soft_affinity["topologyKey"],
},
}
)

for hard_affinity in pod_anti_affinity.get("hard") or []:
res_pod_anti_affinity.setdefault(
"requiredDuringSchedulingIgnoredDuringExecution", []
).append(
{
"labelSelector": {"matchLabels": label_selector},
"namespaces": namespaces,
"topologyKey": hard_affinity["topologyKey"],
}
)

if res_pod_anti_affinity:
result["podAntiAffinity"] = res_pod_anti_affinity

return result
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,90 @@ get_service_config:
kind: my_addon_kind
result: "Expected value my_kind for key kind, got my_addon_kind"
raises: True

get_pod_affinity:
# Success: Nominal no affinity
- affinities: {}
result: {}
- affinities: null
result: {}
- affinities:
podAntiAffinity: {}
result: {}
- affinities:
podAntiAffinity: null
result: {}
- affinities:
podAntiAffinity:
hard: null
soft: null
result: {}
- affinities:
podAntiAffinity:
hard: {}
soft: {}
result: {}
- affinities:
podAntiAffinity:
hard: null
result: {}

# Success: Nominal simple soft pod anti affinity on hostname
- &nominal_soft_anti_affinity
affinities:
podAntiAffinity:
soft:
- topologyKey: kubernetes.io/hostname
result:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- &hostname_soft_affinity
weight: 1
podAffinityTerm: &default_pod_affinity_term
labelSelector:
matchLabels:
app: my-app
namespaces:
- my-namespace
topologyKey: kubernetes.io/hostname

# Success: nominal, namespace as string
- <<: *nominal_soft_anti_affinity
namespaces: "my-namespace"

# Success: Hard pod anti affinity on hostname
- affinities:
podAntiAffinity:
hard:
- topologyKey: kubernetes.io/hostname
result:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- *default_pod_affinity_term

# Success: Soft and Hard pod anti affinities
- affinities:
podAntiAffinity:
soft:
- topologyKey: "my-first-soft-key"
- topologyKey: "my-second-soft-key"
weight: 42
hard:
- topologyKey: "my-first-hard-key"
- topologyKey: "my-second-hard-key"
result:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
<<: *default_pod_affinity_term
topologyKey: "my-first-soft-key"
- weight: 42
podAffinityTerm:
<<: *default_pod_affinity_term
topologyKey: "my-second-soft-key"
requiredDuringSchedulingIgnoredDuringExecution:
- <<: *default_pod_affinity_term
topologyKey: "my-first-hard-key"
- <<: *default_pod_affinity_term
topologyKey: "my-second-hard-key"
15 changes: 14 additions & 1 deletion salt/tests/unit/modules/test_metalk8s_service_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from _modules import metalk8s_service_configuration

from tests.unit import mixins
from tests.unit import mixins, utils


YAML_TESTS_FILE = os.path.join(
Expand Down Expand Up @@ -90,3 +90,16 @@ def test_get_service_conf(
result,
)
get_configmap_mock.assert_called_once()

@utils.parameterized_from_cases(YAML_TESTS_CASES["get_pod_affinity"])
def test_get_pod_affinity(self, result, **kwargs):
"""
Tests the return of `get_pod_affinity` function
"""
if not kwargs.get("label_selector"):
kwargs["label_selector"] = {"app": "my-app"}
if not kwargs.get("namespaces"):
kwargs["namespaces"] = ["my-namespace"]
self.assertEqual(
metalk8s_service_configuration.get_pod_affinity(**kwargs), result
)

0 comments on commit 5c69302

Please sign in to comment.