From 993727ff5e9a182ea0c0a7d3c8a5fed85686572e Mon Sep 17 00:00:00 2001 From: William Leemans Date: Sun, 7 Apr 2019 21:11:32 +0200 Subject: [PATCH] New module: xfs_quota (#51654) * New module: xfs_quota * wildcard import resolution * pep8 fixes * validate-modules fixes * pep8 and validate-module fixes * removal of extra copyright info * description capitalization and trailing dot * Some more description * type specification * removal of notes * reorder imports * sorting * starting with variable type * removal of defaults * results to dict * results to dict * complete condition * removal of spaces for pep8 compliancy, removal of root check, addition of tests for failed xfs_quota commands indicating the need for elevation/capabilities * lost result * typo * historical override removal * report back values * unexpected spaces removal * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * typo * raw data, full data * removal of several else: statements and indentation * pep8 * typo * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * typo * use bytes as base, not kilobytes * be consistent * integration tests * lint * empty lines * Update lib/ansible/modules/system/xfs_quota.py Co-Authored-By: bushvin * updates * ci group * XFS not supported on osx * XFS not supported by freebsd * long lines and removal of xfs_quota dict in return * RETURN values * no more xfs_quota dict --- lib/ansible/modules/system/xfs_quota.py | 433 ++++++++++++++++++ test/integration/targets/xfs_quota/aliases | 5 + .../targets/xfs_quota/defaults/main.yml | 42 ++ .../targets/xfs_quota/tasks/gquota.yml | 158 +++++++ .../targets/xfs_quota/tasks/main.yml | 18 + .../targets/xfs_quota/tasks/pquota.yml | 202 ++++++++ .../targets/xfs_quota/tasks/uquota.yml | 158 +++++++ 7 files changed, 1016 insertions(+) create mode 100644 lib/ansible/modules/system/xfs_quota.py create mode 100644 test/integration/targets/xfs_quota/aliases create mode 100644 test/integration/targets/xfs_quota/defaults/main.yml create mode 100644 test/integration/targets/xfs_quota/tasks/gquota.yml create mode 100644 test/integration/targets/xfs_quota/tasks/main.yml create mode 100644 test/integration/targets/xfs_quota/tasks/pquota.yml create mode 100644 test/integration/targets/xfs_quota/tasks/uquota.yml diff --git a/lib/ansible/modules/system/xfs_quota.py b/lib/ansible/modules/system/xfs_quota.py new file mode 100644 index 00000000000..98a65797b1f --- /dev/null +++ b/lib/ansible/modules/system/xfs_quota.py @@ -0,0 +1,433 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Emmanouil Kampitakis +# Copyright: (c) 2018, William Leemans + +# 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''' +--- +module: xfs_quota +short_description: Manage quotas on XFS filesystems +description: + - Configure quotas on XFS filesystems. + - Before using this module /etc/projects and /etc/projid need to be configured. +version_added: "2.8" +author: +- William Leemans (@bushvin) +options: + type: + description: + - The XFS quota type. + type: str + required: true + choices: + - user + - group + - project + name: + description: + - The name of the user, group or project to apply the quota to, if other than default. + type: str + mountpoint: + description: + - The mount point on which to apply the quotas. + type: str + required: true + bhard: + description: + - Hard blocks quota limit. + - This argument supports human readable sizes. + type: str + bsoft: + description: + - Soft blocks quota limit. + - This argument supports human readable sizes. + type: str + ihard: + description: + - Hard inodes quota limit. + type: int + isoft: + description: + - Soft inodes quota limit. + type: int + rtbhard: + description: + - Hard realtime blocks quota limit. + - This argument supports human readable sizes. + type: str + rtbsoft: + description: + - Soft realtime blocks quota limit. + - This argument supports human readable sizes. + type: str + state: + description: + - Whether to apply the limits or remove them. + - When removing limit, they are set to 0, and not quite removed. + type: str + default: present + choices: + - present + - absent + +requirements: + - xfsprogs +''' + +EXAMPLES = r''' +- name: Set default project soft and hard limit on /opt of 1g + xfs_quota: + type: project + mountpoint: /opt + bsoft: 1g + bhard: 1g + state: present + +- name: Remove the default limits on /opt + xfs_quota: + type: project + mountpoint: /opt + state: absent + +- name: Set default soft user inode limits on /home of 1024 inodes and hard of 2048 + xfs_quota: + type: user + mountpoint: /home + isoft: 1024 + ihard: 2048 + +''' + +RETURN = r''' +bhard: + description: the current bhard setting in bytes + returned: always + type: int + sample: 1024 +bsoft: + description: the current bsoft setting in bytes + returned: always + type: int + sample: 1024 +ihard: + description: the current ihard setting in bytes + returned: always + type: int + sample: 100 +isoft: + description: the current isoft setting in bytes + returned: always + type: int + sample: 100 +rtbhard: + description: the current rtbhard setting in bytes + returned: always + type: int + sample: 1024 +rtbsoft: + description: the current rtbsoft setting in bytes + returned: always + type: int + sample: 1024 +''' + +import grp +import os +import pwd + +from ansible.module_utils.basic import AnsibleModule, human_to_bytes + + +def main(): + module = AnsibleModule( + argument_spec=dict( + bhard=dict(type='str'), + bsoft=dict(type='str'), + ihard=dict(type='int'), + isoft=dict(type='int'), + mountpoint=dict(type='str', required=True), + name=dict(type='str'), + rtbhard=dict(type='str'), + rtbsoft=dict(type='str'), + state=dict(type='str', default='present', choices=['absent', 'present']), + type=dict(type='str', required=True, choices=['group', 'project', 'user']) + ), + supports_check_mode=True, + ) + + quota_type = module.params['type'] + name = module.params['name'] + mountpoint = module.params['mountpoint'] + bhard = module.params['bhard'] + bsoft = module.params['bsoft'] + ihard = module.params['ihard'] + isoft = module.params['isoft'] + rtbhard = module.params['rtbhard'] + rtbsoft = module.params['rtbsoft'] + state = module.params['state'] + + if bhard is not None: + bhard = human_to_bytes(bhard) + + if bsoft is not None: + bsoft = human_to_bytes(bsoft) + + if rtbhard is not None: + rtbhard = human_to_bytes(rtbhard) + + if rtbsoft is not None: + rtbsoft = human_to_bytes(rtbsoft) + + result = dict( + changed=False, + ) + + if not os.path.ismount(mountpoint): + module.fail_json(msg="Path '%s' is not a mount point" % mountpoint, **result) + + mp = get_fs_by_mountpoint(mountpoint) + if mp is None: + module.fail_json(msg="Path '%s' is not a mount point or not located on an xfs file system." % mountpoint, **result) + + if quota_type == 'user': + type_arg = '-u' + quota_default = 'root' + if name is None: + name = quota_default + + if 'uquota' not in mp['mntopts'] and 'usrquota' not in mp['mntopts'] and 'quota' not in mp['mntopts'] and 'uqnoenforce' not in mp['mntopts'] and \ + 'qnoenforce' not in mp['mntopts']: + module.fail_json( + msg="Path '%s' is not mounted with the uquota/usrquota/quota/uqnoenforce/qnoenforce option." % mountpoint, **result + ) + try: + pwd.getpwnam(name) + except KeyError as e: + module.fail_json(msg="User '%s' does not exist." % name, **result) + + elif quota_type == 'group': + type_arg = '-g' + quota_default = 'root' + if name is None: + name = quota_default + + if 'gquota' not in mp['mntopts'] and 'grpquota' not in mp['mntopts'] and 'gqnoenforce' not in mp['mntopts']: + module.fail_json( + msg="Path '%s' is not mounted with the gquota/grpquota/gqnoenforce option. (current options: %s)" % (mountpoint, mp['mntopts']), **result + ) + try: + grp.getgrnam(name) + except KeyError as e: + module.fail_json(msg="User '%s' does not exist." % name, **result) + + elif quota_type == 'project': + type_arg = '-p' + quota_default = '#0' + if name is None: + name = quota_default + + if 'pquota' not in mp['mntopts'] and 'prjquota' not in mp['mntopts'] and 'pqnoenforce' not in mp['mntopts']: + module.fail_json(msg="Path '%s' is not mounted with the pquota/prjquota/pqnoenforce option." % mountpoint, **result) + + if name != quota_default and not os.path.isfile('/etc/projects'): + module.fail_json(msg="Path '/etc/projects' does not exist.", **result) + + if name != quota_default and not os.path.isfile('/etc/projid'): + module.fail_json(msg="Path '/etc/projid' does not exist.", **result) + + if name != quota_default and name is not None and get_project_id(name) is None: + module.fail_json(msg="Entry '%s' has not been defined in /etc/projid." % name, **result) + + prj_set = True + if name != quota_default: + cmd = 'project %s' % name + rc, stdout, stderr = exec_quota(module, cmd, mountpoint) + if rc != 0: + result['cmd'] = cmd + result['rc'] = rc + result['stdout'] = stdout + result['stderr'] = stderr + module.fail_json(msg='Could not get project state.', **result) + else: + for line in stdout.split('\n'): + if "Project Id '%s' - is not set." in line: + prj_set = False + break + + if not prj_set and not module.check_mode: + cmd = 'project -s' + rc, stdout, stderr = exec_quota(module, cmd, mountpoint) + if rc != 0: + result['cmd'] = cmd + result['rc'] = rc + result['stdout'] = stdout + result['stderr'] = stderr + module.fail_json(msg='Could not get quota realtime block report.', **result) + + result['changed'] = True + + elif not prj_set and module.check_mode: + result['changed'] = True + + # Set limits + if state == 'absent': + bhard = 0 + bsoft = 0 + ihard = 0 + isoft = 0 + rtbhard = 0 + rtbsoft = 0 + + current_bsoft, current_bhard = quota_report(module, mountpoint, name, quota_type, 'b') + current_isoft, current_ihard = quota_report(module, mountpoint, name, quota_type, 'i') + current_rtbsoft, current_rtbhard = quota_report(module, mountpoint, name, quota_type, 'rtb') + + result['xfs_quota'] = dict( + bsoft=current_bsoft, + bhard=current_bhard, + isoft=current_isoft, + ihard=current_ihard, + rtbsoft=current_rtbsoft, + rtbhard=current_rtbhard + ) + + limit = [] + if bsoft is not None and int(bsoft) != current_bsoft: + limit.append('bsoft=%s' % bsoft) + result['bsoft'] = int(bsoft) + + if bhard is not None and int(bhard) != current_bhard: + limit.append('bhard=%s' % bhard) + result['bhard'] = int(bhard) + + if isoft is not None and isoft != current_isoft: + limit.append('isoft=%s' % isoft) + result['isoft'] = isoft + + if ihard is not None and ihard != current_ihard: + limit.append('ihard=%s' % ihard) + result['ihard'] = ihard + + if rtbsoft is not None and int(rtbsoft) != current_rtbsoft: + limit.append('rtbsoft=%s' % rtbsoft) + result['rtbsoft'] = int(rtbsoft) + + if rtbhard is not None and int(rtbhard) != current_rtbhard: + limit.append('rtbhard=%s' % rtbhard) + result['rtbhard'] = int(rtbhard) + + if len(limit) > 0 and not module.check_mode: + if name == quota_default: + cmd = 'limit %s -d %s' % (type_arg, ' '.join(limit)) + else: + cmd = 'limit %s %s %s' % (type_arg, ' '.join(limit), name) + + rc, stdout, stderr = exec_quota(module, cmd, mountpoint) + if rc != 0: + result['cmd'] = cmd + result['rc'] = rc + result['stdout'] = stdout + result['stderr'] = stderr + module.fail_json(msg='Could not set limits.', **result) + + result['changed'] = True + + elif len(limit) > 0 and module.check_mode: + result['changed'] = True + + module.exit_json(**result) + + +def quota_report(module, mountpoint, name, quota_type, used_type): + soft = None + hard = None + + if quota_type == 'project': + type_arg = '-p' + elif quota_type == 'user': + type_arg = '-u' + elif quota_type == 'group': + type_arg = '-g' + + if used_type == 'b': + used_arg = '-b' + used_name = 'blocks' + factor = 1024 + elif used_type == 'i': + used_arg = '-i' + used_name = 'inodes' + factor = 1 + elif used_type == 'rtb': + used_arg = '-r' + used_name = 'realtime blocks' + factor = 1024 + + rc, stdout, stderr = exec_quota(module, 'report %s %s' % (type_arg, used_arg), mountpoint) + + if rc != 0: + result = dict( + changed=False, + rc=rc, + stdout=stdout, + stderr=stderr, + ) + module.fail_json(msg='Could not get quota report for %s.' % used_name, **result) + + for line in stdout.split('\n'): + line = line.strip().split() + if len(line) > 3 and line[0] == name: + soft = int(line[2]) * factor + hard = int(line[3]) * factor + break + + return soft, hard + + +def exec_quota(module, cmd, mountpoint): + cmd = ['xfs_quota', '-x', '-c'] + [cmd, mountpoint] + (rc, stdout, stderr) = module.run_command(cmd, use_unsafe_shell=True) + if "XFS_GETQUOTA: Operation not permitted" in stderr.split('\n') or \ + rc == 1 and 'xfs_quota: cannot set limits: Operation not permitted' in stderr.split('\n'): + module.fail_json(msg='You need to be root or have CAP_SYS_ADMIN capability to perform this operation') + + return rc, stdout, stderr + + +def get_fs_by_mountpoint(mountpoint): + mpr = None + with open('/proc/mounts', 'r') as s: + for line in s.readlines(): + mp = line.strip().split() + if len(mp) == 6 and mp[1] == mountpoint and mp[2] == 'xfs': + mpr = dict(zip(['spec', 'file', 'vfstype', 'mntopts', 'freq', 'passno'], mp)) + mpr['mntopts'] = mpr['mntopts'].split(',') + break + return mpr + + +def get_project_id(name): + prjid = None + with open('/etc/projid', 'r') as s: + for line in s.readlines(): + line = line.strip().partition(':') + if line[0] == name: + prjid = line[2] + break + + return prjid + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/xfs_quota/aliases b/test/integration/targets/xfs_quota/aliases new file mode 100644 index 00000000000..fd6728e0757 --- /dev/null +++ b/test/integration/targets/xfs_quota/aliases @@ -0,0 +1,5 @@ +needs/privileged +needs/root +shippable/posix/group1 +skip/osx +skip/freebsd diff --git a/test/integration/targets/xfs_quota/defaults/main.yml b/test/integration/targets/xfs_quota/defaults/main.yml new file mode 100644 index 00000000000..ff030ce79bf --- /dev/null +++ b/test/integration/targets/xfs_quota/defaults/main.yml @@ -0,0 +1,42 @@ +--- +uquota_default_bsoft: 1m +uquota_default_bhard: 2m +uquota_default_isoft: 100 +uquota_default_ihard: 200 +uquota_default_rtbsoft: 1m +uquota_default_rtbhard: 2m + +uquota_user_bsoft: 2m +uquota_user_bhard: 3m +uquota_user_isoft: 300 +uquota_user_ihard: 400 +uquota_user_rtbsoft: 3m +uquota_user_rtbhard: 4m + +gquota_default_bsoft: 1m +gquota_default_bhard: 2m +gquota_default_isoft: 100 +gquota_default_ihard: 200 +gquota_default_rtbsoft: 1m +gquota_default_rtbhard: 2m + +gquota_group_bsoft: 2m +gquota_group_bhard: 3m +gquota_group_isoft: 300 +gquota_group_ihard: 400 +gquota_group_rtbsoft: 3m +gquota_group_rtbhard: 4m + +pquota_default_bsoft: 1m +pquota_default_bhard: 2m +pquota_default_isoft: 100 +pquota_default_ihard: 200 +pquota_default_rtbsoft: 1m +pquota_default_rtbhard: 2m + +pquota_project_bsoft: 2m +pquota_project_bhard: 3m +pquota_project_isoft: 300 +pquota_project_ihard: 400 +pquota_project_rtbsoft: 3m +pquota_project_rtbhard: 4m diff --git a/test/integration/targets/xfs_quota/tasks/gquota.yml b/test/integration/targets/xfs_quota/tasks/gquota.yml new file mode 100644 index 00000000000..03e672358dd --- /dev/null +++ b/test/integration/targets/xfs_quota/tasks/gquota.yml @@ -0,0 +1,158 @@ +--- +- name: 'Create disk image' + command: > + dd if=/dev/zero of={{ ansible_user_dir }}/ansible_testing/img-gquota bs=1M count=20 + +- name: 'Create XFS filesystem' + filesystem: + dev: '{{ ansible_user_dir }}/ansible_testing/img-gquota' + fstype: xfs + +- block: + - name: 'Mount filesystem' + mount: + fstab: '{{ ansible_user_dir }}/ansible_testing/fstab' + src: '{{ ansible_user_dir }}/ansible_testing/img-gquota' + path: '{{ ansible_user_dir }}/ansible_testing/gquota' + fstype: xfs + opts: gquota + state: mounted + become: True + + - name: 'Apply default group limits' + xfs_quota: + bsoft: '{{ gquota_default_bsoft }}' + bhard: '{{ gquota_default_bhard }}' + isoft: '{{ gquota_default_isoft }}' + ihard: '{{ gquota_default_ihard }}' + mountpoint: '{{ ansible_user_dir }}/ansible_testing/gquota' + rtbsoft: '{{ gquota_default_rtbsoft }}' + rtbhard: '{{ gquota_default_rtbhard }}' + type: group + become: True + register: test_gquota_default_before + + - name: Assert default group limits results + assert: + that: + - test_gquota_default_before.changed + - test_gquota_default_before.bsoft == gquota_default_bsoft|human_to_bytes + - test_gquota_default_before.bhard == gquota_default_bhard|human_to_bytes + - test_gquota_default_before.isoft == gquota_default_isoft + - test_gquota_default_before.ihard == gquota_default_ihard + - test_gquota_default_before.rtbsoft == gquota_default_rtbsoft|human_to_bytes + - test_gquota_default_before.rtbhard == gquota_default_rtbhard|human_to_bytes + + - name: 'Apply group limits' + xfs_quota: + bsoft: '{{ gquota_group_bsoft }}' + bhard: '{{ gquota_group_bhard }}' + isoft: '{{ gquota_group_isoft }}' + ihard: '{{ gquota_group_ihard }}' + mountpoint: '{{ ansible_user_dir }}/ansible_testing/gquota' + name: xfsquotauser + rtbsoft: '{{ gquota_group_rtbsoft }}' + rtbhard: '{{ gquota_group_rtbhard }}' + type: group + become: True + register: test_gquota_group_before + + - name: Assert group limits results for xfsquotauser + assert: + that: + - test_gquota_group_before.changed + - test_gquota_group_before.bsoft == gquota_group_bsoft|human_to_bytes + - test_gquota_group_before.bhard == gquota_group_bhard|human_to_bytes + - test_gquota_group_before.isoft == gquota_group_isoft + - test_gquota_group_before.ihard == gquota_group_ihard + - test_gquota_group_before.rtbsoft == gquota_group_rtbsoft|human_to_bytes + - test_gquota_group_before.rtbhard == gquota_group_rtbhard|human_to_bytes + + - name: 'Re-apply default group limits' + xfs_quota: + bsoft: '{{ gquota_default_bsoft }}' + bhard: '{{ gquota_default_bhard }}' + isoft: '{{ gquota_default_isoft }}' + ihard: '{{ gquota_default_ihard }}' + mountpoint: '{{ ansible_user_dir }}/ansible_testing/gquota' + rtbsoft: '{{ gquota_default_rtbsoft }}' + rtbhard: '{{ gquota_default_rtbhard }}' + type: group + become: True + register: test_gquota_default_after + + - name: Assert default group limits results after re-apply + assert: + that: + - not test_gquota_default_after.changed + + - name: 'Re-apply group limits' + xfs_quota: + bsoft: '{{ gquota_group_bsoft }}' + bhard: '{{ gquota_group_bhard }}' + isoft: '{{ gquota_group_isoft }}' + ihard: '{{ gquota_group_ihard }}' + mountpoint: '{{ ansible_user_dir }}/ansible_testing/gquota' + name: xfsquotauser + rtbsoft: '{{ gquota_group_rtbsoft }}' + rtbhard: '{{ gquota_group_rtbhard }}' + type: group + become: True + register: test_gquota_group_after + + - name: Assert group limits results for xfsquotauser after re-apply + assert: + that: + - not test_gquota_group_after.changed + + - name: 'Reset default group limits' + xfs_quota: + mountpoint: '{{ ansible_user_dir }}/ansible_testing/gquota' + state: absent + type: group + become: True + register: test_reset_gquota_default + + - name: Assert reset of default group limits results + assert: + that: + - test_reset_gquota_default.changed + - test_reset_gquota_default.bsoft == 0 + - test_reset_gquota_default.bhard == 0 + - test_reset_gquota_default.isoft == 0 + - test_reset_gquota_default.ihard == 0 + - test_reset_gquota_default.rtbsoft == 0 + - test_reset_gquota_default.rtbhard == 0 + + - name: 'Reset group limits for xfsquotauser' + xfs_quota: + mountpoint: '{{ ansible_user_dir }}/ansible_testing/gquota' + name: xfsquotauser + state: absent + type: group + become: True + register: test_reset_gquota_group + + - name: Assert reset of default group limits results + assert: + that: + - test_reset_gquota_group.changed + - test_reset_gquota_group.bsoft == 0 + - test_reset_gquota_group.bhard == 0 + - test_reset_gquota_group.isoft == 0 + - test_reset_gquota_group.ihard == 0 + - test_reset_gquota_group.rtbsoft == 0 + - test_reset_gquota_group.rtbhard == 0 + + always: + - name: 'Unmount filesystem' + mount: + fstab: '{{ ansible_user_dir }}/ansible_testing/fstab' + path: '{{ ansible_user_dir }}/ansible_testing/gquota' + state: unmounted + become: True + + - name: Remove disk image + file: + path: '{{ ansible_user_dir }}/ansible_testing/img-gquota' + state: absent diff --git a/test/integration/targets/xfs_quota/tasks/main.yml b/test/integration/targets/xfs_quota/tasks/main.yml new file mode 100644 index 00000000000..bccfd7fde59 --- /dev/null +++ b/test/integration/targets/xfs_quota/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- block: + - name: Create test user + user: + name: xfsquotauser + state: present + become: yes + + - include_tasks: uquota.yml + - include_tasks: gquota.yml + - include_tasks: pquota.yml + + always: + - name: cleanup test user + user: + name: xfsquotauser + state: absent + become: yes diff --git a/test/integration/targets/xfs_quota/tasks/pquota.yml b/test/integration/targets/xfs_quota/tasks/pquota.yml new file mode 100644 index 00000000000..3ae3a8a32c4 --- /dev/null +++ b/test/integration/targets/xfs_quota/tasks/pquota.yml @@ -0,0 +1,202 @@ +--- +- name: 'Create disk image' + command: > + dd if=/dev/zero of={{ ansible_user_dir }}/ansible_testing/img-pquota bs=1M count=20 + +- name: 'Create XFS filesystem' + filesystem: + dev: '{{ ansible_user_dir }}/ansible_testing/img-pquota' + fstype: xfs + +- name: Create xfs related files + file: + path: '/etc/{{ item }}' + state: touch + become: True + loop: + - 'projid' + - 'projects' + +- name: 'Add test xfs quota project id' + lineinfile: + path: /etc/projid + line: 'xft_quotaval:99999' + state: present + become: True + +- name: 'Add test xfs quota project path' + lineinfile: + path: /etc/projects + line: '99999:{{ ansible_user_dir }}/ansible_testing/pquota/test' + state: present + become: True + +- block: + - name: 'Mount filesystem' + mount: + fstab: '{{ ansible_user_dir }}/ansible_testing/fstab' + src: '{{ ansible_user_dir }}/ansible_testing/img-pquota' + path: '{{ ansible_user_dir }}/ansible_testing/pquota' + fstype: xfs + opts: pquota + state: mounted + become: True + + - name: 'Create test directory' + file: + path: '{{ ansible_user_dir }}/ansible_testing/pquota/test' + state: directory + become: True + + + - name: 'Apply default project limits' + xfs_quota: + bsoft: '{{ pquota_default_bsoft }}' + bhard: '{{ pquota_default_bhard }}' + isoft: '{{ pquota_default_isoft }}' + ihard: '{{ pquota_default_ihard }}' + mountpoint: '{{ ansible_user_dir }}/ansible_testing/pquota' + rtbsoft: '{{ pquota_default_rtbsoft }}' + rtbhard: '{{ pquota_default_rtbhard }}' + type: project + become: True + register: test_pquota_default_before + + - name: Assert default project limits results + assert: + that: + - test_pquota_default_before.changed + - test_pquota_default_before.bsoft == pquota_default_bsoft|human_to_bytes + - test_pquota_default_before.bhard == pquota_default_bhard|human_to_bytes + - test_pquota_default_before.isoft == pquota_default_isoft + - test_pquota_default_before.ihard == pquota_default_ihard + - test_pquota_default_before.rtbsoft == pquota_default_rtbsoft|human_to_bytes + - test_pquota_default_before.rtbhard == pquota_default_rtbhard|human_to_bytes + + - name: 'Apply project limits' + xfs_quota: + bsoft: '{{ pquota_project_bsoft }}' + bhard: '{{ pquota_project_bhard }}' + isoft: '{{ pquota_project_isoft }}' + ihard: '{{ pquota_project_ihard }}' + mountpoint: '{{ ansible_user_dir }}/ansible_testing/pquota' + name: xft_quotaval + rtbsoft: '{{ pquota_project_rtbsoft }}' + rtbhard: '{{ pquota_project_rtbhard }}' + type: project + become: True + register: test_pquota_project_before + + - name: Assert project limits results for xft_quotaval + assert: + that: + - test_pquota_project_before.changed + - test_pquota_project_before.bsoft == pquota_project_bsoft|human_to_bytes + - test_pquota_project_before.bhard == pquota_project_bhard|human_to_bytes + - test_pquota_project_before.isoft == pquota_project_isoft + - test_pquota_project_before.ihard == pquota_project_ihard + - test_pquota_project_before.rtbsoft == pquota_project_rtbsoft|human_to_bytes + - test_pquota_project_before.rtbhard == pquota_project_rtbhard|human_to_bytes + + - name: 'Re-apply default project limits' + xfs_quota: + bsoft: '{{ pquota_default_bsoft }}' + bhard: '{{ pquota_default_bhard }}' + isoft: '{{ pquota_default_isoft }}' + ihard: '{{ pquota_default_ihard }}' + mountpoint: '{{ ansible_user_dir }}/ansible_testing/pquota' + rtbsoft: '{{ pquota_default_rtbsoft }}' + rtbhard: '{{ pquota_default_rtbhard }}' + type: project + become: True + register: test_pquota_default_after + + - name: Assert default project limits results after re-apply + assert: + that: + - not test_pquota_default_after.changed + + - name: 'Re-apply project limits' + xfs_quota: + bsoft: '{{ pquota_project_bsoft }}' + bhard: '{{ pquota_project_bhard }}' + isoft: '{{ pquota_project_isoft }}' + ihard: '{{ pquota_project_ihard }}' + mountpoint: '{{ ansible_user_dir }}/ansible_testing/pquota' + name: xft_quotaval + rtbsoft: '{{ pquota_project_rtbsoft }}' + rtbhard: '{{ pquota_project_rtbhard }}' + type: project + become: True + register: test_pquota_project_after + + - name: Assert project limits results for xft_quotaval after re-apply + assert: + that: + - not test_pquota_project_after.changed + + - name: 'Reset default project limits' + xfs_quota: + mountpoint: '{{ ansible_user_dir }}/ansible_testing/pquota' + state: absent + type: project + become: True + register: test_reset_pquota_default + + - name: Assert reset of default projecy limits results + assert: + that: + - test_reset_pquota_default.changed + - test_reset_pquota_default.bsoft == 0 + - test_reset_pquota_default.bhard == 0 + - test_reset_pquota_default.isoft == 0 + - test_reset_pquota_default.ihard == 0 + - test_reset_pquota_default.rtbsoft == 0 + - test_reset_pquota_default.rtbhard == 0 + + - name: 'Reset project limits for xft_quotaval' + xfs_quota: + mountpoint: '{{ ansible_user_dir }}/ansible_testing/pquota' + name: xft_quotaval + state: absent + type: project + become: True + register: test_reset_pquota_project + + - name: Assert reset of project limits results for xft_quotaval + assert: + that: + - test_reset_pquota_project.changed + - test_reset_pquota_project.bsoft == 0 + - test_reset_pquota_project.bhard == 0 + - test_reset_pquota_project.isoft == 0 + - test_reset_pquota_project.ihard == 0 + - test_reset_pquota_project.rtbsoft == 0 + - test_reset_pquota_project.rtbhard == 0 + + always: + - name: 'Unmount filesystem' + mount: + fstab: '{{ ansible_user_dir }}/ansible_testing/fstab' + path: '{{ ansible_user_dir }}/ansible_testing/pquota' + state: unmounted + become: True + + - name: Remove disk image + file: + path: '{{ ansible_user_dir }}/ansible_testing/img-pquota' + state: absent + + - name: Remove xfs quota project id + lineinfile: + path: /etc/projid + regexp: '^xft_quotaval:99999$' + state: absent + become: True + + - name: Remove xfs quota project path + lineinfile: + path: /etc/projects + regexp: '^99999:.*$' + state: absent + become: True diff --git a/test/integration/targets/xfs_quota/tasks/uquota.yml b/test/integration/targets/xfs_quota/tasks/uquota.yml new file mode 100644 index 00000000000..8f14464db43 --- /dev/null +++ b/test/integration/targets/xfs_quota/tasks/uquota.yml @@ -0,0 +1,158 @@ +--- +- name: 'Create disk image' + command: > + dd if=/dev/zero of={{ ansible_user_dir }}/ansible_testing/img-uquota bs=1M count=20 + +- name: 'Create XFS filesystem' + filesystem: + dev: '{{ ansible_user_dir }}/ansible_testing/img-uquota' + fstype: xfs + +- block: + - name: 'Mount filesystem' + mount: + fstab: '{{ ansible_user_dir }}/ansible_testing/fstab' + src: '{{ ansible_user_dir }}/ansible_testing/img-uquota' + path: '{{ ansible_user_dir }}/ansible_testing/uquota' + fstype: xfs + opts: uquota + state: mounted + become: True + + - name: 'Apply default user limits' + xfs_quota: + bsoft: '{{ uquota_default_bsoft }}' + bhard: '{{ uquota_default_bhard }}' + isoft: '{{ uquota_default_isoft }}' + ihard: '{{ uquota_default_ihard }}' + mountpoint: '{{ ansible_user_dir }}/ansible_testing/uquota' + rtbsoft: '{{ uquota_default_rtbsoft }}' + rtbhard: '{{ uquota_default_rtbhard }}' + type: user + become: True + register: test_uquota_default_before + + - name: Assert default user limits results + assert: + that: + - test_uquota_default_before.changed + - test_uquota_default_before.bsoft == uquota_default_bsoft|human_to_bytes + - test_uquota_default_before.bhard == uquota_default_bhard|human_to_bytes + - test_uquota_default_before.isoft == uquota_default_isoft + - test_uquota_default_before.ihard == uquota_default_ihard + - test_uquota_default_before.rtbsoft == uquota_default_rtbsoft|human_to_bytes + - test_uquota_default_before.rtbhard == uquota_default_rtbhard|human_to_bytes + + - name: 'Apply user limits' + xfs_quota: + bsoft: '{{ uquota_user_bsoft }}' + bhard: '{{ uquota_user_bhard }}' + isoft: '{{ uquota_user_isoft }}' + ihard: '{{ uquota_user_ihard }}' + mountpoint: '{{ ansible_user_dir }}/ansible_testing/uquota' + name: xfsquotauser + rtbsoft: '{{ uquota_user_rtbsoft }}' + rtbhard: '{{ uquota_user_rtbhard }}' + type: user + become: True + register: test_uquota_user_before + + - name: Assert user limits results + assert: + that: + - test_uquota_user_before.changed + - test_uquota_user_before.bsoft == uquota_user_bsoft|human_to_bytes + - test_uquota_user_before.bhard == uquota_user_bhard|human_to_bytes + - test_uquota_user_before.isoft == uquota_user_isoft + - test_uquota_user_before.ihard == uquota_user_ihard + - test_uquota_user_before.rtbsoft == uquota_user_rtbsoft|human_to_bytes + - test_uquota_user_before.rtbhard == uquota_user_rtbhard|human_to_bytes + + - name: 'Re-apply default user limits' + xfs_quota: + bsoft: '{{ uquota_default_bsoft }}' + bhard: '{{ uquota_default_bhard }}' + isoft: '{{ uquota_default_isoft }}' + ihard: '{{ uquota_default_ihard }}' + mountpoint: '{{ ansible_user_dir }}/ansible_testing/uquota' + rtbsoft: '{{ uquota_default_rtbsoft }}' + rtbhard: '{{ uquota_default_rtbhard }}' + type: user + become: True + register: test_uquota_default_after + + - name: Assert default user limits results after re-apply + assert: + that: + - not test_uquota_default_after.changed + + - name: 'Re-apply user limits' + xfs_quota: + bsoft: '{{ uquota_user_bsoft }}' + bhard: '{{ uquota_user_bhard }}' + isoft: '{{ uquota_user_isoft }}' + ihard: '{{ uquota_user_ihard }}' + mountpoint: '{{ ansible_user_dir }}/ansible_testing/uquota' + name: xfsquotauser + rtbsoft: '{{ uquota_user_rtbsoft }}' + rtbhard: '{{ uquota_user_rtbhard }}' + type: user + become: True + register: test_uquota_user_after + + - name: Assert user limits results for xfsquotauser after re-apply + assert: + that: + - not test_uquota_user_after.changed + + - name: 'Reset default user limits' + xfs_quota: + mountpoint: '{{ ansible_user_dir }}/ansible_testing/uquota' + state: absent + type: user + become: True + register: test_reset_uquota_default + + - name: Assert reset of default user limits results + assert: + that: + - test_reset_uquota_default.changed + - test_reset_uquota_default.bsoft == 0 + - test_reset_uquota_default.bhard == 0 + - test_reset_uquota_default.isoft == 0 + - test_reset_uquota_default.ihard == 0 + - test_reset_uquota_default.rtbsoft == 0 + - test_reset_uquota_default.rtbhard == 0 + + - name: 'Reset user limits for xfsquotauser' + xfs_quota: + mountpoint: '{{ ansible_user_dir }}/ansible_testing/uquota' + name: xfsquotauser + state: absent + type: user + become: True + register: test_reset_uquota_user + + - name: Assert reset of default user limits results + assert: + that: + - test_reset_uquota_user.changed + - test_reset_uquota_user.bsoft == 0 + - test_reset_uquota_user.bhard == 0 + - test_reset_uquota_user.isoft == 0 + - test_reset_uquota_user.ihard == 0 + - test_reset_uquota_user.rtbsoft == 0 + - test_reset_uquota_user.rtbhard == 0 + + always: + - name: 'Unmount filesystem' + mount: + fstab: '{{ ansible_user_dir }}/ansible_testing/fstab' + path: '{{ ansible_user_dir }}/ansible_testing/uquota' + state: unmounted + become: True + + - name: Remove disk image + file: + path: '{{ ansible_user_dir }}/ansible_testing/img-uquota' + state: absent