Whamcloud - gitweb
LU-10070 lod: SEL: Repeated components 86/33786/28
authorPatrick Farrell <paf@cray.com>
Thu, 6 Jun 2019 16:34:21 +0000 (19:34 +0300)
committerOleg Drokin <green@whamcloud.com>
Fri, 12 Jul 2019 05:21:26 +0000 (05:21 +0000)
This changes behavior when there is no next component to
spill over to.  Currently, in that case, we just extend
the current component regardless of available space.

Now, if there is no following component, we try repeating
the current component, creating a new component using the
current one as a striping template.  We try assigning
striping for this component.  If there is sufficient free
space on the OSTs chosen for this component, it is
instantiated and i/o continues there.

If there is not sufficient space on the OSTs chosen for the
new component, we remove it & extend the current component.

This is a behavioral improvement, with no implications for
layout sanity checking.

Cray-bug-id: LUS-2528
Signed-off-by: Patrick Farrell <paf@cray.com>
Change-Id: If9f364b4105a4bb892dfe673c724e04781c46336
Reviewed-on: https://review.whamcloud.com/33786
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Alexey Lyashkov <c17817@cray.com>
Reviewed-by: Patrick Farrell <pfarrell@whamcloud.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
lustre/lod/lod_object.c
lustre/tests/sanity-flr.sh
lustre/tests/sanity-pfl.sh

index 1b023b6..27c5e8d 100644 (file)
@@ -4119,6 +4119,112 @@ static int lod_generate_and_set_lovea(const struct lu_env *env,
        RETURN(rc);
 }
 
+static __u32 lod_gen_component_id(struct lod_object *lo,
+                                 int mirror_id, int comp_idx);
+
+/**
+ * Repeat an existing component
+ *
+ * Creates a new layout by replicating an existing component.  Uses striping
+ * policy from previous component as a template for the striping for the new
+ * new component.
+ *
+ * New component starts with zero length, will be extended (or removed) before
+ * returning layout to client.
+ *
+ * NB: Reallocates layout components array (lo->ldo_comp_entries), invalidating
+ * any pre-existing pointers to components.  Handle with care.
+ *
+ * \param[in] env      execution environment for this thread
+ * \param[in,out] lo   object to update the layout of
+ * \param[in] index    index of component to copy
+ *
+ * \retval     0 on success
+ * \retval     negative errno on error
+ */
+static int lod_layout_repeat_comp(const struct lu_env *env,
+                                 struct lod_object *lo, int index)
+{
+       struct lod_layout_component *lod_comp;
+       struct lod_layout_component *new_comp = NULL;
+       struct lod_layout_component *comp_array;
+       int rc = 0, i, new_cnt = lo->ldo_comp_cnt + 1;
+       __u16 mirror_id;
+       int offset = 0;
+       ENTRY;
+
+       lod_comp = &lo->ldo_comp_entries[index];
+       LASSERT(lod_comp_inited(lod_comp) && lod_comp->llc_id != LCME_ID_INVAL);
+
+       CDEBUG(D_LAYOUT, "repeating component %d\n", index);
+
+       OBD_ALLOC(comp_array, sizeof(*comp_array) * new_cnt);
+       if (comp_array == NULL)
+               GOTO(out, rc = -ENOMEM);
+
+       for (i = 0; i < lo->ldo_comp_cnt; i++) {
+               memcpy(&comp_array[i + offset], &lo->ldo_comp_entries[i],
+                      sizeof(*comp_array));
+
+               /* Duplicate this component in to the next slot */
+               if (i == index) {
+                       new_comp = &comp_array[i + 1];
+                       memcpy(&comp_array[i + 1], &lo->ldo_comp_entries[i],
+                              sizeof(*comp_array));
+                       /* We must now skip this new component when copying */
+                       offset = 1;
+               }
+       }
+
+       /* Set up copied component */
+       new_comp->llc_flags &= ~LCME_FL_INIT;
+       new_comp->llc_stripe = NULL;
+       new_comp->llc_stripes_allocated = 0;
+       new_comp->llc_stripe_offset = LOV_OFFSET_DEFAULT;
+       /* for uninstantiated components, layout gen stores default stripe
+        * offset */
+       new_comp->llc_layout_gen = lod_comp->llc_stripe_offset;
+       /* This makes the repeated component zero-length, placed at the end of
+        * the preceding component */
+       new_comp->llc_extent.e_start = new_comp->llc_extent.e_end;
+       new_comp->llc_timestamp = lod_comp->llc_timestamp;
+       new_comp->llc_pool = NULL;
+
+       rc = lod_set_pool(&new_comp->llc_pool, lod_comp->llc_pool);
+       if (rc)
+               GOTO(out, rc);
+
+       if (new_comp->llc_ostlist.op_array) {
+               __u32 *op_array = NULL;
+
+               OBD_ALLOC(op_array, new_comp->llc_ostlist.op_size);
+               if (!op_array)
+                       GOTO(out, rc = -ENOMEM);
+               memcpy(op_array, &new_comp->llc_ostlist.op_array,
+                      new_comp->llc_ostlist.op_size);
+               new_comp->llc_ostlist.op_array = op_array;
+       }
+
+       OBD_FREE(lo->ldo_comp_entries,
+                sizeof(*comp_array) * lo->ldo_comp_cnt);
+       lo->ldo_comp_entries = comp_array;
+       lo->ldo_comp_cnt = new_cnt;
+
+       /* Generate an id for the new component */
+       mirror_id = mirror_id_of(new_comp->llc_id);
+       new_comp->llc_id = LCME_ID_INVAL;
+       new_comp->llc_id = lod_gen_component_id(lo, mirror_id, index + 1);
+       if (new_comp->llc_id == LCME_ID_INVAL)
+               GOTO(out, rc = -ERANGE);
+
+       EXIT;
+out:
+       if (rc)
+               OBD_FREE(comp_array, sizeof(*comp_array) * new_cnt);
+
+       return rc;
+}
+
 static int lod_layout_data_init(struct lod_thread_info *info, __u32 comp_cnt)
 {
        ENTRY;
@@ -6359,6 +6465,16 @@ static __u64 lod_extension_new_end(__u64 extension_size, __u64 extent_end,
        return new_end;
 }
 
+/* As lod_sel_handler() could be re-entered for the same component several
+ * times, this is the data for the next call. Fields could be changed to
+ * component indexes when needed, (e.g. if there is no need to instantiate
+ * all the previous components up to the current position) to tell the caller
+ * where to start over from. */
+struct sel_data {
+       int sd_force;
+       int sd_repeat;
+};
+
 /**
  * Process extent updates for a particular layout component
  *
@@ -6381,8 +6497,15 @@ static __u64 lod_extension_new_end(__u64 extension_size, __u64 extent_end,
  *       extension space component, move the start of the next component down
  *       accordingly, then notify the caller to restart processing w/the new
  *       layout.
- *    b. If there is no following component, we extend the current component
- *       regardless.
+ *    b. If there is no following component, we try repeating the current
+ *       component, creating a new component using the current one as a
+ *       template (keeping its stripe properties but not specific striping),
+ *       and try assigning striping for this component.  If there is sufficient
+ *       free space on the OSTs chosen for this component, it is instantiated
+ *       and i/o continues there.
+ *
+ *       If there is not sufficient space on the new OSTs, we remove this new
+ *       component & extend the current component.
  *
  * Note further that uninited components followed by extension space can be zero
  * length meaning that we will try to extend them before initializing them, and
@@ -6415,7 +6538,8 @@ static int lod_sel_handler(const struct lu_env *env,
                          struct lod_object *lo,
                          struct lu_extent *extent,
                          struct thandle *th, int *max_comp,
-                         int index, int write, int *force)
+                         int index, int write,
+                         struct sel_data *sd)
 {
        struct lod_device *d = lu2lod_dev(lo->ldo_obj.do_lu.lo_dev);
        struct lod_thread_info *info = lod_env_info(env);
@@ -6424,6 +6548,7 @@ static int lod_sel_handler(const struct lu_env *env,
        struct lod_layout_component *next = NULL;
        __u64 extension_size;
        __u64 new_end = 0;
+       bool repeated;
        int change = 0;
        int rc = 0;
        ENTRY;
@@ -6463,21 +6588,24 @@ static int lod_sel_handler(const struct lu_env *env,
                info->lti_count = 1;
                info->lti_comp_idx[0] = index - 1;
                rc = lod_declare_instantiate_components(env, lo, th);
-               /* ENOSPC tells us we can't use this component, so it's the
-                * same as if check_extension failed.  But if there is no next,
-                * we have no fall back if we failed to stripe this, and we
-                * should return the error. */
-               if (rc == -ENOSPC && next)
+               /* ENOSPC tells us we can't use this component.  If there is
+                * a next or we are repeating, we either spill over (next) or
+                * extend the original comp (repeat).  Otherwise, return the
+                * error to the user. */
+               if (rc == -ENOSPC && (next || sd->sd_repeat))
                        rc = 1;
                if (rc < 0)
                        RETURN(rc);
        }
 
-       if (*force == 0 && rc == 0)
+       if (sd->sd_force == 0 && rc == 0)
                rc = !lod_sel_osts_allowed(env, lo, index - 1,
                                           extension_size, extent,
                                           &lod_comp->llc_extent, write);
-       *force = 0;
+
+       repeated = !!(sd->sd_repeat);
+       sd->sd_repeat = 0;
+       sd->sd_force = 0;
 
        /* Extend previous component */
        if (rc == 0) {
@@ -6509,8 +6637,29 @@ static int lod_sel_handler(const struct lu_env *env,
                                change--;
                        }
                        lod_sel_adjust_extents(env, lo, *max_comp, index);
+               } else if (lod_comp_inited(prev)) {
+                       /* If there is no next, and the previous component is
+                        * INIT'ed, try repeating the previous component. */
+                       LASSERT(repeated == 0);
+                       rc = lod_layout_repeat_comp(env, lo, index - 1);
+                       if (rc < 0)
+                               RETURN(rc);
+                       change++;
+                       /* The previous component is a repeated component.
+                        * Record this so we don't keep trying to repeat it. */
+                       sd->sd_repeat = 1;
                } else {
-                       *force = 1;
+                       /* If the previous component is not INIT'ed, this may
+                        * be a component we have just instantiated but failed
+                        * to extend. Or even a repeated component we failed
+                        * to prepare a striping for. Do not repeat but instead
+                        * remove the repeated component & force the extention
+                        * of the original one */
+                       sd->sd_force = 1;
+                       if (repeated) {
+                               prev->llc_id = LCME_ID_INVAL;
+                               change--;
+                       }
                }
        }
 
@@ -6564,12 +6713,12 @@ static int lod_declare_update_extents(const struct lu_env *env,
        struct lod_thread_info *info = lod_env_info(env);
        struct lod_layout_component *lod_comp;
        bool layout_changed = false;
+       struct sel_data sd = { 0 };
        int start_index;
        int i = 0;
        int max_comp = 0;
        int rc = 0, rc2;
        int change = 0;
-       int force = 0;
        ENTRY;
 
        /* This makes us work on the components of the chosen mirror */
@@ -6590,7 +6739,7 @@ static int lod_declare_update_extents(const struct lu_env *env,
                if (lod_comp->llc_flags & LCME_FL_EXTENSION) {
                        layout_changed = true;
                        rc = lod_sel_handler(env, lo, extent, th, &max_comp,
-                                            i, write, &force);
+                                            i, write, &sd);
                        if (rc < 0)
                                GOTO(out, rc);
 
@@ -6600,10 +6749,9 @@ static int lod_declare_update_extents(const struct lu_env *env,
                }
        }
 
-       /* We may have removed components.  If so, we must update the start &
-        * ends of all the mirrors after the current one, and the end of the
-        * current mirror.
-        */
+       /* We may have added or removed components.  If so, we must update the
+        * start & ends of all the mirrors after the current one, and the end
+        * of the current mirror. */
        change = max_comp - 1 - lo->ldo_mirrors[pick].lme_end;
        if (change) {
                lo->ldo_mirrors[pick].lme_end += change;
index 443778b..d4bf66c 100644 (file)
@@ -2466,6 +2466,225 @@ test_204c() {
 }
 run_test 204c "FLR write/stale/resync test with component removal"
 
+# Successful repeated component in primary mirror
+test_204d() {
+       [ $OSTCOUNT -lt 2 ] && skip "needs >= 2 OSTs" && return
+       [ $(lustre_version_code $SINGLEMDS) -lt $(version_code $SEL_VER) ] &&
+               skip "skipped for lustre < $SEL_VERSION"
+
+       local comp_file=$DIR/$tdir/$tfile
+       local found=""
+
+       wait_delete_completed
+       wait_mds_ost_sync
+       test_mkdir $DIR/$tdir
+
+       # first mirror is 64M followed by extension space to -1, second mirror
+       # is 0-10M, then 10M-(-1)
+       $LFS setstripe -N -E-1 -z64M -N -E 10M -E-1 $comp_file ||
+               error "Create $comp_file failed"
+
+       local ost_idx1=$($LFS getstripe -I65537 -i $comp_file)
+       local ost_name=$(ostname_from_index $ost_idx1)
+       # degrade OST for first comp so we won't extend there
+       do_facet ost$((ost_idx1+1)) $LCTL set_param -n \
+               obdfilter.$ost_name.degraded=1
+       sleep_maxage
+
+       # Write beyond first component, causing repeat & stale second mirror
+       dd if=/dev/zero bs=1M count=1 seek=66 of=$comp_file conv=notrunc
+       RC=$?
+
+       do_facet ost$((ost_idx1+1)) $LCTL set_param -n \
+               obdfilter.$ost_name.degraded=0
+       sleep_maxage
+
+       [ $RC -eq 0 ] || error "dd to repeat & stale failed"
+
+       $LFS getstripe $comp_file
+
+       found=$($LFS find --component-start 64m --component-end 128m \
+               --component-flags init $comp_file | wc -l)
+       [ $found -eq 1 ] || error "write: Repeat comp incorrect"
+
+       local ost_idx2=$($LFS getstripe --component-start=64m           \
+                        --component-end=128m --component-flags=init    \
+                        -i $comp_file)
+       [[ $ost_idx1 -eq $ost_idx2 ]] && error "$ost_idx1 == $ost_idx2"
+       local mirror_id=$($LFS getstripe --component-start=64m          \
+                        --component-end=128m --component-flags=init    \
+                        $comp_file | grep lcme_mirror_id | awk '{ print $2 }')
+       [[ $mirror_id -eq 1 ]] ||
+               error "component not in correct mirror: $mirror_id, not 1"
+
+       $LFS mirror resync $comp_file
+
+       $LFS getstripe $comp_file
+
+       # component dimensions should not change from resync
+       found=$($LFS find --component-start 0m --component-end 64m \
+               --component-flags init $comp_file | wc -l)
+       [ $found -eq 1 ] || error "resync: first comp incorrect"
+       found=$($LFS find --component-start 64m --component-end 128m \
+               --component-flags init $comp_file | wc -l)
+       [ $found -eq 1 ] || error "resync: repeat comp incorrect"
+
+       sel_layout_sanity $comp_file 5
+
+       rm -f $comp_file
+}
+run_test 204d "FLR write/stale/resync sel test with repeated comp"
+
+# Successful repeated component, SEL in non-primary mirror
+test_204e() {
+       [ $OSTCOUNT -lt 2 ] && skip "needs >= 2 OSTs" && return
+       [ $(lustre_version_code $SINGLEMDS) -lt $(version_code $SEL_VER) ] &&
+               skip "skipped for lustre < $SEL_VERSION"
+
+       local comp_file=$DIR/$tdir/$tfile
+       local found=""
+
+       wait_delete_completed
+       wait_mds_ost_sync
+
+       test_mkdir $DIR/$tdir
+
+       # first mirror is is 0-100M, then 100M-(-1), second mirror is extension
+       # space to -1 (-z 64M, so first comp is 0-64M)
+       # Note: we have to place both 1st components on OST0, otherwise 2 OSTs
+       # will be not enough - one will be degraded, the other is used on
+       # an overlapping mirror.
+       $LFS setstripe -N -E 100M -i 0 -E-1 -N -E-1 -i 0 -z 64M $comp_file ||
+               error "Create $comp_file failed"
+
+       local ost_idx1=$($LFS getstripe --component-start=0 \
+                        --component-end=64m -i $comp_file)
+       local ost_name=$(ostname_from_index $ost_idx1)
+       # degrade OST for first comp of 2nd mirror so we won't extend there
+       do_facet ost$((ost_idx1+1)) $LCTL set_param -n \
+               obdfilter.$ost_name.degraded=1
+       sleep_maxage
+
+       $LFS getstripe $comp_file
+
+       # Write to first component, stale & instantiate second mirror components
+       # overlapping with the written component (0-100M);
+       dd if=/dev/zero bs=2M count=1 of=$comp_file conv=notrunc
+       RC=$?
+
+       do_facet ost$((ost_idx1+1)) $LCTL set_param -n \
+               obdfilter.$ost_name.degraded=0
+       sleep_maxage
+       $LFS getstripe $comp_file
+
+       [ $RC -eq 0 ] || error "dd to repeat & stale failed"
+
+       found=$($LFS find --component-start 0m --component-end 64m \
+               --component-flags init,stale $comp_file | wc -l)
+       [ $found -eq 1 ] || error "write: first comp incorrect"
+
+       # was repeated due to degraded ost
+       found=$($LFS find --component-start 64m --component-end 128m \
+               --component-flags init,stale $comp_file | wc -l)
+       [ $found -eq 1 ] || error "write: repeated comp incorrect"
+
+       local ost_idx2=$($LFS getstripe --component-start=64m           \
+                        --component-end=128m --component-flags=init    \
+                        -i $comp_file)
+       [[ $ost_idx1 -eq $ost_idx2 ]] && error "$ost_idx1 == $ost_idx2"
+       local mirror_id=$($LFS getstripe --component-start=0m           \
+                        --component-end=64m --component-flags=init     \
+                        $comp_file | grep lcme_mirror_id | awk '{ print $2 }')
+       [[ $mirror_id -eq 2 ]] ||
+               error "component not in correct mirror? $mirror_id"
+
+       $LFS mirror resync $comp_file
+
+       $LFS getstripe $comp_file
+
+       # component dimensions should not change from resync
+       found=$($LFS find --component-start 0m --component-end 64m \
+               --component-flags init,^stale $comp_file | wc -l)
+       [ $found -eq 1 ] || error "resync: first comp incorrect"
+       found=$($LFS find --component-start 64m --component-end 128m \
+               --component-flags init,^stale $comp_file | wc -l)
+       [ $found -eq 1 ] || error "resync: repeated comp incorrect"
+
+       sel_layout_sanity $comp_file 5
+
+       rm -f $comp_file
+}
+run_test 204e "FLR write/stale/resync sel test with repeated comp"
+
+# FLR + SEL: failed repeated component, SEL in non-primary mirror
+test_204f() {
+       [ $OSTCOUNT -lt 2 ] && skip "needs >= 2 OSTs" && return
+       [ $(lustre_version_code $SINGLEMDS) -lt $(version_code $SEL_VER) ] &&
+               skip "skipped for lustre < $SEL_VERSION"
+
+       local comp_file=$DIR/$tdir/$tfile
+       local found=""
+
+       wait_delete_completed
+       wait_mds_ost_sync
+       test_mkdir $DIR/$tdir
+
+       pool_add $TESTNAME || error "Pool creation failed"
+       pool_add_targets $TESTNAME 0 1 || error "Pool add targets failed"
+
+       # first mirror is is 0-100M, then 100M-(-1), second mirror is extension
+       # space to -1 (-z 64M, so first comp is 0-64M)
+       $LFS setstripe -N -E 100M -E-1 -N --pool="$TESTNAME" \
+               -E-1 -c 1 -z 64M $comp_file || error "Create $comp_file failed"
+
+       local ost_name0=$(ostname_from_index 0)
+       local ost_name1=$(ostname_from_index 1)
+
+       # degrade both OSTs in pool, so we'll try to repeat, then fail and
+       # extend original comp
+       do_facet ost1 $LCTL set_param -n obdfilter.$ost_name0.degraded=1
+       do_facet ost2 $LCTL set_param -n obdfilter.$ost_name1.degraded=1
+       sleep_maxage
+
+       # a write to the 1st component, 100M length, which will try to stale
+       # the first 100M of mirror 2, attempting to extend its 0-64M component
+       dd if=/dev/zero bs=2M count=1 of=$comp_file conv=notrunc
+       RC=$?
+
+       do_facet ost1 $LCTL set_param -n obdfilter.$ost_name0.degraded=0
+       do_facet ost2 $LCTL set_param -n obdfilter.$ost_name1.degraded=0
+       sleep_maxage
+
+       [ $RC -eq 0 ] || error "dd to extend mirror comp failed"
+
+       $LFS getstripe $comp_file
+
+       found=$($LFS find --component-start 0m --component-end 128m \
+               --component-flags init,stale $comp_file | wc -l)
+       [ $found -eq 1 ] || error "write: First mirror comp incorrect"
+
+       local mirror_id=$($LFS getstripe --component-start=0m           \
+                        --component-end=128m --component-flags=init    \
+                        $comp_file | grep lcme_mirror_id | awk '{ print $2 }')
+
+       [[ $mirror_id -eq 2 ]] ||
+               error "component not in correct mirror? $mirror_id, not 2"
+
+       $LFS mirror resync $comp_file
+
+       $LFS getstripe $comp_file
+
+       # component dimensions should not change from resync
+       found=$($LFS find --component-start 0m --component-end 128m \
+               --component-flags init,^stale $comp_file | wc -l)
+       [ $found -eq 1 ] || error "resync: First mirror comp incorrect"
+
+       sel_layout_sanity $comp_file 4
+
+       rm -f $comp_file
+}
+run_test 204f "FLR write/stale/resync sel w/forced extension"
+
 complete $SECONDS
 check_and_cleanup_lustre
 exit_status
index e6f709c..dfdd83f 100644 (file)
@@ -1544,6 +1544,312 @@ test_21b() {
 }
 run_test 21b "DoM followed by extendable component with removal"
 
+# Test of repeat component behavior with OOS/degraded OST
+test_22a() {
+       [ $OSTCOUNT -lt 2 ] && skip "needs >= 2 OSTs" && return
+       [ $(lustre_version_code $SINGLEMDS) -lt $(version_code $SEL_VER) ] &&
+               skip "skipped for lustre < $SEL_VER"
+
+       local comp_file=$DIR/$tdir/$tfile
+       local flg_opts=""
+       local found=""
+
+       test_mkdir -p $DIR/$tdir
+
+       $LFS setstripe -E -1 -c 1 -z 128M $comp_file ||
+               error "Create $comp_file failed"
+
+       local ost_idx1=$($LFS getstripe -I1 -i $comp_file)
+       local ost_name=$(ostname_from_index $ost_idx1)
+
+       # write past end of first component, so it is extended
+       dd if=/dev/zero of=$comp_file bs=1M count=1 seek=130 ||
+               error "dd to extend failed"
+
+       flg_opts="--comp-flags extension"
+       found=$($LFS find --comp-start 256M -E EOF $flg_opts $comp_file | wc -l)
+       [ $found -eq 1 ] || error "Write: second component not found"
+
+       # degrade OST for component 1
+       do_facet ost$((ost_idx1+1)) $LCTL set_param -n \
+               obdfilter.$ost_name.degraded=1
+       # sleep to guarantee we see the degradation
+       sleep_maxage
+
+       # un-degrade on exit
+       stack_trap "do_facet ost$((ost_idx1+1)) $LCTL set_param -n \
+               obdfilter.$ost_name.degraded=0; sleep_maxage" EXIT
+
+       replay_barrier $SINGLEMDS
+
+       # seek past the end of current comp & write, should cause a new comp
+       # to be created (ie repeat previous comp)
+       dd if=/dev/zero of=$comp_file bs=1M count=1 seek=300 ||
+               error "dd to repeat failed"
+
+       local ost_idx2=$($LFS getstripe -I2 -i $comp_file)
+
+       [ $ost_idx1 -eq $ost_idx2 ] && error "$ost_idx1 == $ost_idx2"
+
+       flg_opts="--comp-flags init"
+       found=$($LFS find --comp-start 256M $flg_opts $comp_file | wc -l)
+       [ $found -eq 1 ] || error "Write: second component not found"
+
+       flg_opts="--comp-flags extension"
+       found=$($LFS find --comp-start 384M -E EOF $flg_opts $comp_file | wc -l)
+       [ $found -eq 1 ] || error "Write: extension component not found"
+
+       fail $SINGLEMDS
+
+       local ost_idx2_2=$($LFS getstripe -I2 -i $comp_file)
+       [ $ost_idx2_2 -ne $ost_idx2 ] && error "$ost_idx2_2 != $ost_idx2"
+
+       flg_opts="--comp-flags init"
+       found=$($LFS find --comp-start 256M $flg_opts $comp_file | wc -l)
+       [ $found -eq 1 ] || error "Failover: second component not found"
+
+       flg_opts="--comp-flags extension"
+       found=$($LFS find --comp-start 384M -E EOF $flg_opts $comp_file | wc -l)
+       [ $found -eq 1 ] || error "Failover: extension component not found"
+
+       sel_layout_sanity $comp_file 3
+}
+run_test 22a "Test repeat component behavior with degraded OST"
+
+# Test repeat behavior with low space
+test_22b() {
+       [ $OSTCOUNT -lt 2 ] && skip "needs >= 2 OSTs" && return
+       [ $(lustre_version_code $SINGLEMDS) -lt $(version_code $SEL_VER) ] &&
+               skip "skipped for lustre < $SEL_VER"
+
+       local comp_file=$DIR/$tdir/$tfile
+       local flg_opts=""
+       local found=""
+
+       test_mkdir -p $DIR/$tdir
+
+       # without this, a previous delete can finish after we check free space
+       wait_delete_completed
+       wait_mds_ost_sync
+
+       $LFS setstripe -E -1 -c 1 -z 128M \
+               $comp_file || error "Create $comp_file failed"
+
+       # write past end of first component, so it is extended
+       dd if=/dev/zero of=$comp_file bs=1M count=1 seek=130 ||
+               error "dd to extend failed"
+
+       flg_opts="--comp-flags extension"
+       found=$($LFS find --comp-start 256M -E EOF $flg_opts $comp_file | wc -l)
+       [ $found -eq 1 ] || error "Write: Second component not found"
+
+       # set our OST low on space
+       local ost_idx1=$($LFS getstripe -I1 -i $comp_file)
+       local wms=$(ost_watermarks_set_low_space $ost_idx1 | grep "watermarks")
+
+       stack_trap "ost_watermarks_clear_enospc $tfile $ost_idx1 $wms" EXIT
+
+       # Write past end of current space, fail to extend, causing repeat
+       dd if=/dev/zero of=$comp_file bs=1M count=1 seek=300 ||
+               error "dd to repeat failed"
+
+       $LFS getstripe $comp_file
+
+       local ost_idx2=$($LFS getstripe -I2 -i $comp_file)
+
+       [ $ost_idx1 -eq $ost_idx2 ] && error "$ost_idx1 == $ost_idx2"
+
+       flg_opts="--comp-flags init"
+       found=$($LFS find --comp-start 256M $flg_opts $comp_file | wc -l)
+       [ $found -eq 1 ] || error "Write: Second component not found"
+
+       flg_opts="--comp-flags extension"
+       found=$($LFS find --comp-start 384M -E EOF $flg_opts $comp_file | wc -l)
+       [ $found -eq 1 ] || error "Write: Extension component not found"
+
+       sel_layout_sanity $comp_file 3
+}
+run_test 22b "Test simple 'out of space' condition with repeat"
+
+# This tests both "repeat" and "extend in place when repeat fails" aspects
+# of repeating components
+test_22c() {
+       [ $OSTCOUNT -lt 2 ] && skip "needs >= 2 OSTs" && return
+       [ $(lustre_version_code $SINGLEMDS) -lt $(version_code $SEL_VER) ] &&
+               skip "skipped for lustre < $SEL_VER"
+
+       local comp_file=$DIR/$tdir/$tfile
+       local flg_opts=""
+       local found=""
+
+       test_mkdir -p $DIR/$tdir
+
+       # pool is used to limit available OSTs to 0 and 1, so we can set all
+       # available OSTs out of space
+       pool_add $TESTNAME || error "Pool creation failed"
+       pool_add_targets $TESTNAME 0 1 || error "Pool add targets failed"
+
+       # without this, a previous delete can finish after we check free space
+       wait_delete_completed
+       wait_mds_ost_sync
+
+       $LFS setstripe -E -1 -z 64M -c 1 -p "$TESTNAME" $comp_file || \
+               error "Create $comp_file failed"
+
+       # write past end of first component, so it is extended
+       dd if=/dev/zero of=$comp_file bs=1M count=1 seek=80 conv=notrunc ||
+               error "dd to extend failed"
+
+       $LFS getstripe $comp_file
+
+       flg_opts="--comp-flags extension"
+       found=$($LFS find --comp-start 128M -E EOF $flg_opts $comp_file | wc -l)
+       [ $found -eq 1 ] || error "Write: second component not found"
+
+       # set our OST out of space
+       local ost_idx1=$($LFS getstripe -I1 -i $comp_file)
+       local wms=$(ost_watermarks_set_enospc $tfile $ost_idx1 |
+                   grep "watermarks")
+       stack_trap "ost_watermarks_clear_enospc $tfile $ost_idx1 $wms" EXIT
+
+       # This should create a repeat component on a new OST
+       dd if=/dev/zero of=$comp_file bs=1M count=1 seek=180 conv=notrunc ||
+               error "dd to repeat failed"
+
+       $LFS getstripe $comp_file
+
+       local comp_cnt=$($LFS getstripe --component-count $comp_file)
+       [ $comp_cnt -ne 3 ] && error "component count: $comp_cnt, should be 3"
+
+       # New second component should be on a different OST
+       local ost_idx2=$($LFS getstripe --comp-start=128m \
+                        --comp-end=192m --comp-flags=init -i $comp_file)
+
+       [ $ost_idx1 -eq $ost_idx2 ] && error "2nd comp: same OST $ost_idx1"
+
+       local wms2=$(ost_watermarks_set_enospc $tfile $ost_idx2 |
+                    grep "watermarks")
+       stack_trap "ost_watermarks_clear_enospc $tfile $ost_idx2 $wms2" EXIT
+
+       # now that the second OST is out of space (as is the first OST), we
+       # attempt to extend.  This should result in an extension of the
+       # existing component, rather than a new component.
+       dd if=/dev/zero of=$comp_file bs=1M count=1 seek=240 conv=notrunc ||
+               error "dd for forced extension failed"
+
+       $LFS getstripe $comp_file
+
+       # clear out of space on first OST
+       ost_watermarks_clear_enospc $tfile $ost_idx1 $wms
+
+       # finally, now that the first OST has space again, we attempt to
+       # extend one last time.  This should create a new component on the
+       # first OST
+       dd if=/dev/zero of=$comp_file bs=1M count=1 seek=300 conv=notrunc ||
+               error "dd for repeat on first OST failed"
+
+       $LFS getstripe $comp_file
+
+       flg_opts="--comp-flags init"
+       found=$($LFS find --comp-start 128M $flg_opts $comp_file | wc -l)
+       [ $found -eq 1 ] || error "Write: second component not found"
+
+       flg_opts="--comp-flags init"
+       found=$($LFS find --comp-start 256M $flg_opts $comp_file | wc -l)
+       [ $found -eq 1 ] || error "Write: third component not found"
+
+       flg_opts="--comp-flags extension"
+       found=$($LFS find --comp-start 320M -E EOF $flg_opts $comp_file | wc -l)
+       [ $found -eq 1 ] || error "Write: extension component not found"
+
+       sel_layout_sanity $comp_file 4
+}
+run_test 22c "Test repeat with out of space on > 1 OST"
+
+test_22d_post_check() {
+       local comp_file=$1
+       local name=$2
+       local flg_opts="--comp-flags init"
+       local found=$($LFS find --comp-start 0M -E 128M $flg_opts $comp_file |
+                     wc -l)
+       [ $found -eq 1 ] || {
+               $LFS getstripe $comp_file
+               error "$name: second component not found"
+       }
+
+       flg_opts="--comp-flags extension"
+       found=$($LFS find --comp-start 128M -E EOF $flg_opts $comp_file | wc -l)
+       [ $found -eq 1 ] || error "$name: third extension component not found"
+
+       sel_layout_sanity $comp_file 2
+}
+
+test_22d_pre() {
+       local comp_file=$1
+       local wms="$2"
+       local RC
+
+       # write past end of first component
+       dd if=/dev/zero of=$comp_file bs=1M count=1 seek=70
+       RC=$?
+
+       ost_watermarks_clear_enospc $tfile 0 $wms
+       [ $RC -eq 0 ] || error "dd to force extend failed"
+
+       test_22d_post_check $comp_file "Write"
+}
+
+test_22d() {
+       [ $OSTCOUNT -lt 2 ] && skip "needs >= 2 OSTs" && return
+       [ $(lustre_version_code $SINGLEMDS) -lt $(version_code $SEL_VER) ] &&
+               skip "skipped for lustre < $SEL_VER"
+
+       local comp_file=$DIR/$tdir/$tfile
+       local flg_opts=""
+       local found=""
+
+       test_mkdir -p $DIR/$tdir
+
+       # without this, a previous delete can finish after we check free space
+       wait_delete_completed
+       wait_mds_ost_sync
+
+       # Pool allows us to force use of only certain OSTs
+       pool_add $TESTNAME || error "Pool creation failed"
+       pool_add_targets $TESTNAME 0 || error "Pool add targets failed"
+
+       # 1. Fail to extend due to OOS, try to repeat within the same pool,
+       # fail to stripe (again OOS) the 0-length component, remove the
+       # repeated one, force the extension on the original one.
+       $LFS setstripe -E -1 -p $TESTNAME -z 64M $comp_file ||
+               error "Create $comp_file failed"
+
+       replay_barrier $SINGLEMDS
+
+       # set our OST out of space
+       local wms=$(ost_watermarks_set_enospc $tfile 0 | grep "watermarks")
+
+       test_22d_pre $comp_file "$wms"
+       fail $SINGLEMDS
+       test_22d_post_check $comp_file "Failover"
+
+       # 2. repeat with low on space: 0-length repeated component will be
+       # striped, but still fails to be extended; otherwise the same as (1).
+       rm -f $comp_file
+       $LFS setstripe -E -1 -p $TESTNAME -z 64M $comp_file ||
+               error "Create $comp_file failed"
+
+       replay_barrier $SINGLEMDS
+
+       # set our OST low on space
+       local wms=$(ost_watermarks_set_low_space 0 | grep "watermarks")
+
+       test_22d_pre $comp_file "$wms"
+       fail $SINGLEMDS
+       test_22d_post_check $comp_file "Failover"
+}
+run_test 22d "out of/low on space + failed to repeat + forced extension"
+
 test_23a() {
        [ $OSTCOUNT -lt 2 ] && skip "needs >= 2 OSTs" && return
        [ $(lustre_version_code $SINGLEMDS) -lt $(version_code $SEL_VER) ] &&
@@ -1691,6 +1997,37 @@ test_23e() {
 }
 run_test 23e "Append with next real comp: spillover and backward extension"
 
+test_23f() {
+       [ $OSTCOUNT -lt 2 ] && skip "needs >= 2 OSTs" && return
+       [ $(lustre_version_code $SINGLEMDS) -lt $(version_code $SEL_VER) ] &&
+               skip "skipped for lustre < $SEL_VER"
+
+       local comp_file=$DIR/$tdir/$tfile
+       test_mkdir -p $DIR/$tdir
+
+       $LFS setstripe -z 64M -c 1 -E -1 $comp_file ||
+               error "Create $comp_file failed"
+
+       local ost_idx=$($LFS getstripe -I1 -i $comp_file)
+       local wms=$(ost_watermarks_set_low_space $ost_idx | grep "watermarks")
+
+       dd if=/dev/zero bs=1M oflag=append count=1 of=$comp_file
+       RC=$?
+
+       ost_watermarks_clear_enospc $tfile $ost_idx $wms
+       [ $RC -eq 0 ] || error "dd append failed"
+
+       local flg_opts="--comp-start 64M -E EOF --comp-flags init"
+       local found=$($LFS find $flg_opts $comp_file | wc -l)
+       [ $found -eq 1 ] || error "Append: component (64M-EOF) not found"
+
+       ost_idx=$($LFS getstripe -I2 -i $comp_file)
+       [ "$ost_idx" != "" ] && error "Append: extension component still exists"
+
+       sel_layout_sanity $comp_file 2
+}
+run_test 23f "Append with low on space: repeat and remove EXT comp"
+
 complete $SECONDS
 check_and_cleanup_lustre
 exit_status