From 287530eea14ce859a015ee4c10b2a85b7011eb28 Mon Sep 17 00:00:00 2001 From: David Teigland Date: Mon, 16 May 2016 14:35:43 -0500 Subject: pvchange: add importclone option This is a native implementation of vgimportclone. pvchange --importclone PV... All cloned PVs from the VG must be imported together. --- lib/cache/lvmcache.c | 40 +++++ lib/cache/lvmcache.h | 5 + lib/filters/filter-usable.c | 6 + tools/args.h | 1 + tools/commands.h | 2 +- tools/pvchange.c | 414 +++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 464 insertions(+), 4 deletions(-) diff --git a/lib/cache/lvmcache.c b/lib/cache/lvmcache.c index 0934b2068..16152e24a 100644 --- a/lib/cache/lvmcache.c +++ b/lib/cache/lvmcache.c @@ -88,6 +88,46 @@ static int _vg_global_lock_held = 0; /* Global lock held when cache wiped? */ static int _found_duplicate_pvs = 0; /* If we never see a duplicate PV we can skip checking for them later. */ static int _suppress_lock_ordering = 0; +static int _importing_clones = 0; +static DM_LIST_INIT(_import_clone_devs); + +int lvmcache_add_import_clone_dev(struct cmd_context *cmd, struct device *dev) +{ + struct device_list *devl; + + if (!(devl = dm_pool_alloc(cmd->mem, sizeof(*devl)))) { + log_error("device_list element allocation failed"); + return 0; + } + devl->dev = dev; + + dm_list_add(&_import_clone_devs, &devl->list); + _importing_clones = 1; + return 1; +} + +int lvmcache_is_import_clone_dev(struct device *dev) +{ + struct device_list *devl; + + dm_list_iterate_items(devl, &_import_clone_devs) { + if (devl->dev == dev) + return 1; + } + return 0; +} + +void lvmcache_clear_import_clone_devs(void) +{ + dm_list_init(&_import_clone_devs); + _importing_clones = 0; +} + +int lvmcache_importing_clones(void) +{ + return _importing_clones; +} + int lvmcache_init(void) { /* diff --git a/lib/cache/lvmcache.h b/lib/cache/lvmcache.h index 4fb74dbac..404015071 100644 --- a/lib/cache/lvmcache.h +++ b/lib/cache/lvmcache.h @@ -213,4 +213,9 @@ int lvmcache_dev_is_unchosen_duplicate(struct device *dev); void lvmcache_remove_unchosen_duplicate(struct device *dev); +int lvmcache_add_import_clone_dev(struct cmd_context *cmd, struct device *dev); +int lvmcache_is_import_clone_dev(struct device *dev); +void lvmcache_clear_import_clone_devs(void); +int lvmcache_importing_clones(void); + #endif diff --git a/lib/filters/filter-usable.c b/lib/filters/filter-usable.c index 4ee2e9df8..d521fa448 100644 --- a/lib/filters/filter-usable.c +++ b/lib/filters/filter-usable.c @@ -15,6 +15,7 @@ #include "lib.h" #include "filter.h" #include "activate.h" /* device_is_usable */ +#include "lvmcache.h" #ifdef UDEV_SYNC_SUPPORT #include #include "dev-ext-udev-constants.h" @@ -158,6 +159,11 @@ static int _passes_usable_filter(struct dev_filter *f, struct device *dev) } } + if (lvmcache_importing_clones() && !lvmcache_is_import_clone_dev(dev)) { + log_debug_devs("%s: Skipping non-import device.", dev_name(dev)); + r = 0; + } + return r; } diff --git a/tools/args.h b/tools/args.h index ebddd2ec4..156234a03 100644 --- a/tools/args.h +++ b/tools/args.h @@ -49,6 +49,7 @@ arg(ignorelockingfailure_ARG, '\0', "ignorelockingfailure", NULL, 0) arg(ignoremonitoring_ARG, '\0', "ignoremonitoring", NULL, 0) arg(ignoreskippedcluster_ARG, '\0', "ignoreskippedcluster", NULL, 0) arg(ignoreunsupported_ARG, '\0', "ignoreunsupported", NULL, 0) +arg(importclone_ARG, '\0', "importclone", NULL, 0) arg(labelsector_ARG, '\0', "labelsector", int_arg, 0) arg(lockopt_ARG, '\0', "lockopt", string_arg, 0) arg(lockstart_ARG, '\0', "lockstart", NULL, 0) diff --git a/tools/commands.h b/tools/commands.h index 7b32835ea..993853a7d 100644 --- a/tools/commands.h +++ b/tools/commands.h @@ -739,7 +739,7 @@ xx(pvchange, all_ARG, allocatable_ARG, allocation_ARG, autobackup_ARG, deltag_ARG, addtag_ARG, force_ARG, ignoreskippedcluster_ARG, metadataignore_ARG, - select_ARG, test_ARG, uuid_ARG) + select_ARG, test_ARG, uuid_ARG, importclone_ARG) xx(pvresize, "Resize physical volume(s)", diff --git a/tools/pvchange.c b/tools/pvchange.c index e100b80c5..0d90a50d0 100644 --- a/tools/pvchange.c +++ b/tools/pvchange.c @@ -14,12 +14,408 @@ */ #include "tools.h" +#include "lvmcache.h" +#include "lvmetad-client.h" struct pvchange_params { unsigned done; unsigned total; + + int import_vg; + int found_args; + struct dm_list arg_import; + const char *base_vgname; + const char *old_vgname; + const char *new_vgname; +}; + +struct pvchange_device { + struct dm_list list; + struct device *dev; + unsigned found_in_vg : 1; }; +static int _pvchange_import_check_pv_single(struct cmd_context *cmd, struct volume_group *vg, + struct physical_volume *pv, struct processing_handle *handle) +{ + struct pvchange_params *pp = (struct pvchange_params *) handle->custom_handle; + struct pvchange_device *pd; + + pp->total++; + + if (vg && is_orphan_vg(vg->name)) { + log_error("Cannot importclone on orphan PV %s.", dev_name(pv->dev)); + return ECMD_FAILED; + } + + if (!(pd = dm_pool_zalloc(cmd->mem, sizeof(*pd)))) { + log_error("alloc failed."); + return ECMD_FAILED; + } + + pd->dev = pv->dev; + dm_list_add(&pp->arg_import, &pd->list); + + log_debug("pvchange dev %s VG %s found to import", + dev_name(pd->dev), vg ? vg->name : ""); + + pp->found_args++; + + return ECMD_PROCESSED; +} + +static int _pvchange_import_check_vg_single(struct cmd_context *cmd, const char *vg_name, + struct volume_group *vg, struct processing_handle *handle) +{ + char uuid[64] __attribute__((aligned(8))); + struct pvchange_params *pp = (struct pvchange_params *) handle->custom_handle; + struct pvchange_device *pd; + struct pv_list *pvl; + int found; + + if (vg_is_exported(vg) && !pp->import_vg) { + log_error("VG %s is exported, use the --importvg option.", vg->name); + goto fail; + } + + if (vg_status(vg) & PARTIAL_VG) { + log_error("VG %s is partial, it must be complete.", vg->name); + goto fail; + } + + /* + * FIXME: This active LV check is not smart enough to distinguish + * between LVs that are active in the original VG vs the cloned VG + * that's being imported. So, even when the cloned devices being + * imported do not have any active LVs, you still need to deactivate + * LVs in the original VG that is not being imported. Using the + * DEV_USED_FOR_LV feature is probably what needs to be done here + * instead. + */ + + if (lvs_in_vg_activated(vg)) { + log_error("VG %s has active LVs, deactivate first.", vg->name); + goto fail; + } + + /* + * The arg_import list must match the PVs in VG. + */ + + dm_list_iterate_items(pvl, &vg->pvs) { + found = 0; + + dm_list_iterate_items(pd, &pp->arg_import) { + if (pvl->pv->dev != pd->dev) + continue; + pd->found_in_vg = 1; + found = 1; + break; + } + + if (!found) { + if (!id_write_format(&pvl->pv->id, uuid, sizeof(uuid))) + goto fail; + + /* all PVs in the VG must be imported together, pvl is missing from args. */ + log_error("PV with UUID %s is part of VG %s, but is not included in the devices to import.", + uuid, vg->name); + log_error("All PVs in the VG must be imported together."); + goto fail; + } + } + + dm_list_iterate_items(pd, &pp->arg_import) { + if (!pd->found_in_vg) { + /* device arg is not in the VG. */ + log_error("Device %s was not found in VG %s.", dev_name(pd->dev), vg->name); + log_error("The devices to import must match the devices in the VG."); + goto fail; + } + } + + return ECMD_PROCESSED; +fail: + return ECMD_FAILED; +} + +/* + * FIXME: It's ugly that we have to write the VG once to change each PV ID, and + * then write the VG again later to change the other things. The metadata + * writing functions don't seem to support writing everything at once. It + * should be possible to change the PV UUIDs in _pvchange_import_vg_single. + */ + +static int _pvchange_import_pv_single(struct cmd_context *cmd, struct volume_group *vg, + struct physical_volume *pv, struct processing_handle *handle) +{ + struct pvchange_params *pp = (struct pvchange_params *) handle->custom_handle; + struct pvchange_device *pd; + int found = 0; + + /* Check again that this PV is one we are importing. */ + dm_list_iterate_items(pd, &pp->arg_import) { + if (pd->dev == pv->dev) { + found = 1; + break; + } + } + + if (!found) { + log_error("PV device %s does not match any command arg.", dev_name(pv->dev)); + return ECMD_FAILED; + } + + if (!archive(vg)) + return_ECMD_FAILED; + + log_debug("pvchange import writing new uuid on dev %s.", dev_name(pv->dev)); + + /* Low level pv_write code needs old_id to be set! */ + memcpy(&pv->old_id, &pv->id, sizeof(pv->id)); + + if (!id_create(&pv->id)) { + log_error("Failed to generate new random UUID for %s.", dev_name(pv->dev)); + return ECMD_FAILED; + } + + if (!pv_write(cmd, pv, 1)) { + log_error("Failed to write PV for %s.", dev_name(pv->dev)); + return ECMD_FAILED; + } + + if (!vg_write(vg) || !vg_commit(vg)) { + log_error("Failed to change VG for %s.", dev_name(pv->dev)); + return ECMD_FAILED; + } + + return ECMD_PROCESSED; +} + +static int _pvchange_import_vg_single(struct cmd_context *cmd, const char *vg_name, + struct volume_group *vg, struct processing_handle *handle) +{ + struct pvchange_params *pp = (struct pvchange_params *) handle->custom_handle; + struct pv_list *pvl, *new_pvl; + struct lv_list *lvl; + + if (pp->import_vg) + vg->status &= ~EXPORTED_VG; + + if (!id_create(&vg->id)) + goto_bad; + + /* Low level vg_write code needs old_name to be set! */ + vg->old_name = vg->name; + + if (!(vg->name = dm_pool_strdup(vg->vgmem, pp->new_vgname))) + goto_bad; + + dm_list_iterate_items(pvl, &vg->pvs) { + if (!(new_pvl = dm_pool_zalloc(vg->vgmem, sizeof(*new_pvl)))) + goto_bad; + + new_pvl->pv = pvl->pv; + + if (!(pvl->pv->vg_name = dm_pool_strdup(vg->vgmem, pp->new_vgname))) + goto_bad; + + /* + * FIXME: It would be nice to write new PV uuids here, but the + * metadata writing functions don't seem to be able to do this, + * so we have a separate loop that writes new PV IDs earlier. + */ + /* + memcpy(&new_pvl->pv->old_id, &new_pvl->pv->id, sizeof(new_pvl->pv->id)); + id_create(&new_pvl->pv->id); + dm_list_add(&vg->pv_write_list, &new_pvl->list); + */ + } + + dm_list_iterate_items(lvl, &vg->lvs) + memcpy(&lvl->lv->lvid, &vg->id, sizeof(vg->id)); + + if (!vg_write(vg) || !vg_commit(vg)) + goto_bad; + + return ECMD_PROCESSED; +bad: + return ECMD_FAILED; +} + +static int _pvchange_importclone(struct cmd_context *cmd, struct processing_handle *handle, + int argc, char **argv) +{ + struct pvchange_params *pp = (struct pvchange_params *) handle->custom_handle; + struct dm_list vgnameids_on_system; /* vgnameid_list */ + struct vgnameid_list *vgnl; + struct pvchange_device *pd; + struct lvmcache_info *info; + const char *vgname; + char base_vgname[NAME_LEN] = { 0 }; + char tmp_vgname[NAME_LEN] = { 0 }; + unsigned int vgname_count; + int ret; + + dm_list_init(&vgnameids_on_system); + dm_list_init(&pp->arg_import); + + /* + * Importing clones is by definition dealing with duplicate PVs, so + * disable lvmetad if it's not already. If there are duplicate PVs to + * import, it may already be disabled. + */ + lvmetad_set_disabled(cmd, LVMETAD_DISABLE_REASON_DUPLICATES); + + if (!lock_vol(cmd, VG_GLOBAL, LCK_VG_WRITE, NULL)) { + log_error("Unable to obtain global lock."); + return ECMD_FAILED; + } + + if (!lockd_gl(cmd, "ex", 0)) + goto_bad; + cmd->lockd_gl_disable = 1; + + /* + * Find the devices being imported which are named on the command line. + * They may be in the list of unchosen duplicates. + */ + + log_debug("Finding devices to import."); + cmd->command->flags |= ENABLE_DUPLICATE_DEVS; + ret = process_each_pv(cmd, argc, argv, NULL, 0, READ_ALLOW_EXPORTED, handle, _pvchange_import_check_pv_single); + if (ret != ECMD_PROCESSED) + goto_bad; + + if (pp->found_args != argc) { + log_error("Failed to find all devices."); + goto_bad; + } + + /* + * Find the VG name of the PVs being imported, save as old_vgname. + * N.B. If pd->dev is a duplicate, then it may not match info->dev. + * TODO: also verify VGID matches for all. + */ + + dm_list_iterate_items(pd, &pp->arg_import) { + if (!(info = lvmcache_info_from_pvid(pd->dev->pvid, 0))) { + log_error("Failed to find PVID for device %s in lvmcache.", dev_name(pd->dev)); + goto_bad; + } + + if (!(vgname = lvmcache_vgname_from_info(info))) { + log_error("Failed to find VG name for device %s in lvmcache.", dev_name(pd->dev)); + goto_bad; + } + + if (!pp->old_vgname) { + if (!(pp->old_vgname = dm_pool_strdup(cmd->mem, vgname))) + goto_bad; + } else { + if (strcmp(pp->old_vgname, vgname)) { + log_error("Devices must be from the same VG."); + goto_bad; + } + } + } + + /* + * Pick a new VG name, save as new_vgname. The new name begins with + * the basevgname or old_vgname, plus a $i suffix, if necessary, to + * make it unique. This requires comparing the old_vgname with all the + * VG names on the system. + */ + + if (pp->base_vgname) { + snprintf(base_vgname, sizeof(base_vgname) - 1, "%s", pp->base_vgname); + memcpy(tmp_vgname, base_vgname, NAME_LEN); + vgname_count = 0; + } else { + snprintf(base_vgname, sizeof(base_vgname) - 1, "%s", pp->old_vgname); + snprintf(tmp_vgname, sizeof(tmp_vgname) - 1, "%s1", pp->old_vgname); + vgname_count = 1; + } + + if (!get_vgnameids(cmd, &vgnameids_on_system, NULL, 0)) + goto_bad; + +retry_name: + dm_list_iterate_items(vgnl, &vgnameids_on_system) { + if (!strcmp(vgnl->vg_name, tmp_vgname)) { + vgname_count++; + snprintf(tmp_vgname, sizeof(tmp_vgname) - 1, "%s%u", base_vgname, vgname_count); + goto retry_name; + } + } + + if (!(pp->new_vgname = dm_pool_strdup(cmd->mem, tmp_vgname))) { + log_error("strdup failed."); + goto_bad; + } + log_debug("Using new VG name %s.", pp->new_vgname); + + /* + * Create a device filter so that we are only working with the devices + * in arg_import. With the original devs hidden (that arg_import were + * cloned from), we can read and write the cloned PVs and VG without + * touching the original PVs/VG. + */ + + dm_list_iterate_items(pd, &pp->arg_import) + lvmcache_add_import_clone_dev(cmd, pd->dev); + lvmcache_destroy(cmd, 1, 0); + dev_cache_full_scan(cmd->full_filter); + + /* + * Read old_vgname, and check that vg->pvs matches the list of devices + * being imported in arg_import, plus some other VG checks. + */ + + log_debug("Checking VG to import."); + ret = process_each_vg(cmd, 0, NULL, pp->old_vgname, 0, handle, _pvchange_import_check_vg_single); + if (ret != ECMD_PROCESSED) + goto_bad; + + /* + * Change the PV uuid of each device. + */ + + log_debug("Changing PVs."); + ret = process_each_pv(cmd, argc, argv, pp->old_vgname, 0, READ_FOR_UPDATE | READ_ALLOW_EXPORTED, handle, _pvchange_import_pv_single); + if (ret != ECMD_PROCESSED) + goto_bad; + + /* + * Change the VG name and uuid. + */ + + log_debug("Changing VG %s to %s.", pp->old_vgname, pp->new_vgname); + + /* We don't care if the new name comes before the old in lock order. */ + lvmcache_lock_ordering(0); + + if (!lock_vol(cmd, pp->new_vgname, LCK_VG_WRITE, NULL)) { + log_error("Can't get lock for new VG name %s", pp->new_vgname); + lvmcache_lock_ordering(1); + goto bad; + } + + ret = process_each_vg(cmd, 0, NULL, pp->old_vgname, READ_FOR_UPDATE | READ_ALLOW_EXPORTED, handle, _pvchange_import_vg_single); + if (ret != ECMD_PROCESSED) + goto_bad; + + unlock_vg(cmd, pp->new_vgname); + unlock_vg(cmd, VG_GLOBAL); + + pp->done = pp->found_args; + return ECMD_PROCESSED; + +bad: + unlock_vg(cmd, VG_GLOBAL); + return ECMD_FAILED; +} + static int _pvchange_single(struct cmd_context *cmd, struct volume_group *vg, struct physical_volume *pv, struct processing_handle *handle) { @@ -207,8 +603,11 @@ int pvchange(struct cmd_context *cmd, int argc, char **argv) struct processing_handle *handle = NULL; int ret; - if (!(arg_count(cmd, allocatable_ARG) + arg_is_set(cmd, addtag_ARG) + - arg_is_set(cmd, deltag_ARG) + arg_count(cmd, uuid_ARG) + + if (!(arg_count(cmd, allocatable_ARG) + + arg_is_set(cmd, addtag_ARG) + + arg_is_set(cmd, deltag_ARG) + + arg_is_set(cmd, importclone_ARG) + + arg_count(cmd, uuid_ARG) + arg_count(cmd, metadataignore_ARG))) { log_error("Please give one or more of -x, -uuid, " "--addtag, --deltag or --metadataignore"); @@ -236,6 +635,12 @@ int pvchange(struct cmd_context *cmd, int argc, char **argv) goto out; } + if (arg_is_set(cmd, importclone_ARG) && (!argc || arg_count(cmd, all_ARG))) { + log_error("Option --importclone requires PV args."); + ret = EINVALID_CMD_LINE; + goto out; + } + if (!argc) { /* * Take the global lock here so the lvmcache remains @@ -252,7 +657,10 @@ int pvchange(struct cmd_context *cmd, int argc, char **argv) set_pv_notify(cmd); - ret = process_each_pv(cmd, argc, argv, NULL, 0, READ_FOR_UPDATE | READ_ALLOW_EXPORTED, handle, _pvchange_single); + if (arg_is_set(cmd, importclone_ARG)) + ret = _pvchange_importclone(cmd, handle, argc, argv); + else + ret = process_each_pv(cmd, argc, argv, NULL, 0, READ_FOR_UPDATE | READ_ALLOW_EXPORTED, handle, _pvchange_single); if (!argc) unlock_vg(cmd, VG_GLOBAL); -- cgit v1.2.1