]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/cephadm: add HostAssignment.validate()
authorSebastian Wagner <sebastian.wagner@suse.com>
Tue, 17 Mar 2020 14:00:12 +0000 (15:00 +0100)
committerSebastian Wagner <sebastian.wagner@suse.com>
Tue, 17 Mar 2020 14:00:12 +0000 (15:00 +0100)
Fixes: https://tracker.ceph.com/issues/44607
Signed-off-by: Sebastian Wagner <sebastian.wagner@suse.com>
src/pybind/mgr/cephadm/module.py
src/pybind/mgr/cephadm/tests/test_scheduling.py
src/python-common/ceph/deployment/service_spec.py

index 887543d105eebfcc9d87d7935625381fb7fd5b6c..b6ac1ca456179ffb18dcd8e8952c75d2377a9e7c 100644 (file)
@@ -2555,6 +2555,13 @@ class CephadmOrchestrator(orchestrator.Orchestrator, MgrModule):
              spec.placement.count < 1:
             raise OrchestratorError('cannot scale %s service below 1' % (
                 spec.service_type))
+
+        HostAssignment(
+            spec=spec,
+            get_hosts_func=self._get_hosts,
+            get_daemons_func=self.cache.get_daemons_by_service,
+        ).validate()
+
         self.log.info('Saving service %s spec with placement %s' % (
             spec.service_name(), spec.placement.pretty_str()))
         self.spec_store.save(spec)
@@ -3181,11 +3188,38 @@ class HostAssignment(object):
         self.filter_new_host = filter_new_host
         self.service_name = spec.service_name()
 
+
+    def validate(self):
+        self.spec.validate()
+
+        if self.spec.placement.hosts:
+            explicit_hostnames = {h.hostname for h in self.spec.placement.hosts}
+            unknown_hosts = explicit_hostnames.difference(set(self.get_hosts_func(None)))
+            if unknown_hosts:
+                raise OrchestratorValidationError(
+                    f'Cannot place {self.spec.one_line_str()} on {unknown_hosts}: Unknown hosts')
+
+        if self.spec.placement.host_pattern:
+            pattern_hostnames = self.spec.placement.pattern_matches_hosts(self.get_hosts_func(None))
+            if not pattern_hostnames:
+                raise OrchestratorValidationError(
+                    f'Cannot place {self.spec.one_line_str()}: No matching hosts')
+
+        if self.spec.placement.label:
+            label_hostnames = self.get_hosts_func(self.spec.placement.label)
+            if not label_hostnames:
+                raise OrchestratorValidationError(
+                    f'Cannot place {self.spec.one_line_str()}: No matching '
+                    f'hosts for label {self.spec.placement.label}')
+
     def place(self):
         # type: () -> List[HostPlacementSpec]
         """
         Load hosts into the spec.placement.hosts container.
         """
+
+        self.validate()
+
         # count == 0
         if self.spec.placement.count == 0:
             return []
index f7f7f7e9edae2a42dc407eda099a30373ad7e3ff..5c01d6d75aebff7f4c0f612a7c65721b17d5b1cd 100644 (file)
@@ -253,3 +253,45 @@ def test_bad_placements(placement):
         assert False
     except ServiceSpecValidationError as e:
         pass
+
+
+class NodeAssignmentTestBadSpec(NamedTuple):
+    service_type: str
+    placement: PlacementSpec
+    hosts: List[str]
+    daemons: List[DaemonDescription]
+    expected: str
+@pytest.mark.parametrize("service_type,placement,hosts,daemons,expected",
+    [
+        # unknown host
+        NodeAssignmentTestBadSpec(
+            'mon',
+            PlacementSpec(hosts=['unknownhost']),
+            ['knownhost'],
+            [],
+            "Cannot place <ServiceSpec for service_name=mon> on {'unknownhost'}: Unknown hosts"
+        ),
+        # unknown host pattern
+        NodeAssignmentTestBadSpec(
+            'mon',
+            PlacementSpec(host_pattern='unknownhost'),
+            ['knownhost'],
+            [],
+            "Cannot place <ServiceSpec for service_name=mon>: No matching hosts"
+        ),
+        # unknown label
+        NodeAssignmentTestBadSpec(
+            'mon',
+            PlacementSpec(label='unknownlabel'),
+            [],
+            [],
+            "Cannot place <ServiceSpec for service_name=mon>: No matching hosts for label unknownlabel"
+        ),
+    ])
+def test_bad_specs(service_type, placement, hosts, daemons, expected):
+    with pytest.raises(OrchestratorValidationError) as e:
+        hosts = HostAssignment(
+            spec=ServiceSpec(service_type, placement=placement),
+            get_hosts_func=lambda _: hosts,
+            get_daemons_func=lambda _: daemons).place()
+    assert str(e.value) == expected
index ae19d739fd2895fb9ee4b08b7d57f6ea662035ae..07dd2a14f8fe5a7e66a954d5a47f590e15bb3207 100644 (file)
@@ -376,6 +376,9 @@ class ServiceSpec(object):
     def __repr__(self):
         return "{}({!r})".format(self.__class__.__name__, self.__dict__)
 
+    def one_line_str(self):
+        return '<{} for service_name={}>'.format(self.__class__.__name__, self.service_name())
+
 
 def servicespec_validate_add(self: ServiceSpec):
     # This must not be a method of ServiceSpec, otherwise you'll hunt