From e4ed8d3767feb9c7078be3e1c2a3a9f47a031b6f Mon Sep 17 00:00:00 2001 From: Hongchao Zhang Date: Fri, 21 Jan 2022 08:43:56 +0800 Subject: [PATCH] LU-15218 quota: delete unused quota ID Add lfs option '--delete' to delete unused quota ID. Lustre-change: https://review.whamcloud.com/45548 Lustre-commit: 78be823f33396819724330d7154f054c52e11944 Signed-off-by: Hongchao Zhang Change-Id: I0d8e6b61dc23c7b22b6054bcced087b8dc94a277 Reviewed-on: https://review.whamcloud.com/46610 Tested-by: jenkins Tested-by: Maloo Reviewed-by: Andreas Dilger --- lustre/doc/lfs-setquota.1 | 21 ++++++-- lustre/include/uapi/linux/lustre/lustre_user.h | 2 + lustre/llite/dir.c | 1 + lustre/mdt/mdt_handler.c | 2 + lustre/quota/lquota_disk.c | 30 +++++++++++ lustre/quota/lquota_internal.h | 17 +++++- lustre/quota/qmt_handler.c | 75 ++++++++++++++++++++++++++ lustre/quota/qmt_lock.c | 5 ++ lustre/quota/qsd_writeback.c | 36 +++++++++++++ lustre/tests/sanity-quota.sh | 62 +++++++++++++++++++++ lustre/utils/lfs.c | 39 +++++++++++--- 11 files changed, 278 insertions(+), 12 deletions(-) diff --git a/lustre/doc/lfs-setquota.1 b/lustre/doc/lfs-setquota.1 index 42542d2..4e09b5d 100644 --- a/lustre/doc/lfs-setquota.1 +++ b/lustre/doc/lfs-setquota.1 @@ -15,7 +15,7 @@ lfs setquota \- set quota limits or grace time for users, groups or projects. [\fB--inode-grace|-i\fR <\fIinode-grace\fR>] <\fIfilesystem\fR> .TP .B lfs setquota {\fB-u|--user|-g|--group|-p|--projid\fR} <\fIuname|uid|gname|gid|projid\fR> - [\fB--default|-d\fR] <\fIfilesystem\fR> + [\fB--default|-D\fR] <\fIfilesystem\fR> .TP .B lfs setquota {\fB-U|--default-usr|-G|--default-grp|-P|--default-prj\fR} [\fB--block-softlimit|-b\fR <\fIblock-softlimit\fR>[kMGTPE]] @@ -23,6 +23,9 @@ lfs setquota \- set quota limits or grace time for users, groups or projects. [\fB--inode-softlimit|-i\fR <\fIinode-softlimit\fR>[kMGTPE]] [\fB--inode-hardlimit|-I\fR <\fIinode-hardlimit\fR>[kMGTPE]] <\fIfilesystem\fR> .TP +.BR "lfs setquota " { -u | --user | -g | --group | -p | --projid "} " \fIUID\fR|\fIGID\fR|\fIPROJID\fR + [\fB--delete\fR] <\fIfilesystem\fR> +.TP .SH DESCRIPTION .PP .BR "lfs setquota " {\fB-u|-g|-p\fR} @@ -47,7 +50,10 @@ Set project quota for \fIprojid\fR. .B --pool <\fIpname\fR> Set quota per OST pool \fIpname\fR. .TP -.B -d|--default +.BR --delete +Delete the unused UID|GID|PROJID. +.TP +.B -D|--default Set user/group/project to use the default quota limits. .TP .B -b|--block-softlimit <\fIblock-softlimit\fR> @@ -122,7 +128,7 @@ Set default project quota limit. .B $ lfs setquota -u bob --block-softlimit 2G --block-hardlimit 1G /mnt/lustre Set quotas of user `bob': 1GB block hardlimit and 2 GB block softlimit .TP -.B $ lfs setquota -u bob -d /mnt/lustre +.B $ lfs setquota -u bob -D /mnt/lustre Set quotas of user `bob' to use default quota setting .TP .B $ lfs setquota -U --block-softlimit 1G --block-hardlimit 2G /mnt/lustre @@ -137,6 +143,15 @@ Set hard block limit 1G for user 'ivan' per pool 'flash_pool' .TP .B $ lfs setquota -t -u --block-grace 1000 --pool flash_pool /mnt/lustre Set grace time 1000 seconds for block quotas per pool 'flash_pool' +.TP +.BR "lfs setquota " { -u | -g | -p "} " \fIUID\fR|\fIGID\fR|\fIPROJID\fR " " [\fB--delete\fR] " " <\fIfilesystem\fR> +Command deletes the unused UID|GID|PROJID from Quota settings. +.TP +.PP +.SH EXAMPLES +.TP +.B $ lfs setquota -u bob --delete /mnt/lustre +Delete unused user 'bob'. .SH SEE ALSO .BR lfs (1), .BR lfs-quota(1) diff --git a/lustre/include/uapi/linux/lustre/lustre_user.h b/lustre/include/uapi/linux/lustre/lustre_user.h index c5ada6f..705ace8 100644 --- a/lustre/include/uapi/linux/lustre/lustre_user.h +++ b/lustre/include/uapi/linux/lustre/lustre_user.h @@ -1317,6 +1317,7 @@ static inline __u64 lustre_stoqb(__kernel_size_t space) #define LUSTRE_Q_SETINFOPOOL 0x800012 /* set pool quota info */ #define LUSTRE_Q_GETDEFAULT_POOL 0x800013 /* get default pool quota*/ #define LUSTRE_Q_SETDEFAULT_POOL 0x800014 /* set default pool quota */ +#define LUSTRE_Q_DELETEQID 0x800015 /* delete quota ID */ /* In the current Lustre implementation, the grace time is either the time * or the timestamp to be used after some quota ID exceeds the soft limt, * 48 bits should be enough, its high 16 bits can be used as quota flags. @@ -1341,6 +1342,7 @@ static inline __u64 lustre_stoqb(__kernel_size_t space) * and high 16 bits will contain this flag (see above comment). * */ #define LQUOTA_FLAG_DEFAULT 0x0001 +#define LQUOTA_FLAG_DELETED 0x0002 #define LUSTRE_Q_CMD_IS_POOL(cmd) \ (cmd == LUSTRE_Q_GETQUOTAPOOL || \ diff --git a/lustre/llite/dir.c b/lustre/llite/dir.c index 19fcb1e..616f0cd 100644 --- a/lustre/llite/dir.c +++ b/lustre/llite/dir.c @@ -1165,6 +1165,7 @@ int quotactl_ioctl(struct ll_sb_info *sbi, struct if_quotactl *qctl) case LUSTRE_Q_SETQUOTAPOOL: case LUSTRE_Q_SETINFOPOOL: case LUSTRE_Q_SETDEFAULT_POOL: + case LUSTRE_Q_DELETEQID: if (!cfs_capable(CFS_CAP_SYS_ADMIN)) RETURN(-EPERM); break; diff --git a/lustre/mdt/mdt_handler.c b/lustre/mdt/mdt_handler.c index 26610dc..035c6fb 100644 --- a/lustre/mdt/mdt_handler.c +++ b/lustre/mdt/mdt_handler.c @@ -3051,6 +3051,7 @@ static int mdt_quotactl(struct tgt_session_info *tsi) case LUSTRE_Q_SETQUOTAPOOL: case LUSTRE_Q_SETINFOPOOL: case LUSTRE_Q_SETDEFAULT_POOL: + case LUSTRE_Q_DELETEQID: if (!nodemap_can_setquota(nodemap, oqctl->qc_type, oqctl->qc_id)) GOTO(out_nodemap, rc = -EPERM); @@ -3121,6 +3122,7 @@ static int mdt_quotactl(struct tgt_session_info *tsi) case LUSTRE_Q_GETINFOPOOL: case LUSTRE_Q_SETDEFAULT_POOL: case LUSTRE_Q_GETDEFAULT_POOL: + case LUSTRE_Q_DELETEQID: /* forward quotactl request to QMT */ rc = qmt_hdls.qmth_quotactl(tsi->tsi_env, qmt, oqctl); break; diff --git a/lustre/quota/lquota_disk.c b/lustre/quota/lquota_disk.c index 4353f1a..bc71833 100644 --- a/lustre/quota/lquota_disk.c +++ b/lustre/quota/lquota_disk.c @@ -701,6 +701,36 @@ out: return rc; } +int lquota_disk_delete(const struct lu_env *env, struct thandle *th, + struct dt_object *obj, __u64 qid, __u64 *ver) +{ + struct lquota_thread_info *qti = lquota_info(env); + struct dt_key *key = (struct dt_key *)&qid; + int rc; + + ENTRY; + + LASSERT(dt_object_exists(obj)); + LASSERT(obj->do_index_ops != NULL); + + /* lock index */ + dt_write_lock(env, obj, 0); + + /* check whether there is already an existing record for this ID */ + rc = dt_lookup(env, obj, (struct dt_rec *)&qti->qti_rec, key); + if (rc == 0) { + rc = dt_delete(env, obj, key, th); + if (rc == 0 && ver != NULL) { + *ver = dt_version_get(env, obj); + (*ver)++; + dt_version_set(env, obj, *ver, th); + } + } + + dt_write_unlock(env, obj); + RETURN(rc); +} + /* * Update version of an index file * diff --git a/lustre/quota/lquota_internal.h b/lustre/quota/lquota_internal.h index 76df850..5aab7f7 100644 --- a/lustre/quota/lquota_internal.h +++ b/lustre/quota/lquota_internal.h @@ -186,7 +186,8 @@ struct lquota_entry { lqe_gl:1, /* glimpse is in progress */ lqe_nopreacq:1, /* pre-acquire disabled */ lqe_is_default:1, /* the default quota is used */ - lqe_is_global:1; /* lqe belongs to global pool "0x0"*/ + lqe_is_global:1, /* lqe belongs to global pool "0x0"*/ + lqe_is_deleted:1; /* lqe will be deleted soon */ /* the lock to protect lqe_glbl_data */ struct mutex lqe_glbl_data_lock; @@ -457,6 +458,18 @@ struct lquota_entry *lqe_locate_find(const struct lu_env *, struct lquota_site *, union lquota_id *, bool); +static inline void lqe_set_deleted(struct lquota_entry *lqe) +{ + lqe->lqe_enforced = 0; + lqe->lqe_edquot = 0; + lqe->lqe_is_default = 0; + lqe->lqe_hardlimit = 0; + lqe->lqe_softlimit = 0; + lqe->lqe_gracetime = 0; + + lqe->lqe_is_deleted = 1; +} + /* lquota_disk.c */ struct dt_object *lquota_disk_dir_find_create(const struct lu_env *, struct dt_device *, @@ -485,6 +498,8 @@ int lquota_disk_declare_write(const struct lu_env *, struct thandle *, int lquota_disk_write(const struct lu_env *, struct thandle *, struct dt_object *, union lquota_id *, struct dt_rec *, __u32, __u64 *); +int lquota_disk_delete(const struct lu_env *env, struct thandle *th, + struct dt_object *obj, __u64 qid, __u64 *ver); int lquota_disk_update_ver(const struct lu_env *, struct dt_device *, struct dt_object *, __u64); int lquota_disk_write_glb(const struct lu_env *, struct dt_object *, __u64, diff --git a/lustre/quota/qmt_handler.c b/lustre/quota/qmt_handler.c index d7583f5..f967dd4 100644 --- a/lustre/quota/qmt_handler.c +++ b/lustre/quota/qmt_handler.c @@ -328,9 +328,74 @@ static int qmt_set(const struct lu_env *env, struct qmt_device *qmt, if (IS_ERR(lqe)) RETURN(PTR_ERR(lqe)); + lqe->lqe_is_deleted = 0; rc = qmt_set_with_lqe(env, qmt, lqe, hard, soft, time, valid, is_default, is_updated); + if (rc == 0) + lqe->lqe_is_deleted = 0; + + lqe_putref(lqe); + RETURN(rc); +} + +/* + * Delete the quota setting of the specified quota ID + * + * \param env - is the environment passed by the caller + * \param qmt - is the quota master target + * \param restype - is the pool type, either block (i.e. LQUOTA_RES_DT) or + * inode (i.e. LQUOTA_RES_MD) + * \param qtype - is the quota type + * \param qid - is the quota indentifier for which we want to delete its + * quota settings. + */ +static int qmt_delete_qid(const struct lu_env *env, struct qmt_device *qmt, + __u8 restype, __u8 qtype, __u64 qid) +{ + struct qmt_thread_info *qti = qmt_info(env); + union lquota_id *quota_id = &qti->qti_id; + struct thandle *th = NULL; + struct qmt_pool_info *qpi = NULL; + struct lquota_entry *lqe = NULL; + __u64 ver = 0; + int rc; + + ENTRY; + + quota_id->qid_uid = qid; + lqe = qmt_pool_lqe_lookup(env, qmt, restype, qtype, quota_id, NULL); + if (IS_ERR(lqe)) + RETURN(PTR_ERR(lqe)); + + lqe_write_lock(lqe); + + qpi = qmt_pool_lookup_glb(env, qmt, restype); + if (IS_ERR(qpi)) + GOTO(out, rc = -ENOMEM); + + th = qmt_trans_start(env, lqe); + if (IS_ERR(th)) + GOTO(out, rc = PTR_ERR(th)); + + rc = lquota_disk_delete(env, th, + qpi->qpi_glb_obj[qtype], qid, &ver); + + dt_trans_stop(env, qmt->qmt_child, th); + + if (rc == 0) { + lqe_set_deleted(lqe); + qmt_glb_lock_notify(env, lqe, ver); + } else if (rc == -ENOENT) { + rc = 0; + } + +out: + if (!IS_ERR_OR_NULL(qpi)) + qpi_putref(env, qpi); + + lqe_write_unlock(lqe); lqe_putref(lqe); + RETURN(rc); } @@ -475,6 +540,16 @@ static int qmt_quotactl(const struct lu_env *env, struct lu_device *ld, false, poolname); break; + case LUSTRE_Q_DELETEQID: + rc = qmt_delete_qid(env, qmt, LQUOTA_RES_MD, oqctl->qc_type, + oqctl->qc_id); + if (rc) + break; + + rc = qmt_delete_qid(env, qmt, LQUOTA_RES_DT, oqctl->qc_type, + oqctl->qc_id); + break; + default: CERROR("%s: unsupported quotactl command: %d\n", qmt->qmt_svname, oqctl->qc_cmd); diff --git a/lustre/quota/qmt_lock.c b/lustre/quota/qmt_lock.c index 87d15a0..e5534693 100644 --- a/lustre/quota/qmt_lock.c +++ b/lustre/quota/qmt_lock.c @@ -791,6 +791,11 @@ void qmt_glb_lock_notify(const struct lu_env *env, struct lquota_entry *lqe, qti->qti_gl_desc.lquota_desc.gl_time = LQUOTA_GRACE_FLAG(0, LQUOTA_FLAG_DEFAULT); + } else if (lqe->lqe_is_deleted) { + qti->qti_gl_desc.lquota_desc.gl_hardlimit = 0; + qti->qti_gl_desc.lquota_desc.gl_softlimit = 0; + qti->qti_gl_desc.lquota_desc.gl_time = LQUOTA_GRACE_FLAG(0, + LQUOTA_FLAG_DELETED); } else { qti->qti_gl_desc.lquota_desc.gl_hardlimit = lqe->lqe_hardlimit; qti->qti_gl_desc.lquota_desc.gl_softlimit = lqe->lqe_softlimit; diff --git a/lustre/quota/qsd_writeback.c b/lustre/quota/qsd_writeback.c index 92cd568..9f36287 100644 --- a/lustre/quota/qsd_writeback.c +++ b/lustre/quota/qsd_writeback.c @@ -292,12 +292,48 @@ static int qsd_process_upd(const struct lu_env *env, struct qsd_upd_rec *upd) return 1; } + if (upd->qur_global && + (LQUOTA_FLAG(upd->qur_rec.lqr_glb_rec.qbr_time) & + LQUOTA_FLAG_DELETED)) { + struct thandle *th = NULL; + struct dt_object *obj; + + obj = qqi->qqi_glb_obj; + + th = dt_trans_create(env, qqi->qqi_qsd->qsd_dev); + if (IS_ERR(th)) + RETURN(PTR_ERR(th)); + + rc = lquota_disk_declare_write(env, th, obj, &upd->qur_qid); + if (rc) + GOTO(out_del, rc); + + rc = dt_trans_start_local(env, qqi->qqi_qsd->qsd_dev, th); + if (rc) + GOTO(out_del, rc); + + rc = lquota_disk_delete(env, th, obj, upd->qur_qid.qid_uid, + NULL); + if (rc == -ENOENT) + rc = 0; + +out_del: + dt_trans_stop(env, qqi->qqi_qsd->qsd_dev, th); + if (lqe != NULL) + lqe_set_deleted(lqe); + + qsd_bump_version(qqi, upd->qur_ver, true); + RETURN(rc); + } + if (lqe == NULL) { lqe = lqe_locate(env, qqi->qqi_site, &upd->qur_qid); if (IS_ERR(lqe)) GOTO(out, rc = PTR_ERR(lqe)); } + lqe->lqe_is_deleted = 0; + /* The in-memory lqe update for slave index copy isn't deferred, * we shouldn't touch it here. */ if (upd->qur_global) { diff --git a/lustre/tests/sanity-quota.sh b/lustre/tests/sanity-quota.sh index 463b4de..7d153c4 100755 --- a/lustre/tests/sanity-quota.sh +++ b/lustre/tests/sanity-quota.sh @@ -3614,6 +3614,68 @@ test_41() { } run_test 41 "df should return projid-specific values" +test_delete_qid() +{ + local qslv_file=$1 + local qtype_file=$2 + local qtype=$3 + local qid=$4 + local osd="osd-ldiskfs" + + [ "$ost1_FSTYPE" = zfs ] && osd="osd-zfs" + + rm -f $DIR/$tdir/$tfile + $LFS setstripe -i 0 -c 1 $DIR/$tdir/$tfile + chmod a+rw $DIR/$tdir/$tfile + + $LFS setquota $qtype $qid -B 300M $MOUNT + $RUNAS dd if=/dev/zero of=$DIR/$tdir/$tfile bs=1M count=1 || + error "failed to dd" + + do_facet $SINGLEMDS \ + "cat /proc/fs/lustre/qmt/$FSNAME-QMT0000/dt-0x0/$qtype_file | + grep -E 'id: *$qid'" || error "QMT: no qid $qid is found" + echo $osd + do_facet ost1 \ + "cat /proc/fs/lustre/$osd/$FSNAME-OST0000/$qslv_file | + grep -E 'id: *$qid'" || error "QSD: no qid $qid is found" + + $LFS setquota $qtype $qid --delete $MOUNT + do_facet $SINGLEMDS \ + "cat /proc/fs/lustre/qmt/$FSNAME-QMT0000/dt-0x0/$qtype_file | + grep -E 'id: *$qid'" && error "QMT: qid $qid is not deleted" + sleep 5 + do_facet ost1 \ + "cat /proc/fs/lustre/$osd/$FSNAME-OST0000/$qslv_file | + grep -E 'id: *$qid'" && error "QSD: qid $qid is not deleted" + + $LFS setquota $qtype $qid -B 500M $MOUNT + $RUNAS dd if=/dev/zero of=$DIR/$tdir/$tfile bs=1M count=1 || + error "failed to dd" + do_facet $SINGLEMDS \ + "cat /proc/fs/lustre/qmt/$FSNAME-QMT0000/dt-0x0/$qtype_file | + grep -E 'id: *$qid'" || error "QMT: qid $pid is not recreated" + cat /proc/fs/lustre/$osd/$FSNAME-OST0000/$qslv_file + do_facet ost1 \ + "cat /proc/fs/lustre/$osd/$FSNAME-OST0000/$qslv_file | + grep -E 'id: *$qid'" || error "QSD: qid $qid is not recreated" +} + +test_48() +{ + setup_quota_test || error "setup quota failed with $?" + set_ost_qtype $QTYPE || error "enable ost quota failed" + quota_init + + test_delete_qid "quota_slave/limit_user" "glb-usr" "-u" $TSTID + test_delete_qid "quota_slave/limit_group" "glb-grp" "-g" $TSTID + is_project_quota_supported && + test_delete_qid "quota_slave/limit_project" "glb-prj" "-p" "10000" + + cleanup_quota_test +} +run_test 48 "lfs quota --delete should delete quota project ID" + test_50() { ! is_project_quota_supported && skip "Project quota is not supported" diff --git a/lustre/utils/lfs.c b/lustre/utils/lfs.c index 6360ddd..f3a9b8a 100644 --- a/lustre/utils/lfs.c +++ b/lustre/utils/lfs.c @@ -558,12 +558,13 @@ command_t cmdlist[] = { " [--inode-softlimit ]\n" " [--inode-hardlimit ] \n" " setquota <-u|-g|-p> ||||\n" - " <-d|--default>\n" + " <-D|--default>\n" + " setquota {-u|-g|-p} --delete FILESYSTEM\n" " -b can be used instead of --block-softlimit/--block-grace\n" " -B can be used instead of --block-hardlimit\n" " -i can be used instead of --inode-softlimit/--inode-grace\n" " -I can be used instead of --inode-hardlimit\n" - " -d can be used instead of --default\n\n" + " -D can be used instead of --default\n\n" "Note: The total quota space will be split into many qunits and\n" " balanced over all server targets, the minimal qunit size is\n" " 1M bytes for block space and 1K inodes for inode space.\n\n" @@ -3367,6 +3368,7 @@ static void lfs_mirror_list_free(struct mirror_args *mirror_list) } enum { + LFS_SETQUOTA_DELETE = 1, LFS_POOL_OPT = 3, LFS_COMP_COUNT_OPT, LFS_COMP_START_OPT, @@ -7330,6 +7332,8 @@ int lfs_setquota(int argc, char **argv) { .val = 'B', .name = "block-hardlimit", .has_arg = required_argument }, { .val = 'd', .name = "default", .has_arg = no_argument }, + { .val = LFS_SETQUOTA_DELETE, + .name = "delete", .has_arg = no_argument }, { .val = 'g', .name = "group", .has_arg = required_argument }, { .val = 'G', .name = "default-grp", .has_arg = no_argument }, { .val = 'i', .name = "inode-softlimit", @@ -7366,8 +7370,7 @@ int lfs_setquota(int argc, char **argv) * so it can be used as a marker that qc_type * isn't reinitialized from command line */ - - while ((c = getopt_long(argc, argv, "b:B:dg:Gi:I:p:Pu:U", + while ((c = getopt_long(argc, argv, "b:B:dDg:Gi:I:p:Pu:U", long_opts, NULL)) != -1) { switch (c) { case 'U': @@ -7425,9 +7428,19 @@ quota_type_def: } qctl->qc_type = qtype; break; +#if LUSTRE_VERSION_CODE < OBD_OCD_VERSION(3, 0, 53, 0) case 'd': - qctl->qc_cmd = LUSTRE_Q_SETDEFAULT; +#if LUSTRE_VERSION_CODE > OBD_OCD_VERSION(2, 15, 53, 0) + fprintf(stderr, "'-d' deprecatd, use '-D' or '--default'\n"); +#endif + /* falltrrough */ +#endif + case 'D': use_default = true; + qctl->qc_cmd = LUSTRE_Q_SETDEFAULT; + break; + case LFS_SETQUOTA_DELETE: + qctl->qc_cmd = LUSTRE_Q_DELETEQID; break; case 'b': ARG2ULL(dqb->dqb_bsoftlimit, optarg, 1024); @@ -7503,7 +7516,8 @@ quota_type_def: goto out; } - if (!use_default && limit_mask == 0) { + if (!use_default && qctl->qc_cmd != LUSTRE_Q_DELETEQID && + limit_mask == 0) { fprintf(stderr, "%s setquota: at least one limit must be specified\n", progname); @@ -7511,9 +7525,10 @@ quota_type_def: goto out; } - if (use_default && limit_mask != 0) { + if ((use_default || qctl->qc_cmd == LUSTRE_Q_DELETEQID) && + limit_mask != 0) { fprintf(stderr, - "%s setquota: limits should not be specified when using default quota\n", + "%s setquota: limits should not be specified when using default quota or deleting quota ID\n", progname); rc = CMD_HELP; goto out; @@ -7527,6 +7542,14 @@ quota_type_def: goto out; } + if (qctl->qc_cmd == LUSTRE_Q_DELETEQID && qctl->qc_id == 0) { + fprintf(stderr, + "%s setquota: can not delete root user/group/project\n", + progname); + rc = CMD_HELP; + goto out; + } + if (optind != argc - 1) { fprintf(stderr, "%s setquota: filesystem not specified or unexpected argument '%s'\n", -- 1.8.3.1