WHATS_NEW | 2 + lib/metadata/raid_manip.c | 277 +++++++++++++++++++++++++++++++--- test/shell/lvconvert-raid-takeover.sh | 53 ++++++- tools/lvconvert.c | 24 +-- 4 files changed, 314 insertions(+), 42 deletions(-) diff --git a/WHATS_NEW b/WHATS_NEW index 5cbf4ec..6a0c311 100644 --- a/WHATS_NEW +++ b/WHATS_NEW @@ -1,5 +1,7 @@ Version 2.02.167 - ====================================== + Add direct striped -> raid4 conversion + Fix raid4 parity image pair position on conversions from striped/raid0* Disable lvconvert of thin pool to raid while active. Version 2.02.166 - 26th September 2016 diff --git a/lib/metadata/raid_manip.c b/lib/metadata/raid_manip.c index 5fc520e..e5fd195 100644 --- a/lib/metadata/raid_manip.c +++ b/lib/metadata/raid_manip.c @@ -2459,7 +2459,7 @@ static struct lv_segment *_convert_striped_to_raid0(struct logical_volume *lv, 0 /* chunk_size */, 0 /* seg->region_size */, 0u /* extents_copied */ , NULL /* pvmove_source_seg */))) { - log_error("Failed to allocate new raid0 segement for LV %s.", display_lvname(lv)); + log_error("Failed to allocate new raid0 segment for LV %s.", display_lvname(lv)); return NULL; } @@ -2519,42 +2519,51 @@ static struct possible_takeover_reshape_type _possible_takeover_reshape_types[] { .current_types = SEG_STRIPED_TARGET, /* linear, i.e. seg->area_count = 1 */ .possible_types = SEG_RAID1, .current_areas = 1, - .options = ALLOW_NONE }, + .options = ALLOW_NONE }, /* FIXME: ALLOW_REGION_SIZE */ { .current_types = SEG_STRIPED_TARGET, /* linear, i.e. seg->area_count = 1 */ .possible_types = SEG_RAID0|SEG_RAID0_META, .current_areas = 1, .options = ALLOW_STRIPE_SIZE }, - { .current_types = SEG_STRIPED_TARGET, /* striped, i.e. seg->area_count > 1 */ + { .current_types = SEG_STRIPED_TARGET, /* striped -> raid0*, i.e. seg->area_count > 1 */ .possible_types = SEG_RAID0|SEG_RAID0_META, .current_areas = ~0U, .options = ALLOW_NONE }, + { .current_types = SEG_STRIPED_TARGET, /* striped -> raid4 , i.e. seg->area_count > 1 */ + .possible_types = SEG_RAID4, + .current_areas = ~0U, + .options = ALLOW_NONE }, /* FIXME: ALLOW_REGION_SIZE */ /* raid0* -> */ { .current_types = SEG_RAID0|SEG_RAID0_META, /* seg->area_count = 1 */ .possible_types = SEG_RAID1, .current_areas = 1, + .options = ALLOW_NONE }, /* FIXME: ALLOW_REGION_SIZE */ + { .current_types = SEG_RAID0|SEG_RAID0_META, /* raid0* -> striped, i.e. seg->area_count > 1 */ + .possible_types = SEG_STRIPED_TARGET, + .current_areas = ~0U, .options = ALLOW_NONE }, - { .current_types = SEG_RAID0|SEG_RAID0_META, /* seg->area_count > 1 */ - .possible_types = SEG_RAID4, + { .current_types = SEG_RAID0|SEG_RAID0_META, /* raid0* -> raid0*, i.e. seg->area_count > 1 */ + .possible_types = SEG_RAID0_META|SEG_RAID0, .current_areas = ~0U, .options = ALLOW_NONE }, - { .current_types = SEG_RAID0|SEG_RAID0_META, /* raid0 striped, i.e. seg->area_count > 0 */ + { .current_types = SEG_RAID0|SEG_RAID0_META, /* raid0* -> raid4, i.e. seg->area_count > 1 */ + .possible_types = SEG_RAID4, + .current_areas = ~0U, + .options = ALLOW_NONE }, /* FIXME: ALLOW_REGION_SIZE */ + /* raid4 -> -> */ + { .current_types = SEG_RAID4, /* raid4 ->striped/raid0*, i.e. seg->area_count > 1 */ .possible_types = SEG_STRIPED_TARGET|SEG_RAID0|SEG_RAID0_META, .current_areas = ~0U, .options = ALLOW_NONE }, - /* raid1 -> */ + /* raid1 -> mirror */ { .current_types = SEG_RAID1, - .possible_types = SEG_RAID1|SEG_MIRROR, + .possible_types = SEG_MIRROR, .current_areas = ~0U, - .options = ALLOW_NONE }, + .options = ALLOW_NONE }, /* FIXME: ALLOW_REGION_SIZE */ /* mirror -> raid1 with arbitrary number of legs */ { .current_types = SEG_MIRROR, - .possible_types = SEG_MIRROR|SEG_RAID1, - .current_areas = ~0U, - .options = ALLOW_NONE }, - { .current_types = SEG_RAID4, - .possible_types = SEG_STRIPED_TARGET|SEG_RAID0|SEG_RAID0_META, + .possible_types = SEG_RAID1, .current_areas = ~0U, - .options = ALLOW_NONE }, + .options = ALLOW_NONE }, /* FIXME: ALLOW_REGION_SIZE */ /* END */ { .current_types = 0 } @@ -2861,9 +2870,176 @@ static int _raid1_to_mirrored_wrapper(TAKEOVER_FN_ARGS) allocate_pvs, 1, &removal_lvs); } +/* + * HM Helper: (raid0_meta -> raid4) + * + * To convert raid0_meta to raid4, which involves shifting the + * parity device to lv segment area 0 and thus changing MD + * array roles, detach the MetaLVs and reload as raid0 in + * order to wipe them then reattach and set back to raid0_meta. + */ +static int _clear_meta_lvs(struct logical_volume *lv) +{ + uint32_t s; + struct lv_segment *seg = first_seg(lv); + struct lv_segment_area *tmp_areas; + const struct segment_type *tmp_segtype; + struct dm_list meta_lvs; + struct lv_list *lvl_array, *lvl; + + /* Reject non-raid0_meta segment types cautiously */ + if (!seg_is_raid0_meta(seg) || + !seg->meta_areas) + return_0; + + if (!(lvl_array = dm_pool_alloc(lv->vg->vgmem, seg->area_count * sizeof(*lvl_array)))) + return_0; + + dm_list_init(&meta_lvs); + tmp_areas = seg->meta_areas; + + /* Extract all MetaLVs listing them on @meta_lvs */ + log_debug_metadata("Extracting all MetaLVs of %s to activate as raid0", + display_lvname(lv)); + if (!_extract_image_component_sublist(seg, RAID_META, 0, seg->area_count, &meta_lvs, 0)) + return_0; + + /* Memorize meta areas and segtype to set again after initializing. */ + seg->meta_areas = NULL; + tmp_segtype = seg->segtype; + + if (!(seg->segtype = get_segtype_from_flag(lv->vg->cmd, SEG_RAID0)) || + !lv_update_and_reload(lv)) + return_0; + + /* + * Now deactivate the MetaLVs before clearing, so + * that _clear_lvs() will activate them visible. + */ + log_debug_metadata("Deactivating pulled out MetaLVs of %s before initializing.", + display_lvname(lv)); + dm_list_iterate_items(lvl, &meta_lvs) + if (!deactivate_lv(lv->vg->cmd, lvl->lv)) + return_0; + + log_debug_metadata("Clearing allocated raid0_meta metadata LVs for conversion to raid4"); + if (!_clear_lvs(&meta_lvs)) { + log_error("Failed to initialize metadata LVs"); + return 0; + } + + /* Set memorized meta areas and raid0_meta segtype */ + seg->meta_areas = tmp_areas; + seg->segtype = tmp_segtype; + + log_debug_metadata("Adding metadata LVs back into %s", display_lvname(lv)); + s = 0; + dm_list_iterate_items(lvl, &meta_lvs) { + lv_set_hidden(lvl->lv); + if (!set_lv_segment_area_lv(seg, s++, lvl->lv, 0, RAID_META)) + return 0; + } + + return 1; +} + +/* + * HM Helper: (raid0* <-> raid4) + * + * Rename SubLVs (pairs) allowing to shift names w/o collisions with active ones. + */ +#define SLV_COUNT 2 +static int _rename_area_lvs(struct logical_volume *lv, const char *suffix) +{ + uint32_t s; + size_t sz = strlen("rimage") + (suffix ? strlen(suffix) : 0) + 1; + char *sfx[SLV_COUNT] = { NULL, NULL }; + struct lv_segment *seg = first_seg(lv); + + /* Create _generate_raid_name() suffixes w/ or w/o passed in @suffix */ + for (s = 0; s < SLV_COUNT; s++) + if (!(sfx[s] = dm_pool_alloc(lv->vg->cmd->mem, sz)) || + dm_snprintf(sfx[s], sz, suffix ? "%s%s" : "%s", s ? "rmeta" : "rimage", suffix) < 0) + return_0; + + /* Change names (temporarily) to be able to shift numerical name suffixes */ + for (s = 0; s < seg->area_count; s++) { + if (!(seg_lv(seg, s)->name = _generate_raid_name(lv, sfx[0], s))) + return_0; + if (seg->meta_areas && + !(seg_metalv(seg, s)->name = _generate_raid_name(lv, sfx[1], s))) + return_0; + } + + for (s = 0; s < SLV_COUNT; s++) + dm_pool_free(lv->vg->cmd->mem, sfx[s]); + + return 1; +} + +/* + * HM Helper: (raid0* <-> raid4) + * + * Switch area LVs in lv segment @seg indexed by @s1 and @s2 + */ +static void _switch_area_lvs(struct lv_segment *seg, uint32_t s1, uint32_t s2) +{ + struct logical_volume *lvt; + + lvt = seg_lv(seg, s1); + seg_lv(seg, s1) = seg_lv(seg, s2); + seg_lv(seg, s2) = lvt; + + /* Be cautious */ + if (seg->meta_areas) { + lvt = seg_metalv(seg, s1); + seg_metalv(seg, s1) = seg_metalv(seg, s2); + seg_metalv(seg, s2) = lvt; + } +} + +/* + * HM Helper: + * + * shift range of area LVs in @seg in range [ @s1, @s2 ] up if @s1 < @s2, + * else down bubbling the parity SubLVs up/down whilst shifting. + */ +static void _shift_area_lvs(struct lv_segment *seg, uint32_t s1, uint32_t s2) +{ + uint32_t s; + + if (s1 < s2) + /* Forward shift n+1 -> n */ + for (s = s1; s < s2; s++) + _switch_area_lvs(seg, s, s + 1); + else + /* Reverse shift n-1 -> n */ + for (s = s1; s > s2; s--) + _switch_area_lvs(seg, s, s - 1); +} + +/* + * Switch position of first and last area lv within + * @lv to move parity SubLVs from end to end. + * + * Direction depends on segment type raid4 / raid0_meta. + */ +static int _shift_parity_dev(struct lv_segment *seg) +{ + if (seg_is_raid0_meta(seg)) + _shift_area_lvs(seg, seg->area_count - 1, 0); + else if (seg_is_raid4(seg)) + _shift_area_lvs(seg, 0, seg->area_count - 1); + else + return 0; + + return 1; +} + /* raid45 -> raid0* / striped */ static int _raid456_to_raid0_or_striped_wrapper(TAKEOVER_FN_ARGS) { + int rename_sublvs = 0; struct lv_segment *seg = first_seg(lv); struct dm_list removal_lvs; @@ -2879,10 +3055,39 @@ static int _raid456_to_raid0_or_striped_wrapper(TAKEOVER_FN_ARGS) if (!_raid_in_sync(lv)) return 0; + if (!yes && yes_no_prompt("Are you sure you want to convert \"%s\" LV %s to \"%s\" " + "type using all resilience? [y/n]: ", + lvseg_name(seg), display_lvname(lv), new_segtype->name) == 'n') { + log_error("Logical volume %s NOT converted to \"%s\"", + display_lvname(lv), new_segtype->name); + return 0; + } + if (sigint_caught()) + return_0; + /* Archive metadata */ if (!archive(lv->vg)) return_0; + /* + * raid4 (which actually gets mapped to raid5/dedicated first parity disk) + * needs shifting of SubLVs to move the parity SubLV pair in the first area + * to the last one before conversion to raid0[_meta]/striped to allow for + * SubLV removal from the end of the areas arrays. + */ + if (seg_is_raid4(seg)) { + /* Shift parity SubLV pair "PDD..." -> "DD...P" to be able to remove it off the end */ + if (!_shift_parity_dev(seg)) + return 0; + + if (segtype_is_any_raid0(new_segtype) && + !(rename_sublvs = _rename_area_lvs(lv, "_"))) { + log_error("Failed to rename %s LV %s MetaLVs", lvseg_name(seg), display_lvname(lv)); + return 0; + } + + } + /* Remove meta and data LVs requested */ if (!_lv_raid_change_image_count(lv, new_image_count, allocate_pvs, &removal_lvs, 0, 0)) return 0; @@ -2902,7 +3107,19 @@ static int _raid456_to_raid0_or_striped_wrapper(TAKEOVER_FN_ARGS) seg->region_size = 0; - return _lv_update_reload_fns_reset_eliminate_lvs(lv, &removal_lvs); + if (!_lv_update_reload_fns_reset_eliminate_lvs(lv, &removal_lvs)) + return_0; + + if (rename_sublvs) { + if (!_rename_area_lvs(lv, NULL)) { + log_error("Failed to rename %s LV %s MetaLVs", lvseg_name(seg), display_lvname(lv)); + return 0; + } + if (!lv_update_and_reload(lv)) + return_0; + } + + return 1; } static int _striped_to_raid0_wrapper(struct logical_volume *lv, @@ -2930,6 +3147,9 @@ static int _striped_to_raid0_wrapper(struct logical_volume *lv, static int _striped_or_raid0_to_raid45610_wrapper(TAKEOVER_FN_ARGS) { struct lv_segment *seg = first_seg(lv); + struct dm_list removal_lvs; + + dm_list_init(&removal_lvs); if (seg_is_raid10(seg)) return _takeover_unsupported_yet(lv, new_stripes, new_segtype); @@ -2944,6 +3164,13 @@ static int _striped_or_raid0_to_raid45610_wrapper(TAKEOVER_FN_ARGS) return 0; } + /* FIXME: restricted to raid4 for the time being... */ + if (!segtype_is_raid4(new_segtype)) { + /* Can't convert striped/raid0* to e.g. raid10_offset */ + log_error("Can't convert %s to %s", display_lvname(lv), new_segtype->name); + return 0; + } + /* Archive metadata */ if (!archive(lv->vg)) return_0; @@ -2961,7 +3188,10 @@ static int _striped_or_raid0_to_raid45610_wrapper(TAKEOVER_FN_ARGS) log_debug_metadata("Adding metadata LVs to %s", display_lvname(lv)); if (!_raid0_add_or_remove_metadata_lvs(lv, 1 /* update_and_reload */, allocate_pvs, NULL)) return 0; - } + /* raid0_meta -> raid4 needs clearing of MetaLVs in order to avoid raid disk role cahnge issues in the kernel */ + } else if (segtype_is_raid4(new_segtype) && + !_clear_meta_lvs(lv)) + return 0; /* Add the additional component LV pairs */ log_debug_metadata("Adding %" PRIu32 " component LV pair(s) to %s", new_image_count - lv_raid_image_count(lv), @@ -2969,8 +3199,9 @@ static int _striped_or_raid0_to_raid45610_wrapper(TAKEOVER_FN_ARGS) if (!_lv_raid_change_image_count(lv, new_image_count, allocate_pvs, NULL, 0, 1)) return 0; - if (!segtype_is_raid4(new_segtype)) { - /* Can't convert striped/raid0* to e.g. raid10_offset */ + if (segtype_is_raid4(new_segtype) && + (!_shift_parity_dev(seg) || + !_rename_area_lvs(lv, "_"))) { log_error("Can't convert %s to %s", display_lvname(lv), new_segtype->name); return 0; } @@ -2987,6 +3218,14 @@ static int _striped_or_raid0_to_raid45610_wrapper(TAKEOVER_FN_ARGS) if (!_lv_update_reload_fns_reset_eliminate_lvs(lv, NULL)) return_0; + if (segtype_is_raid4(new_segtype)) { + /* We had to rename SubLVs because of collision free sgifting, rename back... */ + if (!_rename_area_lvs(lv, NULL)) + return 0; + if (!lv_update_and_reload(lv)) + return_0; + } + return 1; } diff --git a/test/shell/lvconvert-raid-takeover.sh b/test/shell/lvconvert-raid-takeover.sh index 19a65d3..0140e22 100644 --- a/test/shell/lvconvert-raid-takeover.sh +++ b/test/shell/lvconvert-raid-takeover.sh @@ -78,22 +78,58 @@ aux wait_for_sync $vg $lv1 # Clean up lvremove --yes $vg/$lv1 -# Create 3-way raid0 -lvcreate -y -aey --type raid0 -i 3 -L 64M -n $lv1 $vg -check lv_field $vg/$lv1 segtype "raid0" +# Create 3-way striped +lvcreate -y -aey --type striped -i 3 -L 64M -n $lv1 $vg +check lv_field $vg/$lv1 segtype "striped" check lv_field $vg/$lv1 stripes 3 echo y | mkfs -t ext4 /dev/mapper/$vg-$lv1 fsck -fn /dev/mapper/$vg-$lv1 -# Convert raid0 -> raid4 +# Create 3-way raid0 +lvcreate -y -aey --type raid0 -i 3 -L 64M -n $lv2 $vg +check lv_field $vg/$lv2 segtype "raid0" +check lv_field $vg/$lv2 stripes 3 +echo y | mkfs -t ext4 /dev/mapper/$vg-$lv2 +fsck -fn /dev/mapper/$vg-$lv2 + +# Create 3-way raid0_meta +lvcreate -y -aey --type raid0_meta -i 3 -L 64M -n $lv3 $vg +check lv_field $vg/$lv3 segtype "raid0_meta" +check lv_field $vg/$lv3 stripes 3 +echo y | mkfs -t ext4 /dev/mapper/$vg-$lv3 +fsck -fn /dev/mapper/$vg-$lv3 + +# Create 3-way raid4 +lvcreate -y -aey --type raid4 -i 3 -L 64M -n $lv4 $vg +check lv_field $vg/$lv4 segtype "raid4" +check lv_field $vg/$lv4 stripes 4 +echo y | mkfs -t ext4 /dev/mapper/$vg-$lv4 +fsck -fn /dev/mapper/$vg-$lv4 +aux wait_for_sync $vg $lv4 +fsck -fn /dev/mapper/$vg-$lv4 + +# Convert raid4 -> striped (correct raid4 mapping test!) +lvconvert -y --ty striped $vg/$lv4 +check lv_field $vg/$lv4 segtype "striped" +check lv_field $vg/$lv4 stripes 3 +fsck -fn /dev/mapper/$vg-$lv4 + +# Convert striped -> raid4 lvconvert -y --ty raid4 $vg/$lv1 -lvchange --refresh $vg/$lv1 check lv_field $vg/$lv1 segtype "raid4" check lv_field $vg/$lv1 stripes 4 fsck -fn /dev/mapper/$vg-$lv1 aux wait_for_sync $vg $lv1 fsck -fn /dev/mapper/$vg-$lv1 +# Convert raid0 -> raid4 +lvconvert -y --ty raid4 $vg/$lv2 +check lv_field $vg/$lv2 segtype "raid4" +check lv_field $vg/$lv2 stripes 4 +fsck -fn /dev/mapper/$vg-$lv2 +aux wait_for_sync $vg $lv2 +fsck -fn /dev/mapper/$vg-$lv2 + # Convert raid4 -> raid0_meta lvconvert -y --ty raid0_meta $vg/$lv1 check lv_field $vg/$lv1 segtype "raid0_meta" @@ -116,11 +152,16 @@ fsck -fn /dev/mapper/$vg-$lv1 # Convert raid0 -> raid4 lvconvert -y --ty raid4 $vg/$lv1 -lvchange --refresh $vg/$lv1 check lv_field $vg/$lv1 segtype "raid4" check lv_field $vg/$lv1 stripes 4 fsck -fn /dev/mapper/$vg-$lv1 aux wait_for_sync $vg $lv1 fsck -fn /dev/mapper/$vg-$lv1 +# Convert raid4 -> striped +lvconvert -y --ty striped $vg/$lv1 +check lv_field $vg/$lv1 segtype "striped" +check lv_field $vg/$lv1 stripes 3 +fsck -fn /dev/mapper/$vg-$lv1 + vgremove -ff $vg diff --git a/tools/lvconvert.c b/tools/lvconvert.c index 0d2a4d1..541df72 100644 --- a/tools/lvconvert.c +++ b/tools/lvconvert.c @@ -1931,7 +1931,7 @@ static int _lvconvert_raid(struct logical_volume *lv, struct lvconvert_params *l return 1; } goto try_new_takeover_or_reshape; - } else if (!lp->repair && !lp->replace && (!*lp->type_str || seg->segtype == lp->segtype)) { + } else if (!lp->repair && !lp->replace && !*lp->type_str) { log_error("Conversion operation not yet supported."); return 0; } @@ -2017,28 +2017,18 @@ static int _lvconvert_raid(struct logical_volume *lv, struct lvconvert_params *l return 1; } - try_new_takeover_or_reshape: - /* FIXME This needs changing globally. */ if (!arg_is_set(cmd, stripes_long_ARG)) lp->stripes = 0; - /* Only let raid4 through for now. */ - if (lp->type_str && lp->type_str[0] && lp->segtype != seg->segtype && - ((seg_is_raid4(seg) && seg_is_striped(lp) && lp->stripes > 1) || - (seg_is_striped(seg) && seg->area_count > 1 && seg_is_raid4(lp)))) { - if (!lv_raid_convert(lv, lp->segtype, lp->yes, lp->force, lp->stripes, lp->stripe_size_supplied, lp->stripe_size, - lp->region_size, lp->pvh)) - return_0; - - log_print_unless_silent("Logical volume %s successfully converted.", - display_lvname(lv)); - return 1; - } + if (!lv_raid_convert(lv, lp->segtype, lp->yes, lp->force, lp->stripes, lp->stripe_size_supplied, lp->stripe_size, + lp->region_size, lp->pvh)) + return_0; - log_error("Conversion operation not yet supported."); - return 0; + log_print_unless_silent("Logical volume %s successfully converted.", + display_lvname(lv)); + return 1; } static int _lvconvert_splitsnapshot(struct cmd_context *cmd, struct logical_volume *cow,