Whamcloud - gitweb
LU-13031 jobstats: store jobid in xattr when files are created
authorThomas Bertschinger <bertschinger@lanl.gov>
Fri, 5 May 2023 21:05:22 +0000 (17:05 -0400)
committerAndreas Dilger <adilger@whamcloud.com>
Mon, 11 Sep 2023 00:22:22 +0000 (00:22 +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.

Lustre-change: https://review.whamcloud.com/50982
Lustre-commit: 23a2db28dcf1422a6a6da575e907fd257106d402

LU-13031 tests: skip sanity/test_205h,205i in interop

Skip sanity tests 205h and 205i when the MDS version is too old
to have the jobid xattr changes. Fix test 103a to not try to set
the job_xattr parameter when it does not exist.

Lustre-change: https://review.whamcloud.com/52095
Lustre-commit: a6df532162556028c2ab9b974989fc0cca68d4fe

Test-Parameters: testlist=sanity clientdistro=el8.8 clientjob=lustre-b_es-reviews clientbuildno=12634 env=ONLY=103
Signed-off-by: Thomas Bertschinger <bertschinger@lanl.gov>
Change-Id: Iad78a5ec6fbc4b761ff481141763bdd0cdcd0128
Reviewed-by: Patrick Farrell <pfarrell@whamcloud.com>
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
Reviewed-on: https://review.whamcloud.com/c/ex/lustre-release/+/52195
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@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 45f346c..039abce 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 53105ed..c4082f4 100644 (file)
@@ -1250,6 +1250,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 a5875bb..6522138 100644 (file)
@@ -2499,9 +2499,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);
@@ -2594,7 +2598,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,
@@ -2613,6 +2617,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 e2ce2c7..22a02fa 100644 (file)
@@ -6067,6 +6067,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 bfca5f0..d26dfd3 100644 (file)
@@ -350,6 +350,9 @@ struct mdt_device {
        struct mdt_object         *mdt_md_root;
 
        struct mdt_dir_restriper   mdt_restriper;
+
+       /* 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 b634756..1390e4d 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);
 
@@ -1600,6 +1685,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 35f6c4e..0e96455 100644 (file)
@@ -1615,6 +1615,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 df0cc5f..eed9084 100644 (file)
@@ -663,6 +663,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 e7e96b7..1f72b9b 100755 (executable)
@@ -11656,6 +11656,13 @@ 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)
+
+       [[ -z "$saved" ]] || do_nodes $mdts $LCTL set_param mdt.*.job_xattr=NONE
+       stack_trap "[[ -z \"$saved\" ]] || \
+                   do_nodes $mdts $LCTL set_param mdt.*.job_xattr=$saved" EXIT
+
        ACLBIN=${ACLBIN:-"bin"}
        ACLDMN=${ACLDMN:-"daemon"}
        ACLGRP=${ACLGRP:-"users"}
@@ -19163,6 +19170,89 @@ test_205g() {
 }
 run_test 205g "stress test for job_stats procfile"
 
+test_205h() {
+       (( $MDS1_VERSION >= $(version_code 2.14.0.99) )) ||
+               skip "Need MDS >= 2.14.0.99 for jobid xattr"
+       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() {
+       (( $MDS1_VERSION >= $(version_code 2.14.0.99) )) ||
+               skip "Need MDS >= 2.14.0.99 for jobid xattr"
+
+       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