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)
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 []
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
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