mirror of https://github.com/ceph/ceph-ansible.git
733 lines
22 KiB
Python
733 lines
22 KiB
Python
#!/usr/bin/python
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
try:
|
|
from ansible.module_utils.ca_common import exec_command, \
|
|
is_containerized, \
|
|
fatal
|
|
except ImportError:
|
|
from module_utils.ca_common import exec_command, \
|
|
is_containerized, \
|
|
fatal
|
|
import datetime
|
|
import copy
|
|
import json
|
|
import os
|
|
import re
|
|
|
|
ANSIBLE_METADATA = {
|
|
'metadata_version': '1.0',
|
|
'status': ['preview'],
|
|
'supported_by': 'community'
|
|
}
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: ceph_volume
|
|
|
|
short_description: Create ceph OSDs with ceph-volume
|
|
|
|
description:
|
|
- Using the ceph-volume utility available in Ceph this module
|
|
can be used to create ceph OSDs that are backed by logical volumes.
|
|
- Only available in ceph versions luminous or greater.
|
|
|
|
options:
|
|
cluster:
|
|
description:
|
|
- The ceph cluster name.
|
|
required: false
|
|
default: ceph
|
|
objectstore:
|
|
description:
|
|
- The objectstore of the OSD.
|
|
required: false
|
|
choices: ['bluestore']
|
|
default: bluestore
|
|
action:
|
|
description:
|
|
- The action to take. Creating OSDs and zapping or querying devices.
|
|
required: true
|
|
choices: ['create', 'zap', 'batch', 'prepare', 'activate', 'list', 'inventory']
|
|
default: create
|
|
data:
|
|
description:
|
|
- The logical volume name or device to use for the OSD data.
|
|
required: true
|
|
data_vg:
|
|
description:
|
|
- If data is a lv, this must be the name of the volume group it belongs to.
|
|
required: false
|
|
osd_fsid:
|
|
description:
|
|
- The OSD FSID
|
|
required: false
|
|
osd_id:
|
|
description:
|
|
- The OSD ID
|
|
required: false
|
|
db:
|
|
description:
|
|
- A partition or logical volume name to use for block.db.
|
|
required: false
|
|
db_vg:
|
|
description:
|
|
- If db is a lv, this must be the name of the volume group it belongs to. # noqa: E501
|
|
required: false
|
|
wal:
|
|
description:
|
|
- A partition or logical volume name to use for block.wal.
|
|
required: false
|
|
wal_vg:
|
|
description:
|
|
- If wal is a lv, this must be the name of the volume group it belongs to. # noqa: E501
|
|
required: false
|
|
crush_device_class:
|
|
description:
|
|
- Will set the crush device class for the OSD.
|
|
required: false
|
|
dmcrypt:
|
|
description:
|
|
- If set to True the OSD will be encrypted with dmcrypt.
|
|
required: false
|
|
batch_devices:
|
|
description:
|
|
- A list of devices to pass to the 'ceph-volume lvm batch' subcommand.
|
|
- Only applicable if action is 'batch'.
|
|
required: false
|
|
osds_per_device:
|
|
description:
|
|
- The number of OSDs to create per device.
|
|
- Only applicable if action is 'batch'.
|
|
required: false
|
|
default: 1
|
|
block_db_size:
|
|
description:
|
|
- The size in bytes of bluestore block db lvs.
|
|
- The default of -1 means to create them as big as possible.
|
|
- Only applicable if action is 'batch'.
|
|
required: false
|
|
default: -1
|
|
block_db_devices:
|
|
description:
|
|
- A list of devices for bluestore block db to pass to the 'ceph-volume lvm batch' subcommand.
|
|
- Only applicable if action is 'batch'.
|
|
required: false
|
|
wal_devices:
|
|
description:
|
|
- A list of devices for bluestore block wal to pass to the 'ceph-volume lvm batch' subcommand.
|
|
- Only applicable if action is 'batch'.
|
|
required: false
|
|
report:
|
|
description:
|
|
- If provided the --report flag will be passed to 'ceph-volume lvm batch'.
|
|
- No OSDs will be created.
|
|
- Results will be returned in json format.
|
|
- Only applicable if action is 'batch'.
|
|
required: false
|
|
list:
|
|
description:
|
|
- List potential Ceph LVM metadata on a device
|
|
required: false
|
|
inventory:
|
|
description:
|
|
- List storage device inventory.
|
|
required: false
|
|
|
|
author:
|
|
- Andrew Schoen (@andrewschoen)
|
|
- Sebastien Han <seb@redhat.com>
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
- name: set up a bluestore osd with a raw device for data
|
|
ceph_volume:
|
|
objectstore: bluestore
|
|
data: /dev/sdc
|
|
action: create
|
|
|
|
|
|
- name: set up a bluestore osd with an lv for data and partitions for block.wal and block.db # noqa: E501
|
|
ceph_volume:
|
|
objectstore: bluestore
|
|
data: data-lv
|
|
data_vg: data-vg
|
|
db: /dev/sdc1
|
|
wal: /dev/sdc2
|
|
action: create
|
|
'''
|
|
|
|
|
|
def container_exec(binary, container_image, mounts=None):
|
|
'''
|
|
Build the docker CLI to run a command inside a container
|
|
'''
|
|
_mounts = {}
|
|
_mounts['/run/lock/lvm'] = '/run/lock/lvm:z'
|
|
_mounts['/var/run/udev'] = '/var/run/udev:z'
|
|
_mounts['/dev'] = '/dev'
|
|
_mounts['/etc/ceph'] = '/etc/ceph:z'
|
|
_mounts['/run/lvm'] = '/run/lvm'
|
|
_mounts['/var/lib/ceph'] = '/var/lib/ceph:z'
|
|
_mounts['/var/log/ceph'] = '/var/log/ceph:z'
|
|
if mounts is None:
|
|
mounts = _mounts
|
|
else:
|
|
_mounts.update(mounts)
|
|
|
|
volumes = sum(
|
|
[['-v', '{}:{}'.format(src_dir, dst_dir)]
|
|
for src_dir, dst_dir in _mounts.items()], [])
|
|
|
|
container_binary = os.getenv('CEPH_CONTAINER_BINARY')
|
|
command_exec = [container_binary, 'run',
|
|
'--rm',
|
|
'--privileged',
|
|
'--net=host',
|
|
'--ipc=host'] + volumes + \
|
|
['--entrypoint=' + binary, container_image]
|
|
return command_exec
|
|
|
|
|
|
def build_cmd(action, container_image,
|
|
cluster='ceph',
|
|
binary='ceph-volume', mounts=None):
|
|
'''
|
|
Build the ceph-volume command
|
|
'''
|
|
|
|
_binary = binary
|
|
|
|
if container_image:
|
|
cmd = container_exec(
|
|
binary, container_image, mounts=mounts)
|
|
else:
|
|
binary = [binary]
|
|
cmd = binary
|
|
|
|
if _binary == 'ceph-volume':
|
|
cmd.extend(['--cluster', cluster])
|
|
|
|
cmd.extend(action)
|
|
|
|
return cmd
|
|
|
|
|
|
def get_data(data, data_vg):
|
|
if data_vg:
|
|
data = '{0}/{1}'.format(data_vg, data)
|
|
return data
|
|
|
|
|
|
def get_journal(journal, journal_vg):
|
|
if journal_vg:
|
|
journal = '{0}/{1}'.format(journal_vg, journal)
|
|
return journal
|
|
|
|
|
|
def get_db(db, db_vg):
|
|
if db_vg:
|
|
db = '{0}/{1}'.format(db_vg, db)
|
|
return db
|
|
|
|
|
|
def get_wal(wal, wal_vg):
|
|
if wal_vg:
|
|
wal = '{0}/{1}'.format(wal_vg, wal)
|
|
return wal
|
|
|
|
|
|
def batch(module, container_image, report=None):
|
|
'''
|
|
Batch prepare OSD devices
|
|
'''
|
|
|
|
# get module variables
|
|
cluster = module.params['cluster']
|
|
objectstore = module.params['objectstore']
|
|
batch_devices = module.params.get('batch_devices', None)
|
|
crush_device_class = module.params.get('crush_device_class', None)
|
|
block_db_size = module.params.get('block_db_size', None)
|
|
block_db_devices = module.params.get('block_db_devices', None)
|
|
wal_devices = module.params.get('wal_devices', None)
|
|
dmcrypt = module.params.get('dmcrypt', None)
|
|
osds_per_device = module.params.get('osds_per_device', 1)
|
|
|
|
if not osds_per_device:
|
|
fatal('osds_per_device must be provided if action is "batch"', module)
|
|
|
|
if osds_per_device < 1:
|
|
fatal('osds_per_device must be greater than 0 if action is "batch"', module) # noqa: E501
|
|
|
|
if not batch_devices:
|
|
fatal('batch_devices must be provided if action is "batch"', module)
|
|
|
|
# Build the CLI
|
|
action = ['lvm', 'batch']
|
|
cmd = build_cmd(action, container_image, cluster)
|
|
cmd.extend(['--%s' % objectstore])
|
|
if not report:
|
|
cmd.append('--yes')
|
|
|
|
if container_image:
|
|
cmd.append('--prepare')
|
|
|
|
if crush_device_class:
|
|
cmd.extend(['--crush-device-class', crush_device_class])
|
|
|
|
if dmcrypt:
|
|
cmd.append('--dmcrypt')
|
|
|
|
if osds_per_device > 1:
|
|
cmd.extend(['--osds-per-device', str(osds_per_device)])
|
|
|
|
if objectstore == 'bluestore' and block_db_size != '-1':
|
|
cmd.extend(['--block-db-size', block_db_size])
|
|
|
|
cmd.extend(batch_devices)
|
|
|
|
if block_db_devices and objectstore == 'bluestore':
|
|
cmd.append('--db-devices')
|
|
cmd.extend(block_db_devices)
|
|
|
|
if wal_devices and objectstore == 'bluestore':
|
|
cmd.append('--wal-devices')
|
|
cmd.extend(wal_devices)
|
|
|
|
return cmd
|
|
|
|
|
|
def ceph_volume_cmd(subcommand, container_image, cluster=None):
|
|
'''
|
|
Build ceph-volume initial command
|
|
'''
|
|
|
|
if container_image:
|
|
binary = 'ceph-volume'
|
|
cmd = container_exec(
|
|
binary, container_image)
|
|
else:
|
|
binary = ['ceph-volume']
|
|
cmd = binary
|
|
|
|
if cluster:
|
|
cmd.extend(['--cluster', cluster])
|
|
|
|
cmd.append('lvm')
|
|
cmd.append(subcommand)
|
|
|
|
return cmd
|
|
|
|
|
|
def prepare_or_create_osd(module, action, container_image):
|
|
'''
|
|
Prepare or create OSD devices
|
|
'''
|
|
|
|
# get module variables
|
|
cluster = module.params['cluster']
|
|
objectstore = module.params['objectstore']
|
|
data = module.params['data']
|
|
data_vg = module.params.get('data_vg', None)
|
|
data = get_data(data, data_vg)
|
|
db = module.params.get('db', None)
|
|
db_vg = module.params.get('db_vg', None)
|
|
wal = module.params.get('wal', None)
|
|
wal_vg = module.params.get('wal_vg', None)
|
|
crush_device_class = module.params.get('crush_device_class', None)
|
|
dmcrypt = module.params.get('dmcrypt', None)
|
|
|
|
# Build the CLI
|
|
action = ['lvm', action]
|
|
cmd = build_cmd(action, container_image, cluster)
|
|
cmd.extend(['--%s' % objectstore])
|
|
cmd.append('--data')
|
|
cmd.append(data)
|
|
|
|
if db and objectstore == 'bluestore':
|
|
db = get_db(db, db_vg)
|
|
cmd.extend(['--block.db', db])
|
|
|
|
if wal and objectstore == 'bluestore':
|
|
wal = get_wal(wal, wal_vg)
|
|
cmd.extend(['--block.wal', wal])
|
|
|
|
if crush_device_class:
|
|
cmd.extend(['--crush-device-class', crush_device_class])
|
|
|
|
if dmcrypt:
|
|
cmd.append('--dmcrypt')
|
|
|
|
return cmd
|
|
|
|
|
|
def list_osd(module, container_image):
|
|
'''
|
|
List will detect wether or not a device has Ceph LVM Metadata
|
|
'''
|
|
|
|
# get module variables
|
|
cluster = module.params['cluster']
|
|
data = module.params.get('data', None)
|
|
data_vg = module.params.get('data_vg', None)
|
|
data = get_data(data, data_vg)
|
|
|
|
# Build the CLI
|
|
action = ['lvm', 'list']
|
|
cmd = build_cmd(action,
|
|
container_image,
|
|
cluster,
|
|
mounts={'/var/lib/ceph': '/var/lib/ceph:ro'})
|
|
if data:
|
|
cmd.append(data)
|
|
cmd.append('--format=json')
|
|
|
|
return cmd
|
|
|
|
|
|
def list_storage_inventory(module, container_image):
|
|
'''
|
|
List storage inventory.
|
|
'''
|
|
|
|
action = ['inventory']
|
|
cmd = build_cmd(action, container_image)
|
|
cmd.append('--format=json')
|
|
|
|
return cmd
|
|
|
|
|
|
def activate_osd():
|
|
'''
|
|
Activate all the OSDs on a machine
|
|
'''
|
|
|
|
# build the CLI
|
|
action = ['lvm', 'activate']
|
|
container_image = None
|
|
cmd = build_cmd(action, container_image)
|
|
cmd.append('--all')
|
|
|
|
return cmd
|
|
|
|
|
|
def is_lv(module, vg, lv, container_image):
|
|
'''
|
|
Check if an LV exists
|
|
'''
|
|
|
|
args = ['--noheadings', '--reportformat', 'json', '--select', 'lv_name={},vg_name={}'.format(lv, vg)] # noqa: E501
|
|
|
|
cmd = build_cmd(args, container_image, binary='lvs')
|
|
|
|
rc, cmd, out, err = exec_command(module, cmd)
|
|
|
|
if rc == 0:
|
|
result = json.loads(out)['report'][0]['lv']
|
|
if len(result) > 0:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def zap_devices(module, container_image):
|
|
'''
|
|
Will run 'ceph-volume lvm zap' on all devices, lvs and partitions
|
|
used to create the OSD. The --destroy flag is always passed so that
|
|
if an OSD was originally created with a raw device or partition for
|
|
'data' then any lvs that were created by ceph-volume are removed.
|
|
'''
|
|
|
|
# get module variables
|
|
data = module.params.get('data', None)
|
|
data_vg = module.params.get('data_vg', None)
|
|
db = module.params.get('db', None)
|
|
db_vg = module.params.get('db_vg', None)
|
|
wal = module.params.get('wal', None)
|
|
wal_vg = module.params.get('wal_vg', None)
|
|
osd_fsid = module.params.get('osd_fsid', None)
|
|
osd_id = module.params.get('osd_id', None)
|
|
destroy = module.params.get('destroy', True)
|
|
|
|
# build the CLI
|
|
action = ['lvm', 'zap']
|
|
cmd = build_cmd(action, container_image)
|
|
if destroy:
|
|
cmd.append('--destroy')
|
|
|
|
if osd_fsid:
|
|
cmd.extend(['--osd-fsid', osd_fsid])
|
|
|
|
if osd_id:
|
|
cmd.extend(['--osd-id', osd_id])
|
|
|
|
if data:
|
|
data = get_data(data, data_vg)
|
|
cmd.append(data)
|
|
|
|
if db:
|
|
db = get_db(db, db_vg)
|
|
cmd.extend([db])
|
|
|
|
if wal:
|
|
wal = get_wal(wal, wal_vg)
|
|
cmd.extend([wal])
|
|
|
|
return cmd
|
|
|
|
|
|
def allowed_in_check_mode(module):
|
|
'''
|
|
Check if the action is allowed in check mode
|
|
'''
|
|
|
|
action = module.params['action']
|
|
report = module.params.get('report', False)
|
|
|
|
# batch is allowed in check mode if report is set
|
|
if action == 'batch' and report:
|
|
return True
|
|
|
|
allowed_actions = ['list', 'inventory']
|
|
|
|
return action in allowed_actions
|
|
|
|
|
|
def run_module():
|
|
module_args = dict(
|
|
cluster=dict(type='str', required=False, default='ceph'),
|
|
objectstore=dict(type='str', required=False, choices=[
|
|
'bluestore'], default='bluestore'),
|
|
action=dict(type='str', required=False, choices=[
|
|
'create', 'zap', 'batch', 'prepare', 'activate', 'list',
|
|
'inventory'], default='create'), # noqa: 4502
|
|
data=dict(type='str', required=False),
|
|
data_vg=dict(type='str', required=False),
|
|
db=dict(type='str', required=False),
|
|
db_vg=dict(type='str', required=False),
|
|
wal=dict(type='str', required=False),
|
|
wal_vg=dict(type='str', required=False),
|
|
crush_device_class=dict(type='str', required=False),
|
|
dmcrypt=dict(type='bool', required=False, default=False),
|
|
batch_devices=dict(type='list', required=False, default=[]),
|
|
osds_per_device=dict(type='int', required=False, default=1),
|
|
block_db_size=dict(type='str', required=False, default='-1'),
|
|
block_db_devices=dict(type='list', required=False, default=[]),
|
|
wal_devices=dict(type='list', required=False, default=[]),
|
|
report=dict(type='bool', required=False, default=False),
|
|
osd_fsid=dict(type='str', required=False),
|
|
osd_id=dict(type='str', required=False),
|
|
destroy=dict(type='bool', required=False, default=True),
|
|
)
|
|
|
|
module = AnsibleModule(
|
|
argument_spec=module_args,
|
|
supports_check_mode=True,
|
|
mutually_exclusive=[
|
|
('data', 'osd_fsid', 'osd_id'),
|
|
],
|
|
required_if=[
|
|
('action', 'zap', ('data', 'osd_fsid', 'osd_id'), True)
|
|
]
|
|
)
|
|
|
|
result = dict(
|
|
changed=False,
|
|
stdout='',
|
|
stderr='',
|
|
rc=0,
|
|
start='',
|
|
end='',
|
|
delta='',
|
|
)
|
|
|
|
if module.check_mode and not allowed_in_check_mode(module):
|
|
module.exit_json(**result)
|
|
|
|
# start execution
|
|
startd = datetime.datetime.now()
|
|
|
|
# get the desired action
|
|
action = module.params['action']
|
|
|
|
# will return either the image name or None
|
|
container_image = is_containerized()
|
|
|
|
# Assume the task's status will be 'changed'
|
|
changed = True
|
|
|
|
if action == 'create' or action == 'prepare':
|
|
# First test if the device has Ceph LVM Metadata
|
|
rc, cmd, out, err = exec_command(
|
|
module, list_osd(module, container_image))
|
|
|
|
# list_osd returns a dict, if the dict is empty this means
|
|
# we can not check the return code since it's not consistent
|
|
# with the plain output
|
|
# see: http://tracker.ceph.com/issues/36329
|
|
# FIXME: it's probably less confusing to check for rc
|
|
|
|
# convert out to json, ansible returns a string...
|
|
try:
|
|
out_dict = json.loads(out)
|
|
except ValueError:
|
|
fatal("Could not decode json output: {} from the command {}".format(out, cmd), module) # noqa: E501
|
|
|
|
if out_dict:
|
|
data = module.params['data']
|
|
result['stdout'] = 'skipped, since {0} is already used for an osd'.format(data) # noqa: E501
|
|
result['rc'] = 0
|
|
module.exit_json(**result)
|
|
|
|
# Prepare or create the OSD
|
|
rc, cmd, out, err = exec_command(
|
|
module, prepare_or_create_osd(module, action, container_image))
|
|
err = re.sub('[a-zA-Z0-9+/]{38}==', '*' * 8, err)
|
|
|
|
elif action == 'activate':
|
|
if container_image:
|
|
fatal(
|
|
"This is not how container's activation happens, nothing to activate", module) # noqa: E501
|
|
|
|
# Activate the OSD
|
|
rc, cmd, out, err = exec_command(
|
|
module, activate_osd())
|
|
|
|
elif action == 'zap':
|
|
# Zap the OSD
|
|
skip = []
|
|
for device_type in ['journal', 'data', 'db', 'wal']:
|
|
# 1/ if we passed vg/lv
|
|
if module.params.get('{}_vg'.format(device_type), None) and module.params.get(device_type, None): # noqa: E501
|
|
# 2/ check this is an actual lv/vg
|
|
ret = is_lv(module, module.params['{}_vg'.format(device_type)], module.params[device_type], container_image) # noqa: E501
|
|
skip.append(ret)
|
|
# 3/ This isn't a lv/vg device
|
|
if not ret:
|
|
module.params['{}_vg'.format(device_type)] = False
|
|
module.params[device_type] = False
|
|
# 4/ no journal|data|db|wal|_vg was passed, so it must be a raw device # noqa: E501
|
|
elif not module.params.get('{}_vg'.format(device_type), None) and module.params.get(device_type, None): # noqa: E501
|
|
skip.append(True)
|
|
|
|
cmd = zap_devices(module, container_image)
|
|
|
|
if any(skip) or module.params.get('osd_fsid', None) \
|
|
or module.params.get('osd_id', None):
|
|
rc, cmd, out, err = exec_command(
|
|
module, cmd)
|
|
for scan_cmd in ['vgscan', 'lvscan']:
|
|
module.run_command([scan_cmd, '--cache'])
|
|
else:
|
|
out = 'Skipped, nothing to zap'
|
|
err = ''
|
|
changed = False
|
|
rc = 0
|
|
|
|
elif action == 'list':
|
|
# List Ceph LVM Metadata on a device
|
|
changed = False
|
|
rc, cmd, out, err = exec_command(
|
|
module, list_osd(module, container_image))
|
|
|
|
elif action == 'inventory':
|
|
# List storage device inventory.
|
|
changed = False
|
|
rc, cmd, out, err = exec_command(
|
|
module, list_storage_inventory(module, container_image))
|
|
|
|
elif action == 'batch':
|
|
# Batch prepare AND activate OSDs
|
|
report = module.params.get('report', None)
|
|
|
|
# Add --report flag for the idempotency test
|
|
report_flags = [
|
|
'--report',
|
|
'--format=json',
|
|
]
|
|
|
|
cmd = batch(module, container_image, report=True)
|
|
batch_report_cmd = copy.copy(cmd)
|
|
batch_report_cmd.extend(report_flags)
|
|
|
|
# Run batch --report to see what's going to happen
|
|
# Do not run the batch command if there is nothing to do
|
|
rc, cmd, out, err = exec_command(
|
|
module, batch_report_cmd)
|
|
try:
|
|
if not out:
|
|
out = '{}'
|
|
report_result = json.loads(out)
|
|
except ValueError:
|
|
strategy_changed_in_out = "strategy changed" in out
|
|
strategy_changed_in_err = "strategy changed" in err
|
|
strategy_changed = strategy_changed_in_out or \
|
|
strategy_changed_in_err
|
|
if strategy_changed:
|
|
if strategy_changed_in_out:
|
|
out = json.dumps({"changed": False,
|
|
"stdout": out.rstrip("\r\n")})
|
|
elif strategy_changed_in_err:
|
|
out = json.dumps({"changed": False,
|
|
"stderr": err.rstrip("\r\n")})
|
|
rc = 0
|
|
changed = False
|
|
else:
|
|
out = out.rstrip("\r\n")
|
|
result = dict(
|
|
cmd=cmd,
|
|
stdout=out.rstrip('\r\n'),
|
|
stderr=err.rstrip('\r\n'),
|
|
rc=rc,
|
|
changed=changed,
|
|
)
|
|
if strategy_changed:
|
|
module.exit_json(**result)
|
|
module.fail_json(msg='non-zero return code', **result)
|
|
|
|
if not report:
|
|
if 'changed' in report_result:
|
|
# we have the old batch implementation
|
|
# if not asking for a report, let's just run the batch command
|
|
changed = report_result['changed']
|
|
if changed:
|
|
# Batch prepare the OSD
|
|
rc, cmd, out, err = exec_command(
|
|
module, batch(module, container_image))
|
|
err = re.sub('[a-zA-Z0-9+/]{38}==', '*' * 8, err)
|
|
else:
|
|
# we have the refactored batch, its idempotent so lets just
|
|
# run it
|
|
rc, cmd, out, err = exec_command(
|
|
module, batch(module, container_image))
|
|
err = re.sub('[a-zA-Z0-9+/]{38}==', '*' * 8, err)
|
|
else:
|
|
cmd = batch_report_cmd
|
|
|
|
endd = datetime.datetime.now()
|
|
delta = endd - startd
|
|
|
|
result = dict(
|
|
cmd=cmd,
|
|
start=str(startd),
|
|
end=str(endd),
|
|
delta=str(delta),
|
|
rc=rc,
|
|
stdout=out.rstrip('\r\n'),
|
|
stderr=err.rstrip('\r\n'),
|
|
changed=changed,
|
|
)
|
|
|
|
if rc != 0:
|
|
module.fail_json(msg='non-zero return code', **result)
|
|
|
|
module.exit_json(**result)
|
|
|
|
|
|
def main():
|
|
run_module()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|