Add some helper functions to allow vgsplit to continue in more cases. Introduce "related volumes" concept, as defined in the comments below. --- a/lib/metadata/metadata-exported.h +++ b/lib/metadata/metadata-exported.h @@ -151,6 +151,9 @@ struct pv_segment { uint32_t lv_area; /* Index to area in LV segment */ }; +#define pvseg_is_allocated(pvseg) (pvseg->lvseg) +#define pvseg_is_free(pvseg) (!pvseg_is_allocated(pvseg)) + struct physical_volume { struct id id; struct device *dev; @@ -297,6 +300,52 @@ struct lv_list { }; /* + * A list of related volumes (PVs and LVs) is useful because LVM internals + * require: + * 1) A logical volume must be within a single volume group + * 2) A physical volume must be within a single volume group + * + * A related volume is one that is in the same volume group and related + * to another volume by nature of one of the above two rules. + * + * As an example, consider the following: + * + * VG0 + * +---------------------------+ + * | LV0 | + * | / \ | + * | / \ | + * | / \ +---------------+ + * | LV1 LV2 LV3 LV4 | + * | / \ / \ / / \ | + * | / \ / \ / / \ | + * | / \ / \ / / \ | + * | PV1 PV2 PV3 PV4 PV5 | + * +-------------------------------------------+ + * + * The list of volumes related to LV0 are: + * - PV1, PV2, PV3 + * - LV0, LV1, LV2 + * + * Similarly, the list of volumes related to PV3 is the same list. This might + * not seem as clear but is the result of applying the above two rules + * repeatedly. For instance, if we start with PV3, from rule #1, it is clear + * we must put LV3 and LV2 into the list of related volumes since we cannot + * split logical volumes across volume groups. Once LV3 and LV2 are added, + * we must then add PV2 to the list of related volumes because of the second + * rule. Continuing to apply this rule will result in the same list of + * related volumes as above. + * + * Note that LV4, PV4, and PV5 are not related to the list mentioned above, + * but are related to each other. + */ +struct related_volumes { + struct volume_group *vg; + struct list ll; /* struct lv_list */ + struct list pl; /* struct pv_list */ +}; + +/* * Utility functions */ int vg_write(struct volume_group *vg); @@ -443,6 +492,13 @@ struct logical_volume *find_lv(const struct volume_group *vg, struct physical_volume *find_pv_by_name(struct cmd_context *cmd, const char *pv_name); +void init_related_volumes(struct related_volumes *rvs, struct volume_group *vg); +void pv_add_related_volume(struct related_volumes *rvs, struct pv_list *pvl); +void lv_add_related_volume(struct related_volumes *rvs, struct lv_list *lvl); +void move_related_volumes(struct related_volumes *rvs, + struct volume_group *vg_from, + struct volume_group *vg_to); + /* Find LV segment containing given LE */ struct lv_segment *first_seg(const struct logical_volume *lv); diff --git a/lib/metadata/metadata.c b/lib/metadata/metadata.c index dc6e624..4174457 100644 --- a/lib/metadata/metadata.c +++ b/lib/metadata/metadata.c @@ -1754,6 +1754,240 @@ struct logical_volume *lv_from_lvid(struct cmd_context *cmd, const char *lvid_s, return lvl->lv; } +static void _move_pv(struct volume_group *vg_from, struct volume_group *vg_to, + struct pv_list *pvl) +{ + struct physical_volume *pv; + + list_del(&pvl->list); + list_add(&vg_to->pvs, &pvl->list); + + vg_from->pv_count--; + vg_to->pv_count++; + + pv = list_item(pvl, struct pv_list)->pv; + + vg_from->extent_count -= pv_pe_count(pv); + vg_to->extent_count += pv_pe_count(pv); + + vg_from->free_count -= pv_pe_count(pv) - pv_pe_alloc_count(pv); + vg_to->free_count += pv_pe_count(pv) - pv_pe_alloc_count(pv); +} + +static void _move_lv(struct volume_group *vg_from, struct volume_group *vg_to, + struct lv_list *lvl) +{ + struct logical_volume *lv; + + lv = lvl->lv; + + list_del(&lvl->list); + list_add(&vg_to->lvs, &lvl->list); + if (lv->status & SNAPSHOT) { + vg_from->snapshot_count--; + vg_to->snapshot_count++; + } else { + vg_from->lv_count--; + vg_to->lv_count++; + } +} + +static int find_in_pv_list(const struct list *pl, + const struct physical_volume *pv) +{ + const struct pv_list *pvl; + + list_iterate_items(pvl, pl) + if (pvl->pv == pv) + return 1; + return 0; +} + +static int find_in_lv_list(const struct list *ll, + const struct logical_volume *lv) +{ + const struct lv_list *lvl; + + list_iterate_items(lvl, ll) + if (lvl->lv == lv) + return 1; + return 0; +} + +static void add_related_pv(struct related_volumes *rvs, + const struct physical_volume *pv) +{ + struct pv_list *assoc_pv; + + assoc_pv = find_pv_in_vg(rvs->vg, pv_dev_name(pv)); + if (assoc_pv) + pv_add_related_volume(rvs, assoc_pv); +} + +static void add_related_lv(struct related_volumes *rvs, + const struct logical_volume *lv) +{ + struct lv_list *assoc_lv; + + assoc_lv = find_lv_in_vg(lv->vg, lv->name); + if (assoc_lv) + lv_add_related_volume(rvs, assoc_lv); +} + +/* + * The lv_segment is at the heart of related volumes. + * Handle special cases (lv_log), then iterate through segs_using_this_lv + * (important for stacked LVs) as well as each area, adding LVs or PVs as + * necessary. + */ +static void add_related_segment(struct related_volumes *rvs, + const struct lv_segment *lvseg) +{ + int s; + struct lv_segment *lvseg2; + + if (lvseg->lv) + add_related_lv(rvs, lvseg->lv); + if (lvseg->log_lv) + add_related_lv(rvs, lvseg->log_lv); + if (!list_empty(&lvseg->lv->segs_using_this_lv)) { + lvseg2 = get_only_segment_using_this_lv(lvseg->lv); + if (lvseg2) + add_related_segment(rvs, lvseg2); + } + + for (s = 0; s < lvseg->area_count; s++) { + if (seg_type(lvseg, s) == AREA_LV) + add_related_lv(rvs, seg_lv(lvseg, s)); + else if (seg_type(lvseg, s) == AREA_PV) + add_related_pv(rvs, seg_pv(lvseg, s)); + } +} + +/* + * Find all volumes related to logical volume 'lv'. + * Go through each lv_segment adding as necessary. + * Handle special cases such as snapshots. + */ +static void lv_find_related_volumes(struct related_volumes *rvs, + const struct logical_volume *lv) +{ + struct lv_segment *lvseg, *snap; + struct logical_volume *lv2; + struct list *snh, *snht; + + if (lv_is_origin(lv)) { + /* + * If this is an origin LV for snapshots, all snapshots are + * related. + */ + list_iterate_safe(snh, snht, &lv->snapshot_segs) { + lv2 = list_struct_base(snh, struct lv_segment, + origin_list)->cow; + add_related_lv(rvs, lv2); + } + } else if (lv_is_cow(lv)) { + /* + * If this is a snapshot, the origin is related. + */ + add_related_lv(rvs, origin_from_cow(lv)); + + /* + * The "hidden" struct logical_volume with name + * "snapshotN" is also related. + */ + snap = find_cow(lv); + add_related_lv(rvs, snap->lv); + } + + /* + * Go through each LV segment and associate PVs or LVs below. + */ + list_iterate_items(lvseg, &lv->segments) { + add_related_segment(rvs, lvseg); + } +} + +/* + * Find all volumes related to physical volume 'pv'. + * Any LVs that 'pv' backs are related, so we search each segment and add LVs + * as necessary. + */ +static void pv_find_related_volumes(struct related_volumes *rvs, + const struct physical_volume *pv) +{ + struct pv_segment *pvseg; + + /* + * Go through pv segments and see which LVs are tied to them. If we + * find any LVs, add them to the list of LVs. + */ + list_iterate_items(pvseg, &pv->segments) { + if (pvseg_is_allocated(pvseg) && (pvseg->lvseg->lv)) + add_related_lv(rvs, pvseg->lvseg->lv); + } +} + +/* + * Add a PV entry to a list of related volumes. + * Avoid duplicate entries by first calling find_in_pv_list(). + * Once added, we must then look for further related volumes, so we call + * pv_find_related_volumes(). + */ +void pv_add_related_volume(struct related_volumes *rvs, struct pv_list *pvl) +{ + if (pvl && !find_in_pv_list(&rvs->pl, pvl->pv)) { + list_del(&pvl->list); + list_add(&rvs->pl, &pvl->list); + pv_find_related_volumes(rvs, pvl->pv); + } +} + +/* + * Add a LV entry to a list of related volumes. + * Avoid duplicate entries by first calling find_in_lv_list(). + * Once added, we must then look for further related volumes, so we call + * lv_find_related_volumes(). + */ +void lv_add_related_volume(struct related_volumes *rvs, struct lv_list *lvl) +{ + if (lvl && !find_in_lv_list(&rvs->ll, lvl->lv)) { + list_del(&lvl->list); + list_add(&rvs->ll, &lvl->list); + lv_find_related_volumes(rvs, lvl->lv); + } +} + +/* + * Move a list of related volumes from one volume group to another. + */ +void move_related_volumes(struct related_volumes *rvs, + struct volume_group *vg_from, + struct volume_group *vg_to) +{ + struct lv_list *lvl; + struct pv_list *pvl; + struct list *pvh, *pvht; + struct list *lvh, *lvht; + + list_iterate_safe(pvh, pvht, &rvs->pl) { + pvl = list_item(pvh, struct pv_list); + _move_pv(vg_from, vg_to, pvl); + } + + list_iterate_safe(lvh, lvht, &rvs->ll) { + lvl = list_item(lvh, struct lv_list); + _move_lv(vg_from, vg_to, lvl); + } +} + +void init_related_volumes(struct related_volumes *rvs, struct volume_group *vg) +{ + rvs->vg = vg; + list_init(&rvs->ll); + list_init(&rvs->pl); +} + /** * pv_read - read and return a handle to a physical volume * @cmd: LVM command initiating the pv_read diff --git a/man/vgsplit.8 b/man/vgsplit.8 index 8614c73..e80cbd9 100644 --- a/tools/commands.h +++ b/tools/commands.h @@ -877,6 +877,7 @@ xx(vgsplit, "\t[--alloc AllocationPolicy] " "\n" "\t[-c|--clustered {y|n}] " "\n" "\t[-d|--debug] " "\n" + "\t[-f|--force]\n" "\t[-h|--help] " "\n" "\t[-l|--maxlogicalvolumes MaxLogicalVolumes]" "\n" "\t[-M|--metadatatype 1|2] " "\n" @@ -888,7 +889,7 @@ xx(vgsplit, "\tPhysicalVolumePath [PhysicalVolumePath...]\n", alloc_ARG, autobackup_ARG, clustered_ARG, - maxlogicalvolumes_ARG, maxphysicalvolumes_ARG, + force_ARG, maxlogicalvolumes_ARG, maxphysicalvolumes_ARG, metadatatype_ARG, test_ARG) xx(version, diff --git a/tools/toollib.c b/tools/toollib.c index d9544d4..150db8d 100644 --- a/tools/vgsplit.c +++ b/tools/vgsplit.c @@ -15,191 +15,31 @@ #include "tools.h" -static void _move_pv(struct volume_group *vg_from, struct volume_group *vg_to, - struct pv_list *pvl) -{ - struct physical_volume *pv; - - list_del(&pvl->list); - list_add(&vg_to->pvs, &pvl->list); - - vg_from->pv_count--; - vg_to->pv_count++; - - pv = list_item(pvl, struct pv_list)->pv; - - vg_from->extent_count -= pv_pe_count(pv); - vg_to->extent_count += pv_pe_count(pv); - - vg_from->free_count -= pv_pe_count(pv) - pv_pe_alloc_count(pv); - vg_to->free_count += pv_pe_count(pv) - pv_pe_alloc_count(pv); -} - -/* FIXME Why not (lv->vg == vg) ? */ -static int _lv_is_in_vg(struct volume_group *vg, struct logical_volume *lv) +static int confirm_vgsplit(const struct list *ll, const struct list *pl) { + const char *prompt = "Really move? [y/n]: "; + struct pv_list *pvl; struct lv_list *lvl; - list_iterate_items(lvl, &vg->lvs) - if (lv == lvl->lv) - return 1; - - return 0; -} - - -static int _move_lvs(struct volume_group *vg_from, struct volume_group *vg_to) -{ - struct list *lvh, *lvht; - struct logical_volume *lv; - struct lv_segment *seg; - struct physical_volume *pv; - struct volume_group *vg_with; - unsigned s; - - list_iterate_safe(lvh, lvht, &vg_from->lvs) { - lv = list_item(lvh, struct lv_list)->lv; - - if ((lv->status & SNAPSHOT)) - continue; - - if ((lv->status & MIRRORED)) - continue; - - /* Ensure all the PVs used by this LV remain in the same */ - /* VG as each other */ - vg_with = NULL; - list_iterate_items(seg, &lv->segments) { - for (s = 0; s < seg->area_count; s++) { - /* FIXME Check AREA_LV too */ - if (seg_type(seg, s) != AREA_PV) - continue; - - pv = seg_pv(seg, s); - if (vg_with) { - if (!pv_is_in_vg(vg_with, pv)) { - log_error("Can't split Logical " - "Volume %s between " - "two Volume Groups", - lv->name); - return 0; - } - continue; - } - - if (pv_is_in_vg(vg_from, pv)) { - vg_with = vg_from; - continue; - } - if (pv_is_in_vg(vg_to, pv)) { - vg_with = vg_to; - continue; - } - log_error("Physical Volume %s not found", - pv_dev_name(pv)); - return 0; - } - - } - - if (vg_with == vg_from) + printf("Continuing with vgsplit will cause more volumes to move " + "than originally requested.\n" + "The following logical volumes will move:\n"); + list_iterate_items(lvl, ll) { + if (is_reserved_name(lvl->lv->name)) continue; - - /* Move this LV */ - list_del(lvh); - list_add(&vg_to->lvs, lvh); - - vg_from->lv_count--; - vg_to->lv_count++; + printf("%s ", lvl->lv->name); } - - /* FIXME Ensure no LVs contain segs pointing at LVs in the other VG */ - - return 1; -} - -static int _move_snapshots(struct volume_group *vg_from, - struct volume_group *vg_to) -{ - struct list *lvh, *lvht; - struct logical_volume *lv; - struct lv_segment *seg; - int cow_from = 0; - int origin_from = 0; - - list_iterate_safe(lvh, lvht, &vg_from->lvs) { - lv = list_item(lvh, struct lv_list)->lv; - - if (!(lv->status & SNAPSHOT)) - continue; - - list_iterate_items(seg, &lv->segments) { - cow_from = _lv_is_in_vg(vg_from, seg->cow); - origin_from = _lv_is_in_vg(vg_from, seg->origin); - - if (cow_from && origin_from) - continue; - if ((!cow_from && origin_from) || - (cow_from && !origin_from)) { - log_error("Can't split snapshot %s between" - " two Volume Groups", seg->cow->name); - return 0; - } - } - - /* Move this snapshot */ - list_del(lvh); - list_add(&vg_to->lvs, lvh); - - vg_from->snapshot_count--; - vg_to->snapshot_count++; + printf("\nAnd the following physical volumes will move:\n"); + list_iterate_items(pvl, pl) + printf("%s ", pv_dev_name(pvl->pv)); + printf("\n"); + if (yes_no_prompt(prompt) == 'n') { + log_print("Aborting vgsplit operation."); + return 0; } - return 1; } -static int _move_mirrors(struct volume_group *vg_from, - struct volume_group *vg_to) -{ - struct list *lvh, *lvht; - struct logical_volume *lv; - struct lv_segment *seg; - unsigned s, seg_in, log_in; - - list_iterate_safe(lvh, lvht, &vg_from->lvs) { - lv = list_item(lvh, struct lv_list)->lv; - - if (!(lv->status & MIRRORED)) - continue; - - seg = first_seg(lv); - - seg_in = 0; - for (s = 0; s < seg->area_count; s++) - if (_lv_is_in_vg(vg_to, seg_lv(seg, s))) - seg_in++; - - log_in = (!seg->log_lv || _lv_is_in_vg(vg_to, seg->log_lv)); - - if ((seg_in && seg_in < seg->area_count) || - (seg_in && seg->log_lv && !log_in) || - (!seg_in && seg->log_lv && log_in)) { - log_error("Can't split mirror %s between " - "two Volume Groups", lv->name); - return 0; - } - - if (seg_in == seg->area_count && log_in) { - list_del(lvh); - list_add(&vg_to->lvs, lvh); - - vg_from->lv_count--; - vg_to->lv_count++; - } - } - - return 1; -} /* * Has the user given an option related to a new vg as the split destination? @@ -222,6 +62,10 @@ int vgsplit(struct cmd_context *cmd, int argc, char **argv) int active; int existing_vg; struct pv_list *pvl; + struct lv_list *lvl; + struct related_volumes rvs; + unsigned int lv_count_cmdline; + unsigned int pv_count_cmdline; if (argc < 3) { log_error("Existing VG, new VG and physical volumes required."); @@ -302,28 +146,64 @@ int vgsplit(struct cmd_context *cmd, int argc, char **argv) if (!archive(vg_from)) goto error; - /* Move PVs across to new structure */ + /* + * First verify the cmdline arguments - must be either PVs or LVs that + * are in 'vg_from'. + */ for (opt = 0; opt < argc; opt++) { - if (!(pvl = find_pv_in_vg(vg_from, argv[opt]))) { - log_error("Physical volume %s not in volume group %s", + /* FIXME: handle tags */ + if (!find_lv_in_vg(vg_from, argv[opt]) && + !find_pv_in_vg(vg_from, argv[opt])) { + log_error("Argument %s is neither a physical or " + "logical volume related to volume " + "group %s", argv[opt], vg_from->name); goto error; } + } - _move_pv(vg_from, vg_to, pvl); + /* + * Go through the cmdline a second time. The reason we do this is + * because each call to *_add_related_volume() may add other PVs or + * LVs from the lists on 'vg_from' to the related volumes list. Thus, + * the find_{lv|pv}_in_vg() may fail. + * + * Build temporary related volumes list by removing the lvs and pvs + * from 'vg_from' and storing them inside 'rvs'. + */ + init_related_volumes(&rvs, vg_from); + lv_count_cmdline = 0; + pv_count_cmdline = 0; + for (opt = 0; opt < argc; opt++) { + /* FIXME: handle tags */ + if ((lvl = find_lv_in_vg(vg_from, argv[opt]))) { + lv_count_cmdline++; + lv_add_related_volume(&rvs, lvl); + } else if ((pvl = find_pv_in_vg(vg_from, argv[opt]))) { + pv_count_cmdline++; + pv_add_related_volume(&rvs, pvl); + } } - /* Move required LVs across, checking consistency */ - if (!(_move_lvs(vg_from, vg_to))) - goto error; + /* + * Now that we have the lists of all PVs and LVs involved in the + * split, check if this list contains PVs or LVs above what the user + * requested. If so, ask for confirmation before continuing, as this + * is most likely what the user would expect. + */ + if ( ((pv_count_cmdline && list_size(&rvs.pl) > pv_count_cmdline) || + (lv_count_cmdline && list_size(&rvs.ll) > lv_count_cmdline)) && + !arg_count(cmd, force_ARG) ) { + if (!confirm_vgsplit(&rvs.ll, &rvs.pl)) + goto error; + } - /* Move required snapshots across */ - if (!(_move_snapshots(vg_from, vg_to))) - goto error; + /* FIXME: Move LV active check here and restrict it to 'rvs' */ - /* Move required mirrors across */ - if (!(_move_mirrors(vg_from, vg_to))) - goto error; + /* + * Move PVs and LVs, updating internal structures and accounting. + */ + move_related_volumes(&rvs, vg_from, vg_to); /* Split metadata areas and check if both vgs have at least one area */ if (!(vg_split_mdas(cmd, vg_from, vg_to)) && vg_from->pv_count) { -- 1.5.3.4 --