1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

bugfix: xfs_quota feedback on projects not initialized has changed (#1596) (#1922)

* bugfix: xfs_quota feedback on projects not initialized has changed

* changelog fragment

* Update changelogs/fragments/1596-xfs_quota-feedback_on_projects_not_initialized_has_changed.yml

Thanks for this, felixfontein

Co-authored-by: Felix Fontein <felix@fontein.de>

* xfs_quota is not necessarily in PATH

* pep8 and formatting

* Test was wrong. It needs to be changed

* formatting

* pep8 and formatting

* xfs_quota is not necessarily in PATH

* pep8 and formatting

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit ea65ce8e0d)

Co-authored-by: William Leemans <bushvin@users.noreply.github.com>
This commit is contained in:
patchback[bot] 2021-02-27 09:57:55 +01:00 committed by GitHub
parent 53cf06bf46
commit e68c68d417
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 178 additions and 124 deletions

View file

@ -0,0 +1,3 @@
---
bugfixes:
- xfs_quota - the feedback for initializing project quota using xfs_quota binary from ``xfsprogs`` has changed since the version it was written for (https://github.com/ansible-collections/community.general/pull/1596).

View file

@ -7,9 +7,10 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
DOCUMENTATION = r''' DOCUMENTATION = r"""
--- ---
module: xfs_quota module: xfs_quota
short_description: Manage quotas on XFS filesystems short_description: Manage quotas on XFS filesystems
@ -77,9 +78,9 @@ options:
requirements: requirements:
- xfsprogs - xfsprogs
''' """
EXAMPLES = r''' EXAMPLES = r"""
- name: Set default project soft and hard limit on /opt of 1g - name: Set default project soft and hard limit on /opt of 1g
community.general.xfs_quota: community.general.xfs_quota:
type: project type: project
@ -101,9 +102,9 @@ EXAMPLES = r'''
isoft: 1024 isoft: 1024
ihard: 2048 ihard: 2048
''' """
RETURN = r''' RETURN = r"""
bhard: bhard:
description: the current bhard setting in bytes description: the current bhard setting in bytes
returned: always returned: always
@ -134,7 +135,7 @@ rtbsoft:
returned: always returned: always
type: int type: int
sample: 1024 sample: 1024
''' """
import grp import grp
import os import os
@ -146,30 +147,32 @@ from ansible.module_utils.basic import AnsibleModule, human_to_bytes
def main(): def main():
module = AnsibleModule( module = AnsibleModule(
argument_spec=dict( argument_spec=dict(
bhard=dict(type='str'), bhard=dict(type="str"),
bsoft=dict(type='str'), bsoft=dict(type="str"),
ihard=dict(type='int'), ihard=dict(type="int"),
isoft=dict(type='int'), isoft=dict(type="int"),
mountpoint=dict(type='str', required=True), mountpoint=dict(type="str", required=True),
name=dict(type='str'), name=dict(type="str"),
rtbhard=dict(type='str'), rtbhard=dict(type="str"),
rtbsoft=dict(type='str'), rtbsoft=dict(type="str"),
state=dict(type='str', default='present', choices=['absent', 'present']), state=dict(type="str", default="present", choices=["absent", "present"]),
type=dict(type='str', required=True, choices=['group', 'project', 'user']) type=dict(type="str", required=True, choices=["group", "project", "user"]),
), ),
supports_check_mode=True, supports_check_mode=True,
) )
quota_type = module.params['type'] quota_type = module.params["type"]
name = module.params['name'] name = module.params["name"]
mountpoint = module.params['mountpoint'] mountpoint = module.params["mountpoint"]
bhard = module.params['bhard'] bhard = module.params["bhard"]
bsoft = module.params['bsoft'] bsoft = module.params["bsoft"]
ihard = module.params['ihard'] ihard = module.params["ihard"]
isoft = module.params['isoft'] isoft = module.params["isoft"]
rtbhard = module.params['rtbhard'] rtbhard = module.params["rtbhard"]
rtbsoft = module.params['rtbsoft'] rtbsoft = module.params["rtbsoft"]
state = module.params['state'] state = module.params["state"]
xfs_quota_bin = module.get_bin_path("xfs_quota", True)
if bhard is not None: if bhard is not None:
bhard = human_to_bytes(bhard) bhard = human_to_bytes(bhard)
@ -192,90 +195,122 @@ def main():
mp = get_fs_by_mountpoint(mountpoint) mp = get_fs_by_mountpoint(mountpoint)
if mp is None: 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) 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': if quota_type == "user":
type_arg = '-u' type_arg = "-u"
quota_default = 'root' quota_default = "root"
if name is None: if name is None:
name = quota_default 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 \ if (
'qnoenforce' not in mp['mntopts']: "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( module.fail_json(
msg="Path '%s' is not mounted with the uquota/usrquota/quota/uqnoenforce/qnoenforce option." % mountpoint, **result msg="Path '%s' is not mounted with the uquota/usrquota/quota/uqnoenforce/qnoenforce option."
% mountpoint,
**result
) )
try: try:
pwd.getpwnam(name) pwd.getpwnam(name)
except KeyError as e: except KeyError as e:
module.fail_json(msg="User '%s' does not exist." % name, **result) module.fail_json(msg="User '%s' does not exist." % name, **result)
elif quota_type == 'group': elif quota_type == "group":
type_arg = '-g' type_arg = "-g"
quota_default = 'root' quota_default = "root"
if name is None: if name is None:
name = quota_default name = quota_default
if 'gquota' not in mp['mntopts'] and 'grpquota' not in mp['mntopts'] and 'gqnoenforce' not in mp['mntopts']: if (
"gquota" not in mp["mntopts"]
and "grpquota" not in mp["mntopts"]
and "gqnoenforce" not in mp["mntopts"]
):
module.fail_json( module.fail_json(
msg="Path '%s' is not mounted with the gquota/grpquota/gqnoenforce option. (current options: %s)" % (mountpoint, mp['mntopts']), **result msg="Path '%s' is not mounted with the gquota/grpquota/gqnoenforce option. (current options: %s)"
% (mountpoint, mp["mntopts"]),
**result
) )
try: try:
grp.getgrnam(name) grp.getgrnam(name)
except KeyError as e: except KeyError as e:
module.fail_json(msg="User '%s' does not exist." % name, **result) module.fail_json(msg="User '%s' does not exist." % name, **result)
elif quota_type == 'project': elif quota_type == "project":
type_arg = '-p' type_arg = "-p"
quota_default = '#0' quota_default = "#0"
if name is None: if name is None:
name = quota_default name = quota_default
if 'pquota' not in mp['mntopts'] and 'prjquota' not in mp['mntopts'] and 'pqnoenforce' not in mp['mntopts']: if (
module.fail_json(msg="Path '%s' is not mounted with the pquota/prjquota/pqnoenforce option." % mountpoint, **result) "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'): if name != quota_default and not os.path.isfile("/etc/projects"):
module.fail_json(msg="Path '/etc/projects' does not exist.", **result) module.fail_json(msg="Path '/etc/projects' does not exist.", **result)
if name != quota_default and not os.path.isfile('/etc/projid'): if name != quota_default and not os.path.isfile("/etc/projid"):
module.fail_json(msg="Path '/etc/projid' does not exist.", **result) 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: 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) module.fail_json(
msg="Entry '%s' has not been defined in /etc/projid." % name, **result
)
prj_set = True prj_set = True
if name != quota_default: if name != quota_default:
cmd = 'project %s' % name cmd = "project %s" % name
rc, stdout, stderr = exec_quota(module, cmd, mountpoint) rc, stdout, stderr = exec_quota(module, xfs_quota_bin, cmd, mountpoint)
if rc != 0: if rc != 0:
result['cmd'] = cmd result["cmd"] = cmd
result['rc'] = rc result["rc"] = rc
result['stdout'] = stdout result["stdout"] = stdout
result['stderr'] = stderr result["stderr"] = stderr
module.fail_json(msg='Could not get project state.', **result) module.fail_json(msg="Could not get project state.", **result)
else: else:
for line in stdout.split('\n'): for line in stdout.split("\n"):
if "Project Id '%s' - is not set." in line: if (
"Project Id '%s' - is not set." in line
or "project identifier is not set" in line
):
prj_set = False prj_set = False
break break
if not prj_set and not module.check_mode: if not prj_set and not module.check_mode:
cmd = 'project -s' cmd = "project -s"
rc, stdout, stderr = exec_quota(module, cmd, mountpoint) rc, stdout, stderr = exec_quota(module, xfs_quota_bin, cmd, mountpoint)
if rc != 0: if rc != 0:
result['cmd'] = cmd result["cmd"] = cmd
result['rc'] = rc result["rc"] = rc
result['stdout'] = stdout result["stdout"] = stdout
result['stderr'] = stderr result["stderr"] = stderr
module.fail_json(msg='Could not get quota realtime block report.', **result) module.fail_json(
msg="Could not get quota realtime block report.", **result
)
result['changed'] = True result["changed"] = True
elif not prj_set and module.check_mode: elif not prj_set and module.check_mode:
result['changed'] = True result["changed"] = True
# Set limits # Set limits
if state == 'absent': if state == "absent":
bhard = 0 bhard = 0
bsoft = 0 bsoft = 0
ihard = 0 ihard = 0
@ -283,91 +318,99 @@ def main():
rtbhard = 0 rtbhard = 0
rtbsoft = 0 rtbsoft = 0
current_bsoft, current_bhard = quota_report(module, mountpoint, name, quota_type, 'b') current_bsoft, current_bhard = quota_report(
current_isoft, current_ihard = quota_report(module, mountpoint, name, quota_type, 'i') module, xfs_quota_bin, mountpoint, name, quota_type, "b"
current_rtbsoft, current_rtbhard = quota_report(module, mountpoint, name, quota_type, 'rtb') )
current_isoft, current_ihard = quota_report(
module, xfs_quota_bin, mountpoint, name, quota_type, "i"
)
current_rtbsoft, current_rtbhard = quota_report(
module, xfs_quota_bin, mountpoint, name, quota_type, "rtb"
)
result['xfs_quota'] = dict( result["xfs_quota"] = dict(
bsoft=current_bsoft, bsoft=current_bsoft,
bhard=current_bhard, bhard=current_bhard,
isoft=current_isoft, isoft=current_isoft,
ihard=current_ihard, ihard=current_ihard,
rtbsoft=current_rtbsoft, rtbsoft=current_rtbsoft,
rtbhard=current_rtbhard rtbhard=current_rtbhard,
) )
limit = [] limit = []
if bsoft is not None and int(bsoft) != current_bsoft: if bsoft is not None and int(bsoft) != current_bsoft:
limit.append('bsoft=%s' % bsoft) limit.append("bsoft=%s" % bsoft)
result['bsoft'] = int(bsoft) result["bsoft"] = int(bsoft)
if bhard is not None and int(bhard) != current_bhard: if bhard is not None and int(bhard) != current_bhard:
limit.append('bhard=%s' % bhard) limit.append("bhard=%s" % bhard)
result['bhard'] = int(bhard) result["bhard"] = int(bhard)
if isoft is not None and isoft != current_isoft: if isoft is not None and isoft != current_isoft:
limit.append('isoft=%s' % isoft) limit.append("isoft=%s" % isoft)
result['isoft'] = isoft result["isoft"] = isoft
if ihard is not None and ihard != current_ihard: if ihard is not None and ihard != current_ihard:
limit.append('ihard=%s' % ihard) limit.append("ihard=%s" % ihard)
result['ihard'] = ihard result["ihard"] = ihard
if rtbsoft is not None and int(rtbsoft) != current_rtbsoft: if rtbsoft is not None and int(rtbsoft) != current_rtbsoft:
limit.append('rtbsoft=%s' % rtbsoft) limit.append("rtbsoft=%s" % rtbsoft)
result['rtbsoft'] = int(rtbsoft) result["rtbsoft"] = int(rtbsoft)
if rtbhard is not None and int(rtbhard) != current_rtbhard: if rtbhard is not None and int(rtbhard) != current_rtbhard:
limit.append('rtbhard=%s' % rtbhard) limit.append("rtbhard=%s" % rtbhard)
result['rtbhard'] = int(rtbhard) result["rtbhard"] = int(rtbhard)
if len(limit) > 0 and not module.check_mode: if len(limit) > 0 and not module.check_mode:
if name == quota_default: if name == quota_default:
cmd = 'limit %s -d %s' % (type_arg, ' '.join(limit)) cmd = "limit %s -d %s" % (type_arg, " ".join(limit))
else: else:
cmd = 'limit %s %s %s' % (type_arg, ' '.join(limit), name) cmd = "limit %s %s %s" % (type_arg, " ".join(limit), name)
rc, stdout, stderr = exec_quota(module, cmd, mountpoint) rc, stdout, stderr = exec_quota(module, xfs_quota_bin, cmd, mountpoint)
if rc != 0: if rc != 0:
result['cmd'] = cmd result["cmd"] = cmd
result['rc'] = rc result["rc"] = rc
result['stdout'] = stdout result["stdout"] = stdout
result['stderr'] = stderr result["stderr"] = stderr
module.fail_json(msg='Could not set limits.', **result) module.fail_json(msg="Could not set limits.", **result)
result['changed'] = True result["changed"] = True
elif len(limit) > 0 and module.check_mode: elif len(limit) > 0 and module.check_mode:
result['changed'] = True result["changed"] = True
module.exit_json(**result) module.exit_json(**result)
def quota_report(module, mountpoint, name, quota_type, used_type): def quota_report(module, xfs_quota_bin, mountpoint, name, quota_type, used_type):
soft = None soft = None
hard = None hard = None
if quota_type == 'project': if quota_type == "project":
type_arg = '-p' type_arg = "-p"
elif quota_type == 'user': elif quota_type == "user":
type_arg = '-u' type_arg = "-u"
elif quota_type == 'group': elif quota_type == "group":
type_arg = '-g' type_arg = "-g"
if used_type == 'b': if used_type == "b":
used_arg = '-b' used_arg = "-b"
used_name = 'blocks' used_name = "blocks"
factor = 1024 factor = 1024
elif used_type == 'i': elif used_type == "i":
used_arg = '-i' used_arg = "-i"
used_name = 'inodes' used_name = "inodes"
factor = 1 factor = 1
elif used_type == 'rtb': elif used_type == "rtb":
used_arg = '-r' used_arg = "-r"
used_name = 'realtime blocks' used_name = "realtime blocks"
factor = 1024 factor = 1024
rc, stdout, stderr = exec_quota(module, 'report %s %s' % (type_arg, used_arg), mountpoint) rc, stdout, stderr = exec_quota(
module, xfs_quota_bin, "report %s %s" % (type_arg, used_arg), mountpoint
)
if rc != 0: if rc != 0:
result = dict( result = dict(
@ -376,9 +419,9 @@ def quota_report(module, mountpoint, name, quota_type, used_type):
stdout=stdout, stdout=stdout,
stderr=stderr, stderr=stderr,
) )
module.fail_json(msg='Could not get quota report for %s.' % used_name, **result) module.fail_json(msg="Could not get quota report for %s." % used_name, **result)
for line in stdout.split('\n'): for line in stdout.split("\n"):
line = line.strip().split() line = line.strip().split()
if len(line) > 3 and line[0] == name: if len(line) > 3 and line[0] == name:
soft = int(line[2]) * factor soft = int(line[2]) * factor
@ -388,33 +431,41 @@ def quota_report(module, mountpoint, name, quota_type, used_type):
return soft, hard return soft, hard
def exec_quota(module, cmd, mountpoint): def exec_quota(module, xfs_quota_bin, cmd, mountpoint):
cmd = ['xfs_quota', '-x', '-c'] + [cmd, mountpoint] cmd = [xfs_quota_bin, "-x", "-c"] + [cmd, mountpoint]
(rc, stdout, stderr) = module.run_command(cmd, use_unsafe_shell=True) (rc, stdout, stderr) = module.run_command(cmd, use_unsafe_shell=True)
if "XFS_GETQUOTA: Operation not permitted" in stderr.split('\n') or \ if (
rc == 1 and 'xfs_quota: cannot set limits: Operation not permitted' in stderr.split('\n'): "XFS_GETQUOTA: 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') 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 return rc, stdout, stderr
def get_fs_by_mountpoint(mountpoint): def get_fs_by_mountpoint(mountpoint):
mpr = None mpr = None
with open('/proc/mounts', 'r') as s: with open("/proc/mounts", "r") as s:
for line in s.readlines(): for line in s.readlines():
mp = line.strip().split() mp = line.strip().split()
if len(mp) == 6 and mp[1] == mountpoint and mp[2] == 'xfs': if len(mp) == 6 and mp[1] == mountpoint and mp[2] == "xfs":
mpr = dict(zip(['spec', 'file', 'vfstype', 'mntopts', 'freq', 'passno'], mp)) mpr = dict(
mpr['mntopts'] = mpr['mntopts'].split(',') zip(["spec", "file", "vfstype", "mntopts", "freq", "passno"], mp)
)
mpr["mntopts"] = mpr["mntopts"].split(",")
break break
return mpr return mpr
def get_project_id(name): def get_project_id(name):
prjid = None prjid = None
with open('/etc/projid', 'r') as s: with open("/etc/projid", "r") as s:
for line in s.readlines(): for line in s.readlines():
line = line.strip().partition(':') line = line.strip().partition(":")
if line[0] == name: if line[0] == name:
prjid = line[2] prjid = line[2]
break break
@ -422,5 +473,5 @@ def get_project_id(name):
return prjid return prjid
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View file

@ -118,7 +118,7 @@
- name: Assert project limits results for xft_quotaval after re-apply - name: Assert project limits results for xft_quotaval after re-apply
assert: assert:
that: that:
- not test_pquota_project_after.changed - test_pquota_project_after.changed
- name: Reset default project limits - name: Reset default project limits
xfs_quota: xfs_quota:
mountpoint: '{{ remote_tmp_dir }}/pquota' mountpoint: '{{ remote_tmp_dir }}/pquota'