forked from stove/risotto
215 lines
7.9 KiB
Python
215 lines
7.9 KiB
Python
#!/usr/bin/python3
|
|
|
|
from time import sleep
|
|
from os import fdopen
|
|
from dbus import SystemBus, Array
|
|
from dbus.exceptions import DBusException
|
|
from subprocess import run
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
|
|
|
|
|
|
def stop(bus, machines):
|
|
changed = False
|
|
remote_object = bus.get_object('org.freedesktop.machine1',
|
|
'/org/freedesktop/machine1',
|
|
False,
|
|
)
|
|
res = remote_object.ListMachines(dbus_interface='org.freedesktop.machine1.Manager')
|
|
started_machines = [str(r[0]) for r in res if str(r[0]) != '.host']
|
|
for host in machines:
|
|
if host not in started_machines:
|
|
continue
|
|
changed = True
|
|
remote_object.TerminateMachine(host, dbus_interface='org.freedesktop.machine1.Manager')
|
|
idx = 0
|
|
errors = []
|
|
while True:
|
|
res = remote_object.ListMachines(dbus_interface='org.freedesktop.machine1.Manager')
|
|
started_machines = [str(r[0]) for r in res if str(r[0]) != '.host']
|
|
for host in machines:
|
|
if host in started_machines:
|
|
break
|
|
else:
|
|
break
|
|
sleep(1)
|
|
idx += 1
|
|
if idx == 120:
|
|
errors.append('Cannot not stopped: ' + ','.join(started_machines))
|
|
break
|
|
return changed, errors
|
|
|
|
|
|
def start(bus, machines):
|
|
changed = False
|
|
remote_object = bus.get_object('org.freedesktop.machine1',
|
|
'/org/freedesktop/machine1',
|
|
False,
|
|
)
|
|
res = remote_object.ListMachines(dbus_interface='org.freedesktop.machine1.Manager')
|
|
started_machines = [str(r[0]) for r in res if str(r[0]) != '.host']
|
|
remote_object_system = bus.get_object('org.freedesktop.systemd1',
|
|
'/org/freedesktop/systemd1',
|
|
False,
|
|
)
|
|
for host in machines:
|
|
if host in started_machines:
|
|
continue
|
|
changed = True
|
|
service = f'systemd-nspawn@{host}.service'
|
|
remote_object_system.StartUnit(service, 'fail', dbus_interface='org.freedesktop.systemd1.Manager')
|
|
errors = []
|
|
idx = 0
|
|
while True:
|
|
res = remote_object.ListMachines(dbus_interface='org.freedesktop.machine1.Manager')
|
|
started_machines = [str(r[0]) for r in res if str(r[0]) != '.host']
|
|
for host in machines:
|
|
if host not in started_machines:
|
|
break
|
|
else:
|
|
break
|
|
sleep(1)
|
|
idx += 1
|
|
if idx == 120:
|
|
hosts = set(machines) - set(started_machines)
|
|
errors.append('Cannot not start: ' + ','.join(hosts))
|
|
break
|
|
if not errors:
|
|
idx = 0
|
|
for host in machines:
|
|
cmd = ['/usr/bin/systemctl', 'is-system-running']
|
|
error = False
|
|
while True:
|
|
try:
|
|
res = remote_object.OpenMachineShell(host,
|
|
'',
|
|
cmd[0],
|
|
Array(cmd, signature='s'),
|
|
Array(['TERM=dumb'], signature='s'),
|
|
dbus_interface='org.freedesktop.machine1.Manager',
|
|
)
|
|
fd = res[0].take()
|
|
fh = fdopen(fd)
|
|
ret = []
|
|
while True:
|
|
try:
|
|
ret.append(fh.readline().strip())
|
|
except OSError as err:
|
|
if err.errno != 5:
|
|
raise err from err
|
|
break
|
|
if not ret:
|
|
errors.append(f'Cannot check {host} status')
|
|
error = True
|
|
break
|
|
if ret[0] in ['running', 'degraded']:
|
|
break
|
|
except DBusException:
|
|
pass
|
|
idx += 1
|
|
sleep(1)
|
|
if idx == 120:
|
|
errors.append(f'Cannot not start {host} ({ret})')
|
|
break
|
|
if error:
|
|
continue
|
|
if ret[0] == 'running':
|
|
continue
|
|
cmd = ['/usr/bin/systemctl', '--state=failed', '--no-legend', '--no-page']
|
|
res = remote_object.OpenMachineShell(host,
|
|
'',
|
|
cmd[0],
|
|
Array(cmd, signature='s'),
|
|
Array(['TERM=dumb'], signature='s'),
|
|
dbus_interface='org.freedesktop.machine1.Manager',
|
|
)
|
|
fd = res[0].take()
|
|
fh = fdopen(fd)
|
|
ret = []
|
|
idx2 = 0
|
|
while True:
|
|
try:
|
|
ret.append(fh.readline().strip())
|
|
except OSError as err:
|
|
if err.errno != 5:
|
|
raise err from err
|
|
break
|
|
idx2 += 1
|
|
if idx2 == 120:
|
|
errors.append(f'Cannot not get status to {host}')
|
|
break
|
|
errors.append(f'{host}: ' + '\n'.join(ret))
|
|
return changed, errors
|
|
|
|
|
|
def enable(machines):
|
|
cmd = ['/usr/bin/machinectl', 'enable'] + machines
|
|
run(cmd)
|
|
return True
|
|
|
|
|
|
def run_module():
|
|
# define available arguments/parameters a user can pass to the module
|
|
module_args = dict(
|
|
state=dict(type='str', required=True),
|
|
machines=dict(type='list', required=True),
|
|
)
|
|
|
|
# seed the result dict in the object
|
|
# we primarily care about changed and state
|
|
# changed is if this module effectively modified the target
|
|
# state will include any data that you want your module to pass back
|
|
# for consumption, for example, in a subsequent task
|
|
result = dict(
|
|
changed=False,
|
|
message=''
|
|
)
|
|
|
|
# the AnsibleModule object will be our abstraction working with Ansible
|
|
# this includes instantiation, a couple of common attr would be the
|
|
# args/params passed to the execution, as well as if the module
|
|
# supports check mode
|
|
module = AnsibleModule(
|
|
argument_spec=module_args,
|
|
supports_check_mode=True
|
|
)
|
|
# if the user is working with this module in only check mode we do not
|
|
# want to make any changes to the environment, just return the current
|
|
# state with no modifications
|
|
if module.check_mode:
|
|
module.exit_json(**result)
|
|
|
|
# manipulate or modify the state as needed (this is going to be the
|
|
# part where your module will do what it needs to do)
|
|
machines = module.params['machines']
|
|
if module.params['state'] == 'stopped':
|
|
bus = SystemBus()
|
|
result['changed'], errors = stop(bus, machines)
|
|
if errors:
|
|
errors = '\n\n'.join(errors)
|
|
module.fail_json(msg=f'Some machines are not stopping correctly {errors}', **result)
|
|
elif module.params['state'] == 'started':
|
|
bus = SystemBus()
|
|
result['changed'], errors = start(bus, machines)
|
|
if errors:
|
|
errors = '\n\n'.join(errors)
|
|
module.fail_json(msg=f'Some machines are not running correctly {errors}', **result)
|
|
elif module.params['state'] == 'enabled':
|
|
result['changed'] = enable(machines)
|
|
else:
|
|
module.fail_json(msg=f"Unknown state: {module.params['state']}")
|
|
|
|
|
|
|
|
# in the event of a successful module execution, you will want to
|
|
# simple AnsibleModule.exit_json(), passing the key/value results
|
|
module.exit_json(**result)
|
|
|
|
|
|
def main():
|
|
run_module()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|