diff --git a/docs/reference.md b/docs/reference.md index 61010363d8911c13c990f7856183fc596dfb9c1e..c1e6eb8511ab1af935be4dfebccbd0cee1823f3b 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -3318,6 +3318,19 @@ If you want more control over this process (Debian upgrades have been event-less for a while now, but it's not always been the case) you can of course run the upgrade manually. +### Decommissioning a host + +When turning down a host, it is necessary, at some point, to +reschedule the services that were there onto some other hosts. To +achieve a smooth transition, this is best done while the host is still +available. + +To do this, set the *turndown* attribute to *true* in the inventory +for the host you want to turn down, and then run *float* once more. +This should safely reschedule all services, and remove them from the +target host. It is then possible to simply shut down the target host +and wipe its data. + # Example scenarios This section will look at some example scenarios and use cases for diff --git a/docs/reference.pdf b/docs/reference.pdf index 822b75c0e569b4280059734e474714e7b7ecade1..7a29260cc0f44d7f4bac04e7b096da388ecd8d67 100644 Binary files a/docs/reference.pdf and b/docs/reference.pdf differ diff --git a/plugins/inventory/float.py b/plugins/inventory/float.py index 46c2b25a0e4d0d1c53635da2e197e14f576963c9..e67df79ecb905ae7792d9ae24b952ab3ef688613 100644 --- a/plugins/inventory/float.py +++ b/plugins/inventory/float.py @@ -282,6 +282,16 @@ def _global_dns_map(inventory): return dns +# Return the hosts that are not available for scheduling, as a +# Python set. +def _unavailable_hosts(inventory): + unavail = set() + for name, values in inventory['hosts'].items(): + if values.get('turndown'): + unavail.add(name) + return unavail + + # Build a group -> hosts map out of an inventory. def _build_group_map(inventory, assignments=None): group_map = {} @@ -499,7 +509,8 @@ class Assignments(object): return str(self._fwd) @classmethod - def _available_hosts(cls, service, group_map, service_hosts_map): + def _available_hosts(cls, service, group_map, service_hosts_map, + unavailable_hosts={}): if 'schedule_with' in service: return service_hosts_map[service['schedule_with']] scheduling_groups = ['all'] @@ -512,7 +523,7 @@ class Assignments(object): if g not in group_map: raise Exception(f'The scheduling_group "{g}" is not defined in inventoy') available_hosts.update(group_map[g]) - return list(available_hosts) + return list(available_hosts.difference(unavailable_hosts)) @classmethod def schedule(cls, services, inventory): @@ -525,6 +536,7 @@ class Assignments(object): """ service_hosts_map = {} service_master_map = {} + unavailable_hosts = _unavailable_hosts(inventory) group_map = _build_group_map(inventory) host_occupation = collections.defaultdict(int) @@ -540,13 +552,16 @@ class Assignments(object): for service_name in sorted(services.keys(), key=_sort_key): service = services[service_name] available_hosts = cls._available_hosts(service, group_map, - service_hosts_map) + service_hosts_map, + unavailable_hosts) num_instances = service.get('num_instances', 'all') if num_instances == 'all': service_hosts = sorted(available_hosts) else: service_hosts = sorted(_binpack( available_hosts, host_occupation, num_instances)) + if not service_hosts: + raise Exception(f'No hosts available to schedule service {service_name}') service_hosts_map[service_name] = service_hosts for h in service_hosts: host_occupation[h] += 1