#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (c) 2017, Kairo Araujo # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import absolute_import, division, print_function __metaclass__ = type 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. extends_documentation_fragment: - community.general.attributes attributes: check_mode: support: full diff_mode: support: none options: account_subsystem: description: - Specifies whether the file system is to be processed by the accounting subsystem. type: bool default: false attributes: description: - Specifies attributes for files system separated by comma. type: list elements: str default: - agblksize='4096' - isnapshot='no' auto_mount: description: - File system is automatically mounted at system restart. type: bool default: true 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. V(rw) (read-write) or V(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 V(absent). type: bool default: false size: description: - Specifies the file system size. - For already V(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. - V(present) check if file system exists, creates or resize. - V(absent) removes existing file system if already V(unmounted). - V(mounted) checks if the file system is mounted or mount the file system. - V(unmounted) check if the file system is unmounted or unmount the file system. type: str choices: [ absent, mounted, present, unmounted ] default: present vg: description: - Specifies an existing volume group (VG). type: str notes: - For more O(attributes), please check "crfs" AIX manual. ''' EXAMPLES = r''' - name: Create filesystem in a previously defined logical volume. community.general.aix_filesystem: device: testlv filesystem: /testfs state: present - name: Creating NFS filesystem from nfshost. community.general.aix_filesystem: device: /home/ftp nfs_server: nfshost filesystem: /home/ftp state: present - name: Creating a new file system without a previously logical volume. community.general.aix_filesystem: filesystem: /newfs size: 1G state: present vg: datavg - name: Unmounting /testfs. community.general.aix_filesystem: filesystem: /testfs state: unmounted - name: Resizing /mksysb to +512M. community.general.aix_filesystem: filesystem: /mksysb size: +512M state: present - name: Resizing /mksysb to 11G. community.general.aix_filesystem: filesystem: /mksysb size: 11G state: present - name: Resizing /mksysb to -2G. community.general.aix_filesystem: filesystem: /mksysb size: -2G state: present - name: Remove NFS filesystem /home/ftp. community.general.aix_filesystem: filesystem: /home/ftp rm_mount_point: true state: absent - name: Remove /newfs. community.general.aix_filesystem: filesystem: /newfs rm_mount_point: true 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_collections.community.general.plugins.module_utils._mount import ismount import re def _fs_exists(module, filesystem): """ Check if file system already exists on /etc/filesystems. :param module: Ansible module. :param community.general.filesystem: filesystem name. :return: True or False. """ lsfs_cmd = module.get_bin_path('lsfs', True) rc, lsfs_out, err = module.run_command([lsfs_cmd, "-l", filesystem]) if rc == 1: if re.findall("No record matching", err): return False else: module.fail_json(msg="Failed to run lsfs. Error message: %s" % 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([showmount_cmd, "-a", nfs_host]) if rc != 0: module.fail_json(msg="Failed to run showmount. Error message: %s" % 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([lsvg_cmd, "-o"]) if rc != 0: module.fail_json(msg="Failed executing %s command." % lsvg_cmd) rc, current_all_vgs, err = module.run_command([lsvg_cmd, "%s"]) 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([chfs_cmd, "-a", "size=%s" % 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(msg="Failed to run chfs. Error message: %s" % 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([mknfsmnt_cmd, "-f", filesystem, device, "-h", nfs_server, "-t", permissions, auto_mount, "-w", "bg"]) if rc != 0: module.fail_json(msg="Failed to run mknfsmnt. Error message: %s" % 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: cmd = [crfs_cmd, "-v", fs_type, "-m", filesystem, vg, device, mount_group, auto_mount, account_subsystem, "-p", permissions, size, "-a", attributes] rc, crfs_out, err = module.run_command(cmd) 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 %s. Error message: %s" % (cmd, 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: cmd = [rmfs_cmd, "-r", rm_mount_point, filesystem] rc, rmfs_out, err = module.run_command(cmd) if rc != 0: module.fail_json(msg="Failed to run %s. Error message: %s" % (cmd, 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([mount_cmd, filesystem]) if rc != 0: module.fail_json(msg="Failed to run mount. Error message: %s" % 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([unmount_cmd, filesystem]) if rc != 0: module.fail_json(msg="Failed to run unmount. Error message: %s" % 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', elements='str', 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: result['msg'] = 'Required parameter "device" and/or "vg" is missing for filesystem creation.' 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()