Add lfs option '--delete' to delete unused quota ID.
Signed-off-by: Hongchao Zhang <hongchao@whamcloud.com>
Change-Id: I0d8e6b61dc23c7b22b6054bcced087b8dc94a277
Reviewed-on: https://review.whamcloud.com/45548
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
Reviewed-by: Li Dongyang <dongyangli@ddn.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
[\fB--inode-grace\fR|\fB-i\fR \fIINODE_GRACE_TIME\fR] <\fIfilesystem\fR>
.TP
.BR "lfs setquota " { -u | --user | -g | --group | -p | --projid "} " \fIUID\fR|\fIGID\fR|\fIPROJID\fR
- [\fB--default|-d\fR] <\fIfilesystem\fR>
+ [\fB--default|-D\fR] <\fIfilesystem\fR>
.TP
.BR "lfs setquota " { -U | --default-usr | -G | --default-grp | -P | --default-prj }
[\fB--block-softlimit\fR|\fB-b\fR \fIBLOCK_SOFTLIMIT\fR[\fBkMGTPE\fR]]
[\fB--inode-softlimit\fR|\fB-i\fR \fIINODE_SOFTLIMIT\fR[\fBkMGTPE\fR]]
[\fB--inode-hardlimit\fR|\fB-I\fR \fIINODE_HARDLIMIT\fR[\fBkMGTPE\fR]] <\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
.TP
.BR "lfs setquota " {\fB-u|-g|-p\fR}
Specify block hardlimit, zero means unlimited. The block hardlimit should be
greater than block softlimit when it's being specified.
.TP
-.BR -d|--default
+.BR --delete
+Delete the unused UID|GID|PROJID.
+.TP
+.BR -D|--default
Set user/group/project to use the default quota limits.
.TP
.BR -g | --group \fIGROUPNAME\fR|\fIGID
.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
.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)
#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.
* 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 || \
case LUSTRE_Q_SETQUOTAPOOL:
case LUSTRE_Q_SETINFOPOOL:
case LUSTRE_Q_SETDEFAULT_POOL:
+ case LUSTRE_Q_DELETEQID:
if (!capable(CAP_SYS_ADMIN))
RETURN(-EPERM);
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);
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;
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
*
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 */
struct lqe_glbl_data *lqe_glbl_data;
};
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 *,
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,
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);
}
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);
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;
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) {
}
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"
"Usage: getname [--help|-h] [--instance|-i] [--fsname|-n] [path ...]"},
#ifdef HAVE_SYS_QUOTA_H
{"setquota", lfs_setquota, 0, "Set filesystem quotas.\n"
- "usage: setquota [-t][-d] {-u|-U|-g|-G|-p|-P} {-b|-B|-i|-I LIMIT} [--pool POOL] FILESYSTEM"},
+ "usage: setquota [-t][-D] {-u|-U|-g|-G|-p|-P} {-b|-B|-i|-I LIMIT} [--pool POOL] FILESYSTEM\n"
+ " setquota {-u|-g|-p} --delete FILESYSTEM\n"},
{"quota", lfs_quota, 0, "Display disk usage and limits.\n"
"usage: quota [-q] [-v] [-h] [-o OBD_UUID|-i MDT_IDX|-I OST_IDX]\n"
" [{-u|-g|-p} UNAME|UID|GNAME|GID|PROJID]\n"
}
enum {
+ LFS_SETQUOTA_DELETE = 1,
LFS_POOL_OPT = 3,
LFS_COMP_COUNT_OPT,
LFS_COMP_START_OPT,
{ .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 = 'h', .name = "help", .has_arg = no_argument },
* 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:Ghi:I:p:Pu:U",
+ while ((c = getopt_long(argc, argv, "b:B:dDg:Ghi:I:p:Pu:U",
long_opts, NULL)) != -1) {
switch (c) {
case 'U':
}
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);
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);
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;
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",