mirror of
git://sourceware.org/git/lvm2.git
synced 2024-12-21 13:34:40 +03:00
lvconvert: add infrastructure for RaidLV reshaping support
In order to support striped raid5/6/10 LV reshaping (change of LV type, stripesize or number of legs), this patch introduces more local infrastructure to raid_manip.c used by followup patches. Changes: - add lv_raid_data_copies returning raid type specific number; needed for raid10 with more than 2 data copies - remove _shift_and_rename_image_components() constraint to support more than 10 raid legs - add function to calculate total rimage length used by out-of-place reshape space allocation - add out-of-place reshape space alloc/relocate/free functions - move _data_rimages_count() used by reshape space alloc/realocate functions Related: rhbz834579 Related: rhbz1191935 Related: rhbz1191978
This commit is contained in:
parent
c1865b0a86
commit
92691e345d
@ -957,7 +957,7 @@ struct lv_segment *alloc_lv_segment(const struct segment_type *segtype,
|
||||
seg->stripe_size = stripe_size;
|
||||
seg->area_count = area_count;
|
||||
seg->area_len = area_len;
|
||||
seg->data_copies = data_copies ? : 0; // lv_raid_data_copies(segtype, area_count);
|
||||
seg->data_copies = data_copies ? : lv_raid_data_copies(segtype, area_count);
|
||||
seg->chunk_size = chunk_size;
|
||||
seg->region_size = region_size;
|
||||
seg->extents_copied = extents_copied;
|
||||
|
@ -708,12 +708,11 @@ static int _reorder_raid10_near_seg_areas(struct lv_segment *seg, enum raid0_rai
|
||||
*
|
||||
* Returns: 1 on success, 0 on failure
|
||||
*/
|
||||
static char *_generate_raid_name(struct logical_volume *lv,
|
||||
const char *suffix, int count);
|
||||
static int _shift_and_rename_image_components(struct lv_segment *seg)
|
||||
{
|
||||
int len;
|
||||
char *shift_name;
|
||||
uint32_t s, missing;
|
||||
struct cmd_context *cmd = seg->lv->vg->cmd;
|
||||
|
||||
/*
|
||||
* All LVs must be properly named for their index before
|
||||
@ -724,21 +723,12 @@ static int _shift_and_rename_image_components(struct lv_segment *seg)
|
||||
if (!seg_is_raid(seg))
|
||||
return_0;
|
||||
|
||||
if (seg->area_count > 10) {
|
||||
/*
|
||||
* FIXME: Handling more would mean I'd have
|
||||
* to handle double digits
|
||||
*/
|
||||
log_error("Unable handle arrays with more than 10 devices.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
log_very_verbose("Shifting images in %s.", display_lvname(seg->lv));
|
||||
|
||||
for (s = 0, missing = 0; s < seg->area_count; s++) {
|
||||
if (seg_type(seg, s) == AREA_UNASSIGNED) {
|
||||
if (seg_metatype(seg, s) != AREA_UNASSIGNED) {
|
||||
log_error(INTERNAL_ERROR "Metadata segment area"
|
||||
log_error(INTERNAL_ERROR "Metadata segment area."
|
||||
" #%d should be AREA_UNASSIGNED.", s);
|
||||
return 0;
|
||||
}
|
||||
@ -753,24 +743,16 @@ static int _shift_and_rename_image_components(struct lv_segment *seg)
|
||||
display_lvname(seg_lv(seg, s)), missing);
|
||||
|
||||
/* Alter rmeta name */
|
||||
shift_name = dm_pool_strdup(cmd->mem, seg_metalv(seg, s)->name);
|
||||
if (!shift_name) {
|
||||
if (!(seg_metalv(seg, s)->name = _generate_raid_name(seg->lv, "rmeta", s - missing))) {
|
||||
log_error("Memory allocation failed.");
|
||||
return 0;
|
||||
}
|
||||
len = strlen(shift_name) - 1;
|
||||
shift_name[len] -= missing;
|
||||
seg_metalv(seg, s)->name = shift_name;
|
||||
|
||||
/* Alter rimage name */
|
||||
shift_name = dm_pool_strdup(cmd->mem, seg_lv(seg, s)->name);
|
||||
if (!shift_name) {
|
||||
if (!(seg_lv(seg, s)->name = _generate_raid_name(seg->lv, "rimage", s - missing))) {
|
||||
log_error("Memory allocation failed.");
|
||||
return 0;
|
||||
}
|
||||
len = strlen(shift_name) - 1;
|
||||
shift_name[len] -= missing;
|
||||
seg_lv(seg, s)->name = shift_name;
|
||||
|
||||
seg->areas[s - missing] = seg->areas[s];
|
||||
seg->meta_areas[s - missing] = seg->meta_areas[s];
|
||||
@ -1030,6 +1012,442 @@ uint32_t raid_rimage_extents(const struct segment_type *segtype,
|
||||
return r > UINT_MAX ? 0 : (uint32_t) r;
|
||||
}
|
||||
|
||||
/* Return number of data copies for @segtype */
|
||||
uint32_t lv_raid_data_copies(const struct segment_type *segtype, uint32_t area_count)
|
||||
{
|
||||
if (segtype_is_any_raid10(segtype))
|
||||
/* FIXME: change for variable number of data copies */
|
||||
return 2;
|
||||
else if (segtype_is_mirrored(segtype))
|
||||
return area_count;
|
||||
else if (segtype_is_striped_raid(segtype))
|
||||
return segtype->parity_devs + 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* Return data images count for @total_rimages depending on @seg's type */
|
||||
static uint32_t _data_rimages_count(const struct lv_segment *seg, const uint32_t total_rimages)
|
||||
{
|
||||
if (!seg_is_thin(seg) && total_rimages <= seg->segtype->parity_devs)
|
||||
return_0;
|
||||
|
||||
return total_rimages - seg->segtype->parity_devs;
|
||||
}
|
||||
|
||||
/* Get total area len of @lv, i.e. sum of area_len of all segments */
|
||||
static uint32_t _lv_total_rimage_len(struct logical_volume *lv)
|
||||
{
|
||||
uint32_t s;
|
||||
struct lv_segment *seg = first_seg(lv);
|
||||
|
||||
if (seg_is_raid(seg)) {
|
||||
for (s = 0; s < seg->area_count; s++)
|
||||
if (seg_lv(seg, s))
|
||||
return seg_lv(seg, s)->le_count;
|
||||
} else
|
||||
return lv->le_count;
|
||||
|
||||
return_0;
|
||||
}
|
||||
|
||||
/*
|
||||
* HM helper:
|
||||
*
|
||||
* Compare the raid levels in segtype @t1 and @t2
|
||||
*
|
||||
* Return 1 if same, else 0
|
||||
*/
|
||||
static int _cmp_level(const struct segment_type *t1, const struct segment_type *t2)
|
||||
{
|
||||
if ((segtype_is_any_raid10(t1) && !segtype_is_any_raid10(t2)) ||
|
||||
(!segtype_is_any_raid10(t1) && segtype_is_any_raid10(t2)))
|
||||
return 0;
|
||||
|
||||
return !strncmp(t1->name, t2->name, 5);
|
||||
}
|
||||
|
||||
/*
|
||||
* HM Helper:
|
||||
*
|
||||
* Check for same raid levels in segtype @t1 and @t2
|
||||
*
|
||||
* Return 1 if same, else != 1
|
||||
*/
|
||||
__attribute__ ((__unused__))
|
||||
static int is_same_level(const struct segment_type *t1, const struct segment_type *t2)
|
||||
{
|
||||
return _cmp_level(t1, t2);
|
||||
}
|
||||
|
||||
/* Return # of reshape LEs per device for @seg */
|
||||
static uint32_t _reshape_len_per_dev(struct lv_segment *seg)
|
||||
{
|
||||
return seg->reshape_len;
|
||||
}
|
||||
|
||||
/* Return # of reshape LEs per @lv (sum of all sub LVs reshape LEs) */
|
||||
static uint32_t _reshape_len_per_lv(struct logical_volume *lv)
|
||||
{
|
||||
struct lv_segment *seg = first_seg(lv);
|
||||
|
||||
return _reshape_len_per_dev(seg) * _data_rimages_count(seg, seg->area_count);
|
||||
}
|
||||
|
||||
/*
|
||||
* HM Helper:
|
||||
*
|
||||
* store the allocated reshape length per data image
|
||||
* in the only segment of the top-level RAID @lv and
|
||||
* in the first segment of each sub lv.
|
||||
*/
|
||||
static int _lv_set_reshape_len(struct logical_volume *lv, uint32_t reshape_len)
|
||||
{
|
||||
uint32_t s;
|
||||
struct lv_segment *data_seg, *seg = first_seg(lv);
|
||||
|
||||
if (reshape_len >= lv->le_count - 1)
|
||||
return_0;
|
||||
|
||||
seg->reshape_len = reshape_len;
|
||||
|
||||
for (s = 0; s < seg->area_count; s++) {
|
||||
if (!seg_lv(seg, s))
|
||||
return_0;
|
||||
|
||||
reshape_len = seg->reshape_len;
|
||||
dm_list_iterate_items(data_seg, &seg_lv(seg, s)->segments) {
|
||||
data_seg->reshape_len = reshape_len;
|
||||
reshape_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* HM Helper:
|
||||
*
|
||||
* correct segments logical start extents in all sub LVs of @lv
|
||||
* after having reordered any segments in sub LVs e.g. because of
|
||||
* reshape space (re)allocation.
|
||||
*/
|
||||
static int _lv_set_image_lvs_start_les(struct logical_volume *lv)
|
||||
{
|
||||
uint32_t le, s;
|
||||
struct lv_segment *data_seg, *seg = first_seg(lv);
|
||||
|
||||
|
||||
for (s = 0; s < seg->area_count; s++) {
|
||||
if (!seg_lv(seg, s))
|
||||
return_0;
|
||||
|
||||
le = 0;
|
||||
dm_list_iterate_items(data_seg, &(seg_lv(seg, s)->segments)) {
|
||||
data_seg->reshape_len = le ? 0 : seg->reshape_len;
|
||||
data_seg->le = le;
|
||||
le += data_seg->len;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try merging rimage sub LV segments _after_ adjusting start LEs */
|
||||
for (s = 0; s < seg->area_count; s++)
|
||||
if (!lv_merge_segments(seg_lv(seg, s)))
|
||||
return_0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Relocate @out_of_place_les_per_disk from @lv's data images begin <-> end depending on @where
|
||||
*
|
||||
* @where:
|
||||
* alloc_begin: end -> begin
|
||||
* alloc_end: begin -> end
|
||||
*/
|
||||
enum alloc_where { alloc_begin, alloc_end, alloc_anywhere, alloc_none };
|
||||
static int _lv_relocate_reshape_space(struct logical_volume *lv, enum alloc_where where)
|
||||
{
|
||||
uint32_t le, begin, end, s;
|
||||
struct logical_volume *dlv;
|
||||
struct dm_list *insert;
|
||||
struct lv_segment *data_seg, *seg = first_seg(lv);
|
||||
|
||||
if (!_reshape_len_per_dev(seg))
|
||||
return_0;
|
||||
|
||||
/*
|
||||
* Move the reshape LEs of each stripe (i.e. the data image sub lv)
|
||||
* in the first/last segment(s) across to the opposite end of the
|
||||
* address space
|
||||
*/
|
||||
for (s = 0; s < seg->area_count; s++) {
|
||||
if (!(dlv = seg_lv(seg, s)))
|
||||
return_0;
|
||||
|
||||
switch (where) {
|
||||
case alloc_begin:
|
||||
/* Move to the beginning -> start moving to the beginning from "end - reshape LEs" to end */
|
||||
begin = dlv->le_count - _reshape_len_per_dev(seg);
|
||||
end = dlv->le_count;
|
||||
break;
|
||||
case alloc_end:
|
||||
/* Move to the end -> start moving to the end from 0 and end with reshape LEs */
|
||||
begin = 0;
|
||||
end = _reshape_len_per_dev(seg);
|
||||
break;
|
||||
default:
|
||||
log_error(INTERNAL_ERROR "bogus reshape space reallocation request [%d]", where);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Ensure segment boundary at begin/end of reshape space */
|
||||
if (!lv_split_segment(dlv, begin ?: end))
|
||||
return_0;
|
||||
|
||||
/* Select destination to move to (begin/end) */
|
||||
insert = begin ? dlv->segments.n : &dlv->segments;
|
||||
if (!(data_seg = find_seg_by_le(dlv, begin)))
|
||||
return_0;
|
||||
|
||||
le = begin;
|
||||
while (le < end) {
|
||||
struct dm_list *n = data_seg->list.n;
|
||||
|
||||
le += data_seg->len;
|
||||
|
||||
dm_list_move(insert, &data_seg->list);
|
||||
|
||||
/* If moving to the begin, adjust insertion point so that we don't reverse order */
|
||||
if (begin)
|
||||
insert = data_seg->list.n;
|
||||
|
||||
data_seg = dm_list_item(n, struct lv_segment);
|
||||
}
|
||||
|
||||
le = 0;
|
||||
dm_list_iterate_items(data_seg, &dlv->segments) {
|
||||
data_seg->reshape_len = le ? 0 : _reshape_len_per_dev(seg);
|
||||
data_seg->le = le;
|
||||
le += data_seg->len;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if we've got out of space reshape
|
||||
* capacity in @lv and allocate if necessary.
|
||||
*
|
||||
* We inquire the targets status interface to retrieve
|
||||
* the current data_offset and the device size and
|
||||
* compare that to the size of the component image LV
|
||||
* to tell if an extension of the LV is needed or
|
||||
* existing space can just be used,
|
||||
*
|
||||
* Three different scenarios need to be covered:
|
||||
*
|
||||
* - we have to reshape forwards
|
||||
* (true for adding disks to a raid set) ->
|
||||
* add extent to each component image upfront
|
||||
* or move an existing one at the end across;
|
||||
* kernel will set component devs data_offset to
|
||||
* the passed in one and new_data_offset to 0,
|
||||
* i.e. the data starts at offset 0 after the reshape
|
||||
*
|
||||
* - we have to reshape backwards
|
||||
* (true for removing disks form a raid set) ->
|
||||
* add extent to each component image by the end
|
||||
* or use already existing one from a previous reshape;
|
||||
* kernel will leave the data_offset of each component dev
|
||||
* at 0 and set new_data_offset to the passed in one,
|
||||
* i.e. the data will be at offset new_data_offset != 0
|
||||
* after the reshape
|
||||
*
|
||||
* - we are free to reshape either way
|
||||
* (true for layout changes keeping number of disks) ->
|
||||
* let the kernel identify free out of place reshape space
|
||||
* and select the appropriate data_offset and reshape direction
|
||||
*
|
||||
* Kernel will always be told to put data offset
|
||||
* on an extent boundary.
|
||||
* When we convert to mappings outside MD ones such as linear,
|
||||
* striped and mirror _and_ data_offset != 0, split the first segment
|
||||
* and adjust the rest to remove the reshape space.
|
||||
* If it's at the end, just lv_reduce() and set seg->reshape_len to 0.
|
||||
*
|
||||
* Does not write metadata!
|
||||
*/
|
||||
static int _lv_alloc_reshape_space(struct logical_volume *lv,
|
||||
enum alloc_where where,
|
||||
enum alloc_where *where_it_was,
|
||||
struct dm_list *allocate_pvs)
|
||||
{
|
||||
uint32_t out_of_place_les_per_disk;
|
||||
uint64_t data_offset;
|
||||
struct lv_segment *seg = first_seg(lv);
|
||||
|
||||
if (!seg->stripe_size)
|
||||
return_0;
|
||||
|
||||
/* Ensure min out-of-place reshape space 1 MiB */
|
||||
out_of_place_les_per_disk = max(2048U, (unsigned) seg->stripe_size);
|
||||
out_of_place_les_per_disk = (uint32_t) max(out_of_place_les_per_disk / (unsigned long long) lv->vg->extent_size, 1ULL);
|
||||
|
||||
/* Get data_offset and dev_sectors from the kernel */
|
||||
if (!lv_raid_data_offset(lv, &data_offset)) {
|
||||
log_error("Can't get data offset and dev size for %s from kernel.",
|
||||
display_lvname(lv));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we have reshape space allocated and it has to grow,
|
||||
* relocate it to the end in case kernel says it is at the
|
||||
* beginning in order to grow the LV.
|
||||
*/
|
||||
if (_reshape_len_per_dev(seg)) {
|
||||
if (out_of_place_les_per_disk > _reshape_len_per_dev(seg)) {
|
||||
/* Kernel says data is at data_offset > 0 -> relocate reshape space at the begin to the end */
|
||||
if (data_offset && !_lv_relocate_reshape_space(lv, alloc_end))
|
||||
return_0;
|
||||
|
||||
data_offset = 0;
|
||||
out_of_place_les_per_disk -= _reshape_len_per_dev(seg);
|
||||
} else
|
||||
out_of_place_les_per_disk = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we don't reshape space allocated extend the LV.
|
||||
*
|
||||
* first_seg(lv)->reshape_len (only segment of top level raid LV)
|
||||
* is accounting for the data rimages so that unchanged
|
||||
* lv_extend()/lv_reduce() can be used to allocate/free,
|
||||
* because seg->len etc. still holds the whole size as before
|
||||
* including the reshape space
|
||||
*/
|
||||
if (out_of_place_les_per_disk) {
|
||||
uint32_t data_rimages = _data_rimages_count(seg, seg->area_count);
|
||||
uint32_t reshape_len = out_of_place_les_per_disk * data_rimages;
|
||||
uint32_t prev_rimage_len = _lv_total_rimage_len(lv);
|
||||
uint64_t lv_size = lv->size;
|
||||
|
||||
if (!lv_extend(lv, seg->segtype, data_rimages,
|
||||
seg->stripe_size, 1, seg->region_size,
|
||||
reshape_len /* # of reshape LEs to add */,
|
||||
allocate_pvs, lv->alloc, 0)) {
|
||||
log_error("Failed to allocate out-of-place reshape space for %s.",
|
||||
display_lvname(lv));
|
||||
return 0;
|
||||
}
|
||||
|
||||
lv->size = lv_size;
|
||||
/* pay attention to lv_extend maybe having allocated more because of layout specific rounding */
|
||||
if (!_lv_set_reshape_len(lv, _lv_total_rimage_len(lv) - prev_rimage_len))
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Preset data offset in case we fail relocating reshape space below */
|
||||
seg->data_offset = 0;
|
||||
|
||||
/*
|
||||
* Handle reshape space relocation
|
||||
*/
|
||||
switch (where) {
|
||||
case alloc_begin:
|
||||
/* Kernel says data is at data_offset == 0 -> relocate reshape space at the end to the begin */
|
||||
if (!data_offset && !_lv_relocate_reshape_space(lv, where))
|
||||
return_0;
|
||||
break;
|
||||
|
||||
case alloc_end:
|
||||
/* Kernel says data is at data_offset > 0 -> relocate reshape space at the begin to the end */
|
||||
if (data_offset && !_lv_relocate_reshape_space(lv, where))
|
||||
return_0;
|
||||
break;
|
||||
|
||||
case alloc_anywhere:
|
||||
/* We don't care where the space is, kernel will just toggle data_offset accordingly */
|
||||
break;
|
||||
|
||||
default:
|
||||
log_error(INTERNAL_ERROR "Bogus reshape space allocation request.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (where_it_was)
|
||||
*where_it_was = data_offset ? alloc_begin : alloc_end;
|
||||
|
||||
/* Inform kernel about the reshape length in sectors */
|
||||
seg->data_offset = _reshape_len_per_dev(seg) * lv->vg->extent_size;
|
||||
|
||||
return _lv_set_image_lvs_start_les(lv);
|
||||
}
|
||||
|
||||
/* Remove any reshape space from the data LVs of @lv */
|
||||
static int _lv_free_reshape_space_with_status(struct logical_volume *lv, enum alloc_where *where_it_was)
|
||||
{
|
||||
uint32_t total_reshape_len;
|
||||
struct lv_segment *seg = first_seg(lv);
|
||||
|
||||
if ((total_reshape_len = _reshape_len_per_lv(lv))) {
|
||||
enum alloc_where where;
|
||||
/*
|
||||
* raid10:
|
||||
*
|
||||
* the allocator will have added times #data_copies stripes,
|
||||
* so we need to lv_reduce() less visible size.
|
||||
*/
|
||||
if (seg_is_any_raid10(seg)) {
|
||||
if (total_reshape_len % seg->data_copies)
|
||||
return_0;
|
||||
|
||||
total_reshape_len /= seg->data_copies;
|
||||
}
|
||||
|
||||
/*
|
||||
* Got reshape space on request to free it.
|
||||
*
|
||||
* If it happens to be at the beginning of
|
||||
* the data LVs, remap it to the end in order
|
||||
* to be able to free it via lv_reduce().
|
||||
*/
|
||||
if (!_lv_alloc_reshape_space(lv, alloc_end, &where, NULL))
|
||||
return_0;
|
||||
|
||||
seg->extents_copied = first_seg(lv)->area_len;
|
||||
if (!lv_reduce(lv, total_reshape_len))
|
||||
return_0;
|
||||
|
||||
seg->extents_copied = first_seg(lv)->area_len;
|
||||
|
||||
if (!_lv_set_reshape_len(lv, 0))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Only in case reshape space was freed at the beginning,
|
||||
* which is indicated by "where == alloc_begin",
|
||||
* tell kernel to adjust data_offsets on raid devices to 0.
|
||||
*
|
||||
* The special, unused value '1' for seg->data_offset will cause
|
||||
* "data_offset 0" to be emitted in the segment line.
|
||||
*/
|
||||
seg->data_offset = (where == alloc_begin) ? 1 : 0;
|
||||
|
||||
} else if (where_it_was)
|
||||
*where_it_was = alloc_none;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
__attribute__ ((__unused__))
|
||||
static int _lv_free_reshape_space(struct logical_volume *lv)
|
||||
{
|
||||
return _lv_free_reshape_space_with_status(lv, NULL);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* _alloc_rmeta_for_lv
|
||||
* @lv
|
||||
@ -4088,12 +4506,6 @@ static takeover_fn_t _get_takeover_fn(const struct lv_segment *seg, const struct
|
||||
return _takeover_fns[_segtype_ix(seg->segtype, seg->area_count)][_segtype_ix(new_segtype, new_image_count)];
|
||||
}
|
||||
|
||||
/* Number of data (not parity) rimages */
|
||||
static uint32_t _data_rimages_count(const struct lv_segment *seg, const uint32_t total_rimages)
|
||||
{
|
||||
return total_rimages - seg->segtype->parity_devs;
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine whether data_copies, stripes, stripe_size are
|
||||
* possible for conversion from seg_from to new_segtype.
|
||||
|
@ -171,7 +171,7 @@ static int _raid_text_import(struct lv_segment *seg,
|
||||
}
|
||||
|
||||
if (seg->data_copies < 2)
|
||||
seg->data_copies = 0; // lv_raid_data_copies(seg->segtype, seg->area_count);
|
||||
seg->data_copies = lv_raid_data_copies(seg->segtype, seg->area_count);
|
||||
|
||||
if (seg_is_any_raid0(seg))
|
||||
seg->area_len /= seg->area_count;
|
||||
|
Loading…
Reference in New Issue
Block a user