new module: Configure LVM and NFS file systems for AIX (#30810)

* new module: AIX LVM file system and NFS

This module creates, removes, mount and unmount LVM and NFS file system
for AIX using /etc/filesystems. For LVM file systems is also possible
to resize the file system.

* better parameters options structure

better parameters options structure

* Improved file system resize returns

Added better tratment for return codes for file system resize.
When a resize is not possible because no enough space on lv or
shrink is not allowed.

* improved doc and creation file system return code

- improved doc
- creation file system return code 10 was treated.

* Doc recomendations, dict result, line limit

- Added doc recomendations
- Changed return to dict results on main()
- Using 159 columns for code limit

* wrong changed return when file system is already

Fixed wrong changed return when file system is already mounted.
When the file system is already mounted the return for changed
is False.

* Fixed description and included playbook for tests

- Fixed description
- Included playbook for manual tests

* Various small bits to get this merged ASAP

* Rename test/legacy/aix_filesystem.yml to test/integration/targets/aix_filesystem/tasks/main.yml

Move integration test to its proper location

* Create aliases

* Fix CI issues
This commit is contained in:
Kairo Araujo 2019-01-05 02:18:12 +01:00 committed by Dag Wieers
parent bb77bc54d5
commit fc6d85e4cf
3 changed files with 704 additions and 0 deletions

View file

@ -0,0 +1,579 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Kairo Araujo <kairo@kairo.eti.br>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = r'''
---
author:
- Kairo Araujo (@kairoaraujo)
module: aix_filesystem
short_description: Configure LVM and NFS file systems for AIX
description:
- This module creates, removes, mount and unmount LVM and NFS file system for
AIX using C(/etc/filesystems).
- For LVM file systems is possible to resize a file system.
version_added: '2.8'
options:
account_subsystem:
description:
- Specifies whether the file system is to be processed by the accounting subsystem.
type: bool
default: no
attributes:
description:
- Specifies attributes for files system separated by comma.
type: str
default: agblksize='4096',isnapshot='no'
auto_mount:
description:
- File system is automatically mounted at system restart.
type: bool
default: yes
device:
description:
- Logical volume (LV) device name or remote export device to create a NFS file system.
- It is used to create a file system on an already existing logical volume or the exported NFS file system.
- If not mentioned a new logical volume name will be created following AIX standards (LVM).
type: str
fs_type:
description:
- Specifies the virtual file system type.
type: str
default: jfs2
permissions:
description:
- Set file system permissions. C(rw) (read-write) or C(ro) (read-only).
type: str
choices: [ ro, rw ]
default: rw
mount_group:
description:
- Specifies the mount group.
type: str
filesystem:
description:
- Specifies the mount point, which is the directory where the file system will be mounted.
type: str
required: true
nfs_server:
description:
- Specifies a Network File System (NFS) server.
type: str
rm_mount_point:
description:
- Removes the mount point directory when used with state C(absent).
type: bool
default: no
size:
description:
- Specifies the file system size.
- For already C(present) it will be resized.
- 512-byte blocks, Megabytes or Gigabytes. If the value has M specified
it will be in Megabytes. If the value has G specified it will be in
Gigabytes.
- If no M or G the value will be 512-byte blocks.
- If "+" is specified in begin of value, the value will be added.
- If "-" is specified in begin of value, the value will be removed.
- If "+" or "-" is not specified, the total value will be the specified.
- Size will respects the LVM AIX standards.
type: str
state:
description:
- Controls the file system state.
- C(present) check if file system exists, creates or resize.
- C(absent) removes existing file system if already C(unmounted).
- C(mounted) checks if the file system is mounted or mount the file system.
- C(unmounted) check if the file system is unmounted or unmount the file system.
type: str
required: true
choices: [ absent, mounted, present, unmounted ]
default: present
vg:
description:
- Specifies an existing volume group (VG).
type: str
notes:
- For more C(attributes), please check "crfs" AIX manual.
'''
EXAMPLES = r'''
- name: Create filesystem in a previously defined logical volume.
aix_filesystem:
device: testlv
filesystem: /testfs
state: present
- name: Creating NFS filesystem from nfshost.
aix_filesystem:
device: /home/ftp
nfs_server: nfshost
filesystem: /home/ftp
state: present
- name: Creating a new file system without a previously logical volume.
aix_filesystem:
filesystem: /newfs
size: 1G
state: present
vg: datavg
- name: Unmounting /testfs.
aix_filesystem:
filesystem: /testfs
state: unmounted
- name: Resizing /mksysb to +512M.
aix_filesystem:
filesystem: /mksysb
size: +512M
state: present
- name: Resizing /mksysb to 11G.
aix_filesystem:
filesystem: /mksysb
size: 11G
state: present
- name: Resizing /mksysb to -2G.
aix_filesystem:
filesystem: /mksysb
size: -2G
state: present
- name: Remove NFS filesystem /home/ftp.
aix_filesystem:
filesystem: /home/ftp
rm_mount_point: yes
state: absent
- name: Remove /newfs.
aix_filesystem:
filesystem: /newfs
rm_mount_point: yes
state: absent
'''
RETURN = r'''
changed:
description: Return changed for aix_filesystems actions as true or false.
returned: always
type: bool
msg:
description: Return message regarding the action.
returned: always
type: str
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ismount import ismount
import re
def _fs_exists(module, filesystem):
"""
Check if file system already exists on /etc/filesystems.
:param module: Ansible module.
:param filesystem: filesystem name.
:return: True or False.
"""
lsfs_cmd = module.get_bin_path('lsfs', True)
rc, lsfs_out, err = module.run_command("%s -l %s" % (lsfs_cmd, filesystem))
if rc == 1:
if re.findall("No record matching", err):
return False
else:
module.fail_json(msg="Failed to run lsfs.", rc=rc, err=err)
else:
return True
def _check_nfs_device(module, nfs_host, device):
"""
Validate if NFS server is exporting the device (remote export).
:param module: Ansible module.
:param nfs_host: nfs_host parameter, NFS server.
:param device: device parameter, remote export.
:return: True or False.
"""
showmount_cmd = module.get_bin_path('showmount', True)
rc, showmount_out, err = module.run_command(
"%s -a %s" % (showmount_cmd, nfs_host))
if rc != 0:
module.fail_json(msg="Failed to run showmount.", rc=rc, err=err)
else:
showmount_data = showmount_out.splitlines()
for line in showmount_data:
if line.split(':')[1] == device:
return True
return False
def _validate_vg(module, vg):
"""
Check the current state of volume group.
:param module: Ansible module argument spec.
:param vg: Volume Group name.
:return: True (VG in varyon state) or False (VG in varyoff state) or
None (VG does not exist), message.
"""
lsvg_cmd = module.get_bin_path('lsvg', True)
rc, current_active_vgs, err = module.run_command("%s -o" % lsvg_cmd)
if rc != 0:
module.fail_json(msg="Failed executing %s command." % lsvg_cmd)
rc, current_all_vgs, err = module.run_command("%s" % lsvg_cmd)
if rc != 0:
module.fail_json(msg="Failed executing %s command." % lsvg_cmd)
if vg in current_all_vgs and vg not in current_active_vgs:
msg = "Volume group %s is in varyoff state." % vg
return False, msg
elif vg in current_active_vgs:
msg = "Volume group %s is in varyon state." % vg
return True, msg
else:
msg = "Volume group %s does not exist." % vg
return None, msg
def resize_fs(module, filesystem, size):
""" Resize LVM file system. """
chfs_cmd = module.get_bin_path('chfs', True)
if not module.check_mode:
rc, chfs_out, err = module.run_command('%s -a size="%s" %s' % (chfs_cmd, size, filesystem))
if rc == 28:
changed = False
return changed, chfs_out
elif rc != 0:
if re.findall('Maximum allocation for logical', err):
changed = False
return changed, err
else:
module.fail_json("Failed to run chfs.", rc=rc, err=err)
else:
if re.findall('The filesystem size is already', chfs_out):
changed = False
else:
changed = True
return changed, chfs_out
else:
changed = True
msg = ''
return changed, msg
def create_fs(
module, fs_type, filesystem, vg, device, size, mount_group, auto_mount,
account_subsystem, permissions, nfs_server, attributes):
""" Create LVM file system or NFS remote mount point. """
attributes = ' -a '.join(attributes)
# Parameters definition.
account_subsys_opt = {
True: '-t yes',
False: '-t no'
}
if nfs_server is not None:
auto_mount_opt = {
True: '-A',
False: '-a'
}
else:
auto_mount_opt = {
True: '-A yes',
False: '-A no'
}
if size is None:
size = ''
else:
size = "-a size=%s" % size
if device is None:
device = ''
else:
device = "-d %s" % device
if vg is None:
vg = ''
else:
vg_state, msg = _validate_vg(module, vg)
if vg_state:
vg = "-g %s" % vg
else:
changed = False
return changed, msg
if mount_group is None:
mount_group = ''
else:
mount_group = "-u %s" % mount_group
auto_mount = auto_mount_opt[auto_mount]
account_subsystem = account_subsys_opt[account_subsystem]
if nfs_server is not None:
# Creates a NFS file system.
mknfsmnt_cmd = module.get_bin_path('mknfsmnt', True)
if not module.check_mode:
rc, mknfsmnt_out, err = module.run_command('%s -f "%s" %s -h "%s" -t "%s" "%s" -w "bg"' % (
mknfsmnt_cmd, filesystem, device, nfs_server, permissions, auto_mount))
if rc != 0:
module.fail_json(msg="Failed to run mknfsmnt.", rc=rc, err=err)
else:
changed = True
msg = "NFS file system %s created." % filesystem
return changed, msg
else:
changed = True
msg = ''
return changed, msg
else:
# Creates a LVM file system.
crfs_cmd = module.get_bin_path('crfs', True)
if not module.check_mode:
rc, crfs_out, err = module.run_command(
"%s -v %s -m %s %s %s %s %s %s -p %s %s -a %s" % (
crfs_cmd, fs_type, filesystem, vg, device, mount_group, auto_mount, account_subsystem, permissions, size, attributes))
if rc == 10:
module.exit_json(
msg="Using a existent previously defined logical volume, "
"volume group needs to be empty. %s" % err)
elif rc != 0:
module.fail_json(msg="Failed to run crfs.", rc=rc, err=err)
else:
changed = True
return changed, crfs_out
else:
changed = True
msg = ''
return changed, msg
def remove_fs(module, filesystem, rm_mount_point):
""" Remove an LVM file system or NFS entry. """
# Command parameters.
rm_mount_point_opt = {
True: '-r',
False: ''
}
rm_mount_point = rm_mount_point_opt[rm_mount_point]
rmfs_cmd = module.get_bin_path('rmfs', True)
if not module.check_mode:
rc, rmfs_out, err = module.run_command("%s -r %s %s" % (rmfs_cmd, rm_mount_point, filesystem))
if rc != 0:
module.fail_json(msg="Failed to run rmfs.", rc=rc, err=err)
else:
changed = True
msg = rmfs_out
if not rmfs_out:
msg = "File system %s removed." % filesystem
return changed, msg
else:
changed = True
msg = ''
return changed, msg
def mount_fs(module, filesystem):
""" Mount a file system. """
mount_cmd = module.get_bin_path('mount', True)
if not module.check_mode:
rc, mount_out, err = module.run_command(
"%s %s" % (mount_cmd, filesystem))
if rc != 0:
module.fail_json("Failed to run mount.", rc=rc, err=err)
else:
changed = True
msg = "File system %s mounted." % filesystem
return changed, msg
else:
changed = True
msg = ''
return changed, msg
def unmount_fs(module, filesystem):
""" Unmount a file system."""
unmount_cmd = module.get_bin_path('unmount', True)
if not module.check_mode:
rc, unmount_out, err = module.run_command("%s %s" % (unmount_cmd, filesystem))
if rc != 0:
module.fail_json("Failed to run unmount.", rc=rc, err=err)
else:
changed = True
msg = "File system %s unmounted." % filesystem
return changed, msg
else:
changed = True
msg = ''
return changed, msg
def main():
module = AnsibleModule(
argument_spec=dict(
account_subsystem=dict(type='bool', default=False),
attributes=dict(type='list', default=["agblksize='4096'", "isnapshot='no'"]),
auto_mount=dict(type='bool', default=True),
device=dict(type='str'),
filesystem=dict(type='str', required=True),
fs_type=dict(type='str', default='jfs2'),
permissions=dict(type='str', default='rw', choices=['rw', 'ro']),
mount_group=dict(type='str'),
nfs_server=dict(type='str'),
rm_mount_point=dict(type='bool', default=False),
size=dict(type='str'),
state=dict(type='str', default='present', choices=['absent', 'mounted', 'present', 'unmounted']),
vg=dict(type='str'),
),
supports_check_mode=True,
)
account_subsystem = module.params['account_subsystem']
attributes = module.params['attributes']
auto_mount = module.params['auto_mount']
device = module.params['device']
fs_type = module.params['fs_type']
permissions = module.params['permissions']
mount_group = module.params['mount_group']
filesystem = module.params['filesystem']
nfs_server = module.params['nfs_server']
rm_mount_point = module.params['rm_mount_point']
size = module.params['size']
state = module.params['state']
vg = module.params['vg']
result = dict(
changed=False,
msg='',
)
if state == 'present':
fs_mounted = ismount(filesystem)
fs_exists = _fs_exists(module, filesystem)
# Check if fs is mounted or exists.
if fs_mounted or fs_exists:
result['msg'] = "File system %s already exists." % filesystem
result['changed'] = False
# If parameter size was passed, resize fs.
if size is not None:
result['changed'], result['msg'] = resize_fs(module, filesystem, size)
# If fs doesn't exist, create it.
else:
# Check if fs will be a NFS device.
if nfs_server is not None:
if device is None:
result['msg'] = 'Parameter "device" is required when "nfs_server" is defined.'
module.fail_json(**result)
else:
# Create a fs from NFS export.
if _check_nfs_device(module, nfs_server, device):
result['changed'], result['msg'] = create_fs(
module, fs_type, filesystem, vg, device, size, mount_group, auto_mount, account_subsystem, permissions, nfs_server, attributes)
if device is None:
if vg is None:
module.fail_json(**result)
else:
# Create a fs from
result['changed'], result['msg'] = create_fs(
module, fs_type, filesystem, vg, device, size, mount_group, auto_mount, account_subsystem, permissions, nfs_server, attributes)
if device is not None and nfs_server is None:
# Create a fs from a previously lv device.
result['changed'], result['msg'] = create_fs(
module, fs_type, filesystem, vg, device, size, mount_group, auto_mount, account_subsystem, permissions, nfs_server, attributes)
elif state == 'absent':
if ismount(filesystem):
result['msg'] = "File system %s mounted." % filesystem
else:
fs_status = _fs_exists(module, filesystem)
if not fs_status:
result['msg'] = "File system %s does not exist." % filesystem
else:
result['changed'], result['msg'] = remove_fs(module, filesystem, rm_mount_point)
elif state == 'mounted':
if ismount(filesystem):
result['changed'] = False
result['msg'] = "File system %s already mounted." % filesystem
else:
result['changed'], result['msg'] = mount_fs(module, filesystem)
elif state == 'unmounted':
if not ismount(filesystem):
result['changed'] = False
result['msg'] = "File system %s already unmounted." % filesystem
else:
result['changed'], result['msg'] = unmount_fs(module, filesystem)
else:
# Unreachable codeblock
result['msg'] = "Unexpected state %s." % state
module.fail_json(**result)
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -0,0 +1 @@
unsupported

View file

@ -0,0 +1,124 @@
---
- name: Testing aix_filesystem module.
hosts: lpar24
tasks:
- name: Umounting /testfs
aix_filesystem:
filesystem: /testfs
state: unmounted
- name: Removing /testfs
aix_filesystem:
filesystem: /testfs
state: absent
- name: Creating a new file system
aix_filesystem:
filesystem: /newfs
size: 1G
state: present
vg: datavg
# It requires a host (nfshost) exporting the NFS
- name: Creating NFS filesystem from nfshost (Linux NFS server)
aix_filesystem:
device: /home/ftp
nfs_server: nfshost
filesystem: /nfs/ftp
state: present
# It requires a volume group named datavg (next three actions)
- name: Creating a logical volume testlv (aix_lvol module)
aix_lvol:
vg: datavg
lv: testlv
size: 2G
state: present
- name: Create filesystem in a previously defined logical volume
aix_filesystem:
device: testlv
filesystem: /testfs
state: present
- name: Create an already existing filesystem using existing logical volume.
aix_filesystem:
vg: datavg
device: mksysblv
filesystem: /mksysb
state: present
- name: Create a filesystem in a non-existing VG
aix_filesystem:
vg: nonexistvg
filesystem: /newlv
state: present
- name: Resizing /mksysb to 1G
aix_filesystem:
filesystem: /mksysb
size: 1G
state: present
- name: Resizing /mksysb to +512M
aix_filesystem:
filesystem: /mksysb
size: +512M
state: present
- name: Resizing /mksysb to 11G
aix_filesystem:
filesystem: /mksysb
size: 11G
state: present
- name: Resizing /mksysb to 11G (already done)
aix_filesystem:
filesystem: /mksysb
size: 11G
state: present
- name: Resizing /mksysb to -2G
aix_filesystem:
filesystem: /mksysb
size: -2G
state: present
- name: Resizing /mksysb to 100G (no enought space)
aix_filesystem:
filesystem: /mksysb
size: +100G
state: present
- name: Unmount filesystem /home/ftp
aix_filesystem:
filesystem: /home/ftp
state: unmounted
- name: Remove NFS filesystem /home/ftp
aix_filesystem:
filesystem: /home/ftp
rm_mount_point: yes
state: absent
- name: Mount filesystem /newfs
aix_filesystem:
filesystem: /newfs
state: mounted
- name: Remove mounted /newfs
aix_filesystem:
filesystem: /newfs
rm_mount_point: yes
state: absent
- name: Umount /newfs
aix_filesystem:
filesystem: /newfs
state: unmounted
- name: Remove /newfs
aix_filesystem:
filesystem: /newfs
rm_mount_point: yes
state: absent