label=None, # type: Optional[str]
hosts=None, # type: Union[List[str],List[HostPlacementSpec], None]
count=None, # type: Optional[int]
- host_pattern=None # type: Optional[str]
+ count_per_host=None, # type: Optional[int]
+ host_pattern=None, # type: Optional[str]
):
# type: (...) -> None
self.label = label
self.set_hosts(hosts)
self.count = count # type: Optional[int]
+ self.count_per_host = count_per_host # type: Optional[int]
#: fnmatch patterns to select hosts. Can also be a single host.
self.host_pattern = host_pattern # type: Optional[str]
self.validate()
def is_empty(self) -> bool:
- return self.label is None and \
- not self.hosts and \
- not self.host_pattern and \
- self.count is None
+ return (
+ self.label is None
+ and not self.hosts
+ and not self.host_pattern
+ and self.count is None
+ and self.count_per_host is None
+ )
def __eq__(self, other: Any) -> bool:
if isinstance(other, PlacementSpec):
return self.label == other.label \
and self.hosts == other.hosts \
and self.count == other.count \
- and self.host_pattern == other.host_pattern
+ and self.host_pattern == other.host_pattern \
+ and self.count_per_host == other.count_per_host
return NotImplemented
def set_hosts(self, hosts: Union[List[str], List[HostPlacementSpec]]) -> None:
kv.append(';'.join([str(h) for h in self.hosts]))
if self.count:
kv.append('count:%d' % self.count)
+ if self.count_per_host:
+ kv.append('count-per-host:%d' % self.count_per_host)
if self.label:
kv.append('label:%s' % self.label)
if self.host_pattern:
kv = []
if self.count:
kv.append('count=%d' % self.count)
+ if self.count_per_host:
+ kv.append('count_per_host=%d' % self.count_per_host)
if self.label:
kv.append('label=%s' % repr(self.label))
if self.hosts:
r['hosts'] = [host.to_json() for host in self.hosts]
if self.count:
r['count'] = self.count
+ if self.count_per_host:
+ r['count_per_host'] = self.count_per_host
if self.host_pattern:
r['host_pattern'] = self.host_pattern
return r
raise ServiceSpecValidationError('Host and label are mutually exclusive')
if self.count is not None and self.count <= 0:
raise ServiceSpecValidationError("num/count must be > 1")
+ if self.count_per_host is not None and self.count_per_host < 1:
+ raise ServiceSpecValidationError("count-per-host must be >= 1")
+ if self.count is not None and self.count_per_host is not None:
+ raise ServiceSpecValidationError("cannot combine count and count-per-host")
if self.host_pattern and self.hosts:
raise ServiceSpecValidationError('cannot combine host patterns and hosts')
for h in self.hosts:
raise ServiceSpecValidationError('invalid placement %s' % arg)
count = None
+ count_per_host = None
if strings:
try:
count = int(strings[0])
for s in strings:
if s.startswith('count:'):
try:
- count = int(s[6:])
+ count = int(s[len('count:'):])
+ strings.remove(s)
+ break
+ except ValueError:
+ pass
+ for s in strings:
+ if s.startswith('count-per-host:'):
+ try:
+ count_per_host = int(s[len('count-per-host:'):])
strings.remove(s)
break
except ValueError:
'more than one host pattern provided: {}'.format(host_patterns))
ps = PlacementSpec(count=count,
+ count_per_host=count_per_host,
hosts=advanced_hostspecs,
label=label,
host_pattern=host_patterns[0] if host_patterns else None)
('3 data[1-3]', "PlacementSpec(count=3, host_pattern='data[1-3]')"),
('3 data?', "PlacementSpec(count=3, host_pattern='data?')"),
('3 data*', "PlacementSpec(count=3, host_pattern='data*')"),
+ ("count-per-host:4", "PlacementSpec(count_per_host=4)"),
])
def test_parse_placement_specs(test_input, expected):
ret = PlacementSpec.from_string(test_input)
("host=a host*"),
("host=a label:wrong"),
("host? host*"),
+ ('host=a count-per-host:0'),
+ ('host=a count-per-host:-10'),
+ ('count:2 count-per-host:1'),
]
)
def test_parse_placement_specs_raises(test_input):