From: Joseph Sawaya Date: Fri, 3 Sep 2021 14:24:24 +0000 (-0400) Subject: mgr/rook: translate placement spec to node selector and vice versa X-Git-Tag: v17.1.0~869^2~1 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=58d13a5dc44731b3f33450896790dd33f4c2bd53;p=ceph.git mgr/rook: translate placement spec to node selector and vice versa This commit creates methods for translating PlacementSpecs to NodeSelectors and vice versa, this is a general method that would be used by orch commands that have a placement option. It's able to translate specs that specify a label, hosts and the host_pattern '*'. Signed-off-by: Joseph Sawaya --- diff --git a/src/pybind/mgr/rook/rook_cluster.py b/src/pybind/mgr/rook/rook_cluster.py index c0386c849d77..f5cecfa52bcb 100644 --- a/src/pybind/mgr/rook/rook_cluster.py +++ b/src/pybind/mgr/rook/rook_cluster.py @@ -23,7 +23,7 @@ from urllib3.exceptions import ProtocolError from ceph.deployment.inventory import Device from ceph.deployment.drive_group import DriveGroupSpec -from ceph.deployment.service_spec import ServiceSpec, NFSServiceSpec, RGWSpec +from ceph.deployment.service_spec import ServiceSpec, NFSServiceSpec, RGWSpec, PlacementSpec, HostPlacementSpec from ceph.utils import datetime_now from ceph.deployment.drive_selection.matchers import SizeMatcher from mgr_module import NFS_POOL_NAME @@ -1264,3 +1264,45 @@ class RookCluster(object): def blink_light(self, ident_fault, on, locs): # type: (str, bool, List[orchestrator.DeviceLightLoc]) -> List[str] return [self._execute_blight_job(ident_fault, on, loc) for loc in locs] + +def placement_spec_to_node_selector(spec: PlacementSpec, all_hosts: List) -> ccl.NodeSelectorTermsItem: + all_hostnames = [hs.hostname for hs in all_hosts] + res = ccl.NodeSelectorTermsItem(matchExpressions=ccl.MatchExpressionsList()) + if spec.host_pattern and spec.host_pattern != "*": + raise RuntimeError("The Rook orchestrator only supports a host_pattern of * for placements") + if spec.label: + res.matchExpressions.append( + ccl.MatchExpressionsItem( + key="ceph-label/" + spec.label, + operator="Exists" + ) + ) + if spec.hosts: + host_list = [h.hostname for h in spec.hosts if h.hostname in all_hostnames] + res.matchExpressions.append( + ccl.MatchExpressionsItem( + key="kubernetes.io/hostname", + operator="In", + values=ccl.CrdObjectList(host_list) + ) + ) + if spec.host_pattern == "*": + res.matchExpressions.append( + ccl.MatchExpressionsItem( + key="kubernetes.io/hostname", + operator="Exists", + ) + ) + return res + +def node_selector_to_placement_spec(node_selector: ccl.NodeSelectorTermsItem) -> PlacementSpec: + res = PlacementSpec() + for expression in node_selector.matchExpressions: + if expression.key.startswith("ceph-label/"): + res.label = expression.key.split('/')[1] + elif expression.key == "kubernetes.io/hostname": + if expression.operator == "Exists": + res.host_pattern = "*" + elif expression.operator == "In": + res.hosts = [HostPlacementSpec(hostname=value, network='', name='')for value in expression.values] + return res diff --git a/src/pybind/mgr/rook/tests/test_placement.py b/src/pybind/mgr/rook/tests/test_placement.py new file mode 100644 index 000000000000..eeaf191e276a --- /dev/null +++ b/src/pybind/mgr/rook/tests/test_placement.py @@ -0,0 +1,100 @@ +# flake8: noqa + +from rook.rook_cluster import placement_spec_to_node_selector, node_selector_to_placement_spec +from rook.rook_client.ceph.cephcluster import MatchExpressionsItem, MatchExpressionsList, NodeSelectorTermsItem +import pytest +from orchestrator import HostSpec +from ceph.deployment.service_spec import PlacementSpec + +@pytest.mark.parametrize("hosts", + [ # noqa: E128 + [ + HostSpec( + hostname="node1", + labels=["label1"] + ), + HostSpec( + hostname="node2", + labels=[] + ), + HostSpec( + hostname="node3", + labels=["label1"] + ) + ] + ]) +@pytest.mark.parametrize("expected_placement_spec, expected_node_selector", + [ # noqa: E128 + ( + PlacementSpec( + label="label1" + ), + NodeSelectorTermsItem( + matchExpressions=MatchExpressionsList( + [ + MatchExpressionsItem( + key="ceph-label/label1", + operator="Exists" + ) + ] + ) + ) + ), + ( + PlacementSpec( + label="label1", + host_pattern="*" + ), + NodeSelectorTermsItem( + matchExpressions=MatchExpressionsList( + [ + MatchExpressionsItem( + key="ceph-label/label1", + operator="Exists" + ), + MatchExpressionsItem( + key="kubernetes.io/hostname", + operator="Exists", + ) + ] + ) + ) + ), + ( + PlacementSpec( + host_pattern="*" + ), + NodeSelectorTermsItem( + matchExpressions=MatchExpressionsList( + [ + MatchExpressionsItem( + key="kubernetes.io/hostname", + operator="Exists", + ) + ] + ) + ) + ), + ( + PlacementSpec( + hosts=["node1", "node2", "node3"] + ), + NodeSelectorTermsItem( + matchExpressions=MatchExpressionsList( + [ + MatchExpressionsItem( + key="kubernetes.io/hostname", + operator="In", + values=["node1", "node2", "node3"] + ) + ] + ) + ) + ), + ]) +def test_placement_spec_translate(hosts, expected_placement_spec, expected_node_selector): + node_selector = placement_spec_to_node_selector(expected_placement_spec, hosts) + assert [(getattr(expression, 'key', None), getattr(expression, 'operator', None), getattr(expression, 'values', None)) for expression in node_selector.matchExpressions] == [(getattr(expression, 'key', None), getattr(expression, 'operator', None), getattr(expression, 'values', None)) for expression in expected_node_selector.matchExpressions] + placement_spec = node_selector_to_placement_spec(expected_node_selector) + assert placement_spec == expected_placement_spec + assert (getattr(placement_spec, 'label', None), getattr(placement_spec, 'hosts', None), getattr(placement_spec, 'host_pattern', None)) == (getattr(expected_placement_spec, 'label', None), getattr(expected_placement_spec, 'hosts', None), getattr(expected_placement_spec, 'host_pattern', None))