Whamcloud - gitweb
LU-13031 jobstats: store jobid in xattr when files are created 82/50982/8
authorThomas Bertschinger <bertschinger@lanl.gov>
Fri, 5 May 2023 21:05:22 +0000 (17:05 -0400)
committerOleg Drokin <green@whamcloud.com>
Mon, 7 Aug 2023 03:49:43 +0000 (03:49 +0000)
This change stores the jobid of the process that creates a file in an
extended attribute in the file's MDT inode, at file creation time.

The name of the xattr is determined by a new sysfs parameter
"mdt.*.job_xattr" so that the admin can choose a name that does
not conflict with other uses they may have for a given xattr.
The default value is "user.job". A value of "NONE" means that
the jobid will not be stored in the inode.

If the name is in the user namespace "user.", then the name portion
can be up to 7 alphanumeric characters long. The admin can choose
the trusted namespace to prevent users from modifying the value,
but only "trusted.job" is allowed in this namespace.

Allowing users to modify the contents of the xattr is helpful so
that the jobid can be preserved even when files are moved with tools
like `cp` or `rsync`, and when copied from one filesystem to another.

Signed-off-by: Thomas Bertschinger <bertschinger@lanl.gov>
Change-Id: Iad78a5ec6fbc4b761ff481141763bdd0cdcd0128
Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/50982
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
Reviewed-by: Patrick Farrell <pfarrell@whamcloud.com>
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
lustre/include/md_object.h
lustre/include/uapi/linux/lustre/lustre_idl.h
lustre/mdd/mdd_dir.c
lustre/mdt/mdt_handler.c
lustre/mdt/mdt_internal.h
lustre/mdt/mdt_lproc.c
lustre/mdt/mdt_open.c
lustre/mdt/mdt_reint.c
lustre/tests/sanity.sh

index 020cba8..f6a039f 100644 (file)
@@ -177,6 +177,9 @@ struct md_op_spec {
 
        /** to create directory */
        const struct dt_index_features *sp_feat;
+
+       /* name of xattr used to store jobid in inode, or empty if disabled */
+       char sp_cr_job_xattr[XATTR_JOB_MAX_LEN];
 };
 
 enum md_layout_opc {
index 4770e7c..6912a51 100644 (file)
@@ -1231,6 +1231,10 @@ struct lov_mds_md_v1 {            /* LOV EA mds/wire data (little-endian) */
 #define XATTR_NAME_LFSCK_NAMESPACE "trusted.lfsck_ns"
 #define XATTR_NAME_MAX_LEN     32 /* increase this, if there is longer name. */
 
+#define XATTR_NAME_JOB_DEFAULT "user.job"
+/* longest allowed jobid xattr name is "user." + 7 chars + null terminator */
+#define XATTR_JOB_MAX_LEN      13
+
 struct lov_mds_md_v3 {            /* LOV EA mds/wire data (little-endian) */
        __u32 lmm_magic;          /* magic number = LOV_MAGIC_V3 */
        __u32 lmm_pattern;        /* LOV_PATTERN_RAID0, LOV_PATTERN_RAID1 */
index fc1cccf..09840db 100644 (file)
@@ -2500,9 +2500,13 @@ static int mdd_create_object(const struct lu_env *env, struct mdd_object *pobj,
                             struct lu_buf *def_acl_buf,
                             struct lu_buf *hsm_buf,
                             struct dt_allocation_hint *hint,
-                            struct thandle *handle, bool initsecctx)
+                            struct thandle *handle, bool initial_create)
 {
+       const struct lu_fid *son_fid = mdd_object_fid(son);
+       const struct lu_ucred *uc = lu_ucred(env);
+       const char *jobid = uc->uc_jobid;
        const struct lu_buf *buf;
+       size_t jobid_len;
        int rc;
 
        mdd_write_lock(env, son, DT_TGT_CHILD);
@@ -2595,7 +2599,7 @@ static int mdd_create_object(const struct lu_env *env, struct mdd_object *pobj,
                        GOTO(err_initlized, rc = -EFAULT);
        }
 
-       if (initsecctx && spec->sp_cr_file_secctx_name != NULL) {
+       if (initial_create && spec->sp_cr_file_secctx_name != NULL) {
                buf = mdd_buf_get_const(env, spec->sp_cr_file_secctx,
                                        spec->sp_cr_file_secctx_size);
                rc = mdo_xattr_set(env, son, buf, spec->sp_cr_file_secctx_name,
@@ -2614,6 +2618,25 @@ static int mdd_create_object(const struct lu_env *env, struct mdd_object *pobj,
                        GOTO(err_initlized, rc);
        }
 
+       if (initial_create &&
+           spec->sp_cr_job_xattr[0] != '\0' &&
+           jobid[0] != '\0' &&
+           (S_ISREG(attr->la_mode) || S_ISDIR(attr->la_mode))) {
+               jobid_len = strnlen(jobid, LUSTRE_JOBID_SIZE);
+               buf = mdd_buf_get_const(env, jobid, jobid_len);
+
+               rc = mdo_xattr_set(env, son, buf, spec->sp_cr_job_xattr,
+                                  LU_XATTR_CREATE, handle);
+
+               /* this xattr is nonessential, so ignore errors. */
+               if (rc != 0) {
+                       CDEBUG(D_INODE,
+                              DFID" failed to set xattr '%s': rc = %d\n",
+                              PFID(son_fid), spec->sp_cr_job_xattr, rc);
+                       rc = 0;
+               }
+       }
+
 err_initlized:
        if (unlikely(rc != 0)) {
                int rc2;
index ee5a3e5..e0841f9 100644 (file)
@@ -6205,6 +6205,8 @@ static int mdt_init0(const struct lu_env *env, struct mdt_device *m,
        /* Set this lu_device to obd for error handling purposes. */
        obd->obd_lu_dev = &m->mdt_lu_dev;
 
+       strncpy(m->mdt_job_xattr, XATTR_NAME_JOB_DEFAULT, XATTR_JOB_MAX_LEN);
+
        /* init the stack */
        rc = mdt_stack_init((struct lu_env *)env, m, cfg);
        if (rc) {
index 26e9a19..58995ee 100644 (file)
@@ -354,6 +354,9 @@ struct mdt_device {
 
        /* count of old clients that doesn't support DMV implicite inherit */
        atomic_t                   mdt_dmv_old_client_count;
+
+       /* name of xattr used to store jobid in mdt inode */
+       char                       mdt_job_xattr[XATTR_JOB_MAX_LEN];
 };
 
 #define MDT_SERVICE_WATCHDOG_FACTOR    (2)
index c813c05..61eabd6 100644 (file)
@@ -1542,12 +1542,97 @@ static int mdt_checksum_type_seq_show(struct seq_file *m, void *data)
        return 0;
 }
 
+ssize_t job_xattr_show(struct kobject *kobj, struct attribute *attr, char *buf)
+{
+       struct obd_device *obd = container_of(kobj, struct obd_device,
+                                             obd_kset.kobj);
+       struct mdt_device *mdt = mdt_dev(obd->obd_lu_dev);
+
+       if (mdt->mdt_job_xattr[0] == '\0')
+               return scnprintf(buf, PAGE_SIZE, "NONE\n");
+
+       return scnprintf(buf, PAGE_SIZE, "%s\n", mdt->mdt_job_xattr);
+}
+
+/**
+ * Read in a name for the jobid xattr and validate it.
+ * The only valid names are "trusted.job" or "user.*" where the name portion
+ * is <= 7 bytes in the user namespace. Only alphanumeric characters are
+ * allowed, aside from the namespace separator '.'.
+ *
+ * "none" is a valid value to turn this feature off.
+ *
+ * @return -EINVAL if the name is invalid, else count
+ */
+ssize_t job_xattr_store(struct kobject *kobj, struct attribute *attr,
+                       const char *buffer, size_t count)
+{
+       struct obd_device *obd = container_of(kobj, struct obd_device,
+                                             obd_kset.kobj);
+       struct mdt_device *mdt = mdt_dev(obd->obd_lu_dev);
+       char name[XATTR_JOB_MAX_LEN] = { 0 };
+       char *p;
+
+
+       /* writing "none" turns this off by leaving the name empty */
+       if (!strncmp(buffer, "none", 4) ||
+           !strncmp(buffer, "NONE", 4)) {
+               memset(mdt->mdt_job_xattr, 0, sizeof(mdt->mdt_job_xattr));
+               return count;
+       }
+
+       /* account for stripping \n before rejecting name for being too long */
+       if (count > XATTR_JOB_MAX_LEN - 1 &&
+           buffer[XATTR_JOB_MAX_LEN - 1] != '\n')
+               return -EINVAL;
+
+       strncpy(name, buffer, XATTR_JOB_MAX_LEN - 1);
+
+       /* reject if not in namespace.name format */
+       p = strchr(name, '.');
+       if (p == NULL)
+               return -EINVAL;
+
+       p++;
+       for (; *p != '\0'; p++) {
+               /*
+                * if there are any non-alphanumeric characters, the name is
+                * invalid unless it's a newline, in which case overwrite it
+                * with '\0' and that's the end of the name.
+                */
+               if (!isalnum(*p)) {
+                       if (*p != '\n')
+                               return -EINVAL;
+                       *p = '\0';
+               }
+       }
+
+       /* trusted.job is only valid name in trusted namespace */
+       if (!strncmp(name, "trusted.job", 12)) {
+               strncpy(mdt->mdt_job_xattr, name, XATTR_JOB_MAX_LEN);
+               return count;
+       }
+
+       /* only other valid namespace is user */
+       if (strncmp(name, XATTR_USER_PREFIX, sizeof(XATTR_USER_PREFIX) - 1))
+               return -EINVAL;
+
+       /* ensure that a name was specified */
+       if (name[sizeof(XATTR_USER_PREFIX) - 1] == '\0')
+               return -EINVAL;
+
+       strncpy(mdt->mdt_job_xattr, name, XATTR_JOB_MAX_LEN);
+
+       return count;
+}
+
 LPROC_SEQ_FOPS_RO(mdt_checksum_type);
 
 LPROC_SEQ_FOPS_RO_TYPE(mdt, hash);
 LPROC_SEQ_FOPS_WR_ONLY(mdt, mds_evict_client);
 LPROC_SEQ_FOPS_RW_TYPE(mdt, checksum_dump);
 LUSTRE_RW_ATTR(job_cleanup_interval);
+LUSTRE_RW_ATTR(job_xattr);
 LPROC_SEQ_FOPS_RW_TYPE(mdt, nid_stats_clear);
 LUSTRE_RW_ATTR(hsm_control);
 
@@ -1603,6 +1688,7 @@ static struct attribute *mdt_attrs[] = {
        &lustre_attr_migrate_hsm_allowed.attr,
        &lustre_attr_hsm_control.attr,
        &lustre_attr_job_cleanup_interval.attr,
+       &lustre_attr_job_xattr.attr,
        &lustre_attr_readonly.attr,
        &lustre_attr_dir_split_count.attr,
        &lustre_attr_dir_split_delta.attr,
index 59bc55a..142a6f3 100644 (file)
@@ -1577,6 +1577,10 @@ again_pw:
                info->mti_spec.sp_cr_lookup = 0;
                info->mti_spec.sp_feat = &dt_directory_features;
 
+               /* set jobid xattr name from sysfs parameter */
+               strncpy(info->mti_spec.sp_cr_job_xattr, mdt->mdt_job_xattr,
+                       XATTR_JOB_MAX_LEN);
+
                result = mdo_create(info->mti_env, mdt_object_child(parent),
                                    &rr->rr_name, mdt_object_child(child),
                                    &info->mti_spec, &info->mti_attr);
index 4e05c07..701863e 100644 (file)
@@ -651,6 +651,10 @@ static int mdt_create(struct mdt_thread_info *info)
                info->mti_spec.sp_cr_lookup = 1;
        info->mti_spec.sp_feat = &dt_directory_features;
 
+       /* set jobid xattr name from sysfs parameter */
+       strncpy(info->mti_spec.sp_cr_job_xattr, mdt->mdt_job_xattr,
+               XATTR_JOB_MAX_LEN);
+
        rc = mdo_create(info->mti_env, mdt_object_child(parent), &rr->rr_name,
                        mdt_object_child(child), &info->mti_spec, ma);
        if (rc == 0)
index befc9ea..0445ebb 100755 (executable)
@@ -12225,6 +12225,12 @@ test_103a() {
        which setfacl || skip_env "could not find setfacl"
        remote_mds_nodsh && skip "remote MDS with nodsh"
 
+       local mdts=$(comma_list $(mdts_nodes))
+       local saved=$(do_facet mds1 $LCTL get_param -n mdt.$FSNAME-MDT0000.job_xattr)
+
+       do_nodes $mdts $LCTL set_param mdt.*.job_xattr=NONE
+       stack_trap "do_nodes $mdts $LCTL set_param mdt.*.job_xattr=$saved" EXIT
+
        ACLBIN=${ACLBIN:-"bin"}
        ACLDMN=${ACLDMN:-"daemon"}
        ACLGRP=${ACLGRP:-"users"}
@@ -19949,6 +19955,84 @@ test_205g() {
 }
 run_test 205g "stress test for job_stats procfile"
 
+test_205h() {
+       which getfattr > /dev/null 2>&1 || skip_env "no getfattr command"
+
+       local dir=$DIR/$tdir
+       local f=$dir/$tfile
+       local f2=$dir/$tfile-2
+       local f3=$dir/$tfile-3
+       local subdir=$DIR/dir
+       local val
+
+       local mdts=$(comma_list $(mdts_nodes))
+       local mds_saved=$(do_facet mds1 $LCTL get_param -n mdt.$FSNAME-MDT0000.job_xattr)
+       local client_saved=$($LCTL get_param -n jobid_var)
+
+       stack_trap "do_nodes $mdts $LCTL set_param mdt.*.job_xattr=$mds_saved" EXIT
+       stack_trap "$LCTL set_param jobid_var=$client_saved" EXIT
+
+       do_nodes $mdts $LCTL set_param mdt.*.job_xattr=user.job ||
+               error "failed to set job_xattr parameter to user.job"
+       $LCTL set_param jobid_var=procname.uid ||
+               error "failed to set jobid_var parameter"
+
+       test_mkdir $dir
+
+       touch $f
+       val=$(getfattr -n user.job $f | grep user.job)
+       [[ $val = user.job=\"touch.0\" ]] ||
+               error "expected user.job=\"touch.0\", got '$val'"
+
+       mkdir $subdir
+       val=$(getfattr -n user.job $subdir | grep user.job)
+       [[ $val = user.job=\"mkdir.0\" ]] ||
+               error "expected user.job=\"mkdir.0\", got '$val'"
+
+       do_nodes $mdts $LCTL set_param mdt.*.job_xattr=NONE ||
+               error "failed to set job_xattr parameter to NONE"
+
+       touch $f2
+       val=$(getfattr -d $f2)
+       [[ -z $val ]] ||
+               error "expected no user xattr, got '$val'"
+
+       do_nodes $mdts $LCTL set_param mdt.*.job_xattr=trusted.job ||
+               error "failed to set job_xattr parameter to trusted.job"
+
+       touch $f3
+       val=$(getfattr -n trusted.job $f3 | grep trusted.job)
+       [[ $val = trusted.job=\"touch.0\" ]] ||
+               error "expected trusted.job=\"touch.0\", got '$val'"
+}
+run_test 205h "check jobid xattr is stored correctly"
+
+test_205i() {
+       local mdts=$(comma_list $(mdts_nodes))
+       local mds_saved=$(do_facet mds1 $LCTL get_param -n mdt.$FSNAME-MDT0000.job_xattr)
+
+       stack_trap "do_nodes $mdts $LCTL set_param mdt.*.job_xattr=$mds_saved" EXIT
+
+       do_nodes $mdts $LCTL set_param mdt.*.job_xattr=user.1234567 ||
+               error "failed to set mdt.*.job_xattr to user.1234567"
+
+       do_nodes $mdts $LCTL set_param mdt.*.job_xattr=user.12345678 &&
+               error "failed to reject too long job_xattr name"
+
+       do_nodes $mdts $LCTL set_param mdt.*.job_xattr=userjob &&
+               error "failed to reject job_xattr name in bad format"
+
+       do_nodes $mdts $LCTL set_param mdt.*.job_xattr=user.job/ &&
+               error "failed to reject job_xattr name with invalid character"
+
+       do_nodes $mdts "printf 'mdt.*.job_xattr=user.job\x80' |
+                       xargs $LCTL set_param" &&
+               error "failed to reject job_xattr name with non-ascii character"
+
+       return 0
+}
+run_test 205i "check job_xattr parameter accepts and rejects values correctly"
+
 # LU-1480, LU-1773 and LU-1657
 test_206() {
        mkdir -p $DIR/$tdir