]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/commitdiff
ceph: quota: add initial infrastructure to support cephfs quotas
authorLuis Henriques <lhenriques@suse.com>
Fri, 5 Jan 2018 10:47:18 +0000 (10:47 +0000)
committerKleber Sacilotto de Souza <kleber.souza@canonical.com>
Tue, 2 Jul 2019 16:30:46 +0000 (18:30 +0200)
BugLink: https://bugs.launchpad.net/bugs/1834235
This patch adds the infrastructure required to support cephfs quotas as it
is currently implemented in the ceph fuse client.  Cephfs quotas can be
set on any directory, and can restrict the number of bytes or the number
of files stored beneath that point in the directory hierarchy.

Quotas are set using the extended attributes 'ceph.quota.max_files' and
'ceph.quota.max_bytes', and can be removed by setting these attributes to
'0'.

Link: http://tracker.ceph.com/issues/22372
Signed-off-by: Luis Henriques <lhenriques@suse.com>
Reviewed-by: "Yan, Zheng" <zyan@redhat.com>
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
(cherry picked from commit fb18a57568c2b84cd611e242c0f6fa97b45e4907)
Signed-off-by: Connor Kuehl <connor.kuehl@canonical.com>
Acked-by: Kleber Sacilotto de Souza <kleber.souza@canonical.com>
Acked-by: Stefan Bader <stefan.bader@canonical.com>
Signed-off-by: Kleber Sacilotto de Souza <kleber.souza@canonical.com>
Documentation/filesystems/ceph.txt
fs/ceph/Makefile
fs/ceph/inode.c
fs/ceph/mds_client.c
fs/ceph/mds_client.h
fs/ceph/quota.c [new file with mode: 0644]
fs/ceph/super.h
fs/ceph/xattr.c
include/linux/ceph/ceph_features.h
include/linux/ceph/ceph_fs.h
net/ceph/ceph_common.c

index 0b302a11718a43fd7ed44725390a5a4ceacb2229..094772481263852df643546bc91bf9ca633eb968 100644 (file)
@@ -62,6 +62,18 @@ subdirectories, and a summation of all nested file sizes.  This makes
 the identification of large disk space consumers relatively quick, as
 no 'du' or similar recursive scan of the file system is required.
 
+Finally, Ceph also allows quotas to be set on any directory in the system.
+The quota can restrict the number of bytes or the number of files stored
+beneath that point in the directory hierarchy.  Quotas can be set using
+extended attributes 'ceph.quota.max_files' and 'ceph.quota.max_bytes', eg:
+
+ setfattr -n ceph.quota.max_bytes -v 100000000 /some/dir
+ getfattr -n ceph.quota.max_bytes /some/dir
+
+A limitation of the current quotas implementation is that it relies on the
+cooperation of the client mounting the file system to stop writers when a
+limit is reached.  A modified or adversarial client cannot be prevented
+from writing as much data as it needs.
 
 Mount Syntax
 ============
index 174f5709e5086257bdfd623de7fbb07011c30634..a699e320393f2afba3785d2469da36b2c32350fd 100644 (file)
@@ -6,7 +6,7 @@
 obj-$(CONFIG_CEPH_FS) += ceph.o
 
 ceph-y := super.o inode.o dir.o file.o locks.o addr.o ioctl.o \
-       export.o caps.o snap.o xattr.o \
+       export.o caps.o snap.o xattr.o quota.o \
        mds_client.o mdsmap.o strings.o ceph_frag.o \
        debugfs.o
 
index 63fcb9496160f634030e772d1ce685c3463535b6..ae6723e80c7158c79f89f8aa43de239e2f596c36 100644 (file)
@@ -439,6 +439,9 @@ struct inode *ceph_alloc_inode(struct super_block *sb)
        atomic64_set(&ci->i_complete_seq[1], 0);
        ci->i_symlink = NULL;
 
+       ci->i_max_bytes = 0;
+       ci->i_max_files = 0;
+
        memset(&ci->i_dir_layout, 0, sizeof(ci->i_dir_layout));
        RCU_INIT_POINTER(ci->i_layout.pool_ns, NULL);
 
@@ -787,6 +790,9 @@ static int fill_inode(struct inode *inode, struct page *locked_page,
        inode->i_rdev = le32_to_cpu(info->rdev);
        inode->i_blkbits = fls(le32_to_cpu(info->layout.fl_stripe_unit)) - 1;
 
+       ci->i_max_bytes = iinfo->max_bytes;
+       ci->i_max_files = iinfo->max_files;
+
        if ((new_version || (new_issued & CEPH_CAP_AUTH_SHARED)) &&
            (issued & CEPH_CAP_AUTH_EXCL) == 0) {
                inode->i_mode = le32_to_cpu(info->mode);
index 6deaf018d16829c5561c04c04bd48c24698fe0b4..c28e8b3f68a001098e4d8f8530930144167b03d0 100644 (file)
@@ -101,6 +101,26 @@ static int parse_reply_info_in(void **p, void *end,
        } else
                info->inline_version = CEPH_INLINE_NONE;
 
+       if (features & CEPH_FEATURE_MDS_QUOTA) {
+               u8 struct_v, struct_compat;
+               u32 struct_len;
+
+               /*
+                * both struct_v and struct_compat are expected to be >= 1
+                */
+               ceph_decode_8_safe(p, end, struct_v, bad);
+               ceph_decode_8_safe(p, end, struct_compat, bad);
+               if (!struct_v || !struct_compat)
+                       goto bad;
+               ceph_decode_32_safe(p, end, struct_len, bad);
+               ceph_decode_need(p, end, struct_len, bad);
+               ceph_decode_64_safe(p, end, info->max_bytes, bad);
+               ceph_decode_64_safe(p, end, info->max_files, bad);
+       } else {
+               info->max_bytes = 0;
+               info->max_files = 0;
+       }
+
        info->pool_ns_len = 0;
        info->pool_ns_data = NULL;
        if (features & CEPH_FEATURE_FS_FILE_LAYOUT_V2) {
@@ -4107,6 +4127,9 @@ static void dispatch(struct ceph_connection *con, struct ceph_msg *msg)
        case CEPH_MSG_CLIENT_LEASE:
                handle_lease(mdsc, s, msg);
                break;
+       case CEPH_MSG_CLIENT_QUOTA:
+               ceph_handle_quota(mdsc, s, msg);
+               break;
 
        default:
                pr_err("received unknown message type %d %s\n", type,
index e741a1ddd59cf5a28688fb07ae940340f715a3cb..7cfb18c061bae24acffe6938116009a90432a88a 100644 (file)
@@ -49,6 +49,8 @@ struct ceph_mds_reply_info_in {
        char *inline_data;
        u32 pool_ns_len;
        char *pool_ns_data;
+       u64 max_bytes;
+       u64 max_files;
 };
 
 struct ceph_mds_reply_dir_entry {
diff --git a/fs/ceph/quota.c b/fs/ceph/quota.c
new file mode 100644 (file)
index 0000000..1b69d83
--- /dev/null
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * quota.c - CephFS quota
+ *
+ * Copyright (C) 2017-2018 SUSE
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "super.h"
+#include "mds_client.h"
+
+void ceph_handle_quota(struct ceph_mds_client *mdsc,
+                      struct ceph_mds_session *session,
+                      struct ceph_msg *msg)
+{
+       struct super_block *sb = mdsc->fsc->sb;
+       struct ceph_mds_quota *h = msg->front.iov_base;
+       struct ceph_vino vino;
+       struct inode *inode;
+       struct ceph_inode_info *ci;
+
+       if (msg->front.iov_len != sizeof(*h)) {
+               pr_err("%s corrupt message mds%d len %d\n", __func__,
+                      session->s_mds, (int)msg->front.iov_len);
+               ceph_msg_dump(msg);
+               return;
+       }
+
+       /* increment msg sequence number */
+       mutex_lock(&session->s_mutex);
+       session->s_seq++;
+       mutex_unlock(&session->s_mutex);
+
+       /* lookup inode */
+       vino.ino = le64_to_cpu(h->ino);
+       vino.snap = CEPH_NOSNAP;
+       inode = ceph_find_inode(sb, vino);
+       if (!inode) {
+               pr_warn("Failed to find inode %llu\n", vino.ino);
+               return;
+       }
+       ci = ceph_inode(inode);
+
+       spin_lock(&ci->i_ceph_lock);
+       ci->i_rbytes = le64_to_cpu(h->rbytes);
+       ci->i_rfiles = le64_to_cpu(h->rfiles);
+       ci->i_rsubdirs = le64_to_cpu(h->rsubdirs);
+       ci->i_max_bytes = le64_to_cpu(h->max_bytes);
+       ci->i_max_files = le64_to_cpu(h->max_files);
+       spin_unlock(&ci->i_ceph_lock);
+
+       iput(inode);
+}
index 8360dea34de34a36f00f17d91b95d76f3a6cfbe3..366fe03ccdb5b0d1a88e6cf9ad6f7b9f1fde8b4b 100644 (file)
@@ -310,6 +310,9 @@ struct ceph_inode_info {
        u64 i_rbytes, i_rfiles, i_rsubdirs;
        u64 i_files, i_subdirs;
 
+       /* quotas */
+       u64 i_max_bytes, i_max_files;
+
        struct rb_root i_fragtree;
        int i_fragtree_nsplits;
        struct mutex i_fragtree_mutex;
@@ -1071,4 +1074,9 @@ extern int ceph_locks_to_pagelist(struct ceph_filelock *flocks,
 extern int ceph_fs_debugfs_init(struct ceph_fs_client *client);
 extern void ceph_fs_debugfs_cleanup(struct ceph_fs_client *client);
 
+/* quota.c */
+extern void ceph_handle_quota(struct ceph_mds_client *mdsc,
+                             struct ceph_mds_session *session,
+                             struct ceph_msg *msg);
+
 #endif /* _FS_CEPH_SUPER_H */
index e1c4e0b12b4cd1309af51bad916120c7f2273fc4..7e72348639e4bcbf523ec4bec3a589fbcd643c7c 100644 (file)
@@ -224,6 +224,31 @@ static size_t ceph_vxattrcb_dir_rctime(struct ceph_inode_info *ci, char *val,
                        (long)ci->i_rctime.tv_nsec);
 }
 
+/* quotas */
+
+static bool ceph_vxattrcb_quota_exists(struct ceph_inode_info *ci)
+{
+       return (ci->i_max_files || ci->i_max_bytes);
+}
+
+static size_t ceph_vxattrcb_quota(struct ceph_inode_info *ci, char *val,
+                                 size_t size)
+{
+       return snprintf(val, size, "max_bytes=%llu max_files=%llu",
+                       ci->i_max_bytes, ci->i_max_files);
+}
+
+static size_t ceph_vxattrcb_quota_max_bytes(struct ceph_inode_info *ci,
+                                           char *val, size_t size)
+{
+       return snprintf(val, size, "%llu", ci->i_max_bytes);
+}
+
+static size_t ceph_vxattrcb_quota_max_files(struct ceph_inode_info *ci,
+                                           char *val, size_t size)
+{
+       return snprintf(val, size, "%llu", ci->i_max_files);
+}
 
 #define CEPH_XATTR_NAME(_type, _name)  XATTR_CEPH_PREFIX #_type "." #_name
 #define CEPH_XATTR_NAME2(_type, _name, _name2) \
@@ -247,6 +272,15 @@ static size_t ceph_vxattrcb_dir_rctime(struct ceph_inode_info *ci, char *val,
                .hidden = true,                 \
                .exists_cb = ceph_vxattrcb_layout_exists,       \
        }
+#define XATTR_QUOTA_FIELD(_type, _name)                                        \
+       {                                                               \
+               .name = CEPH_XATTR_NAME(_type, _name),                  \
+               .name_size = sizeof(CEPH_XATTR_NAME(_type, _name)),     \
+               .getxattr_cb = ceph_vxattrcb_ ## _type ## _ ## _name,   \
+               .readonly = false,                                      \
+               .hidden = true,                                         \
+               .exists_cb = ceph_vxattrcb_quota_exists,                \
+       }
 
 static struct ceph_vxattr ceph_dir_vxattrs[] = {
        {
@@ -270,6 +304,16 @@ static struct ceph_vxattr ceph_dir_vxattrs[] = {
        XATTR_NAME_CEPH(dir, rsubdirs),
        XATTR_NAME_CEPH(dir, rbytes),
        XATTR_NAME_CEPH(dir, rctime),
+       {
+               .name = "ceph.quota",
+               .name_size = sizeof("ceph.quota"),
+               .getxattr_cb = ceph_vxattrcb_quota,
+               .readonly = false,
+               .hidden = true,
+               .exists_cb = ceph_vxattrcb_quota_exists,
+       },
+       XATTR_QUOTA_FIELD(quota, max_bytes),
+       XATTR_QUOTA_FIELD(quota, max_files),
        { .name = NULL, 0 }     /* Required table terminator */
 };
 static size_t ceph_dir_vxattrs_name_size;      /* total size of all names */
index 59042d5ac52026a5f43416e6a92a2b70166e63b3..3901927cf6a0618122798e8b16491099d35e2cda 100644 (file)
@@ -204,6 +204,7 @@ DEFINE_CEPH_FEATURE_DEPRECATED(63, 1, RESERVED_BROKEN, LUMINOUS) // client-facin
         CEPH_FEATURE_OSD_PRIMARY_AFFINITY |    \
         CEPH_FEATURE_MSGR_KEEPALIVE2 |         \
         CEPH_FEATURE_OSD_POOLRESEND |          \
+        CEPH_FEATURE_MDS_QUOTA |               \
         CEPH_FEATURE_CRUSH_V4 |                \
         CEPH_FEATURE_NEW_OSDOP_ENCODING |      \
         CEPH_FEATURE_SERVER_JEWEL |            \
index 88dd51381aaf9d72e18f277dee6173a214bcb8c8..7ecfc88314d835605d72189e7458f362bb6a1632 100644 (file)
@@ -134,6 +134,7 @@ struct ceph_dir_layout {
 #define CEPH_MSG_CLIENT_LEASE           0x311
 #define CEPH_MSG_CLIENT_SNAP            0x312
 #define CEPH_MSG_CLIENT_CAPRELEASE      0x313
+#define CEPH_MSG_CLIENT_QUOTA           0x314
 
 /* pool ops */
 #define CEPH_MSG_POOLOP_REPLY           48
@@ -807,4 +808,20 @@ struct ceph_mds_snap_realm {
 } __attribute__ ((packed));
 /* followed by my snap list, then prior parent snap list */
 
+/*
+ * quotas
+ */
+struct ceph_mds_quota {
+       __le64 ino;             /* ino */
+       struct ceph_timespec rctime;
+       __le64 rbytes;          /* dir stats */
+       __le64 rfiles;
+       __le64 rsubdirs;
+       __u8 struct_v;          /* compat */
+       __u8 struct_compat;
+       __le32 struct_len;
+       __le64 max_bytes;       /* quota max. bytes */
+       __le64 max_files;       /* quota max. files */
+} __attribute__ ((packed));
+
 #endif
index cdb5b693a135e77c60007ddd1d5eaaa6ab8ec7a7..850fc0d324ba730a587ce5953d79d6b5314f0d6e 100644 (file)
@@ -79,6 +79,7 @@ const char *ceph_msg_type_name(int type)
        case CEPH_MSG_CLIENT_REPLY: return "client_reply";
        case CEPH_MSG_CLIENT_CAPS: return "client_caps";
        case CEPH_MSG_CLIENT_CAPRELEASE: return "client_cap_release";
+       case CEPH_MSG_CLIENT_QUOTA: return "client_quota";
        case CEPH_MSG_CLIENT_SNAP: return "client_snap";
        case CEPH_MSG_CLIENT_LEASE: return "client_lease";
        case CEPH_MSG_OSD_MAP: return "osd_map";