From ebd2758dab39bb70fd63d0d77314971f8bc9cbfd Mon Sep 17 00:00:00 2001 From: David Teigland Date: Wed, 25 May 2016 13:57:33 -0500 Subject: [PATCH] vgimportclone: add native command This is cleaner and more efficient than the script. The args and usage are unchanged. --- lib/Makefile.in | 1 + lib/commands/toolcontext.c | 9 +- lib/filters/filter-internal.c | 81 ++++++++ lib/filters/filter.h | 4 + lib/metadata/metadata.c | 3 + lib/misc/lvm-globals.c | 11 ++ lib/misc/lvm-globals.h | 2 + scripts/Makefile.in | 2 +- test/Makefile.in | 3 +- tools/Makefile.in | 1 + tools/args.h | 2 + tools/commands.h | 15 ++ tools/vgimportclone.c | 363 ++++++++++++++++++++++++++++++++++ 13 files changed, 493 insertions(+), 4 deletions(-) create mode 100644 lib/filters/filter-internal.c create mode 100644 tools/vgimportclone.c diff --git a/lib/Makefile.in b/lib/Makefile.in index 467ef9077..451d96d8f 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -76,6 +76,7 @@ SOURCES =\ filters/filter-partitioned.c \ filters/filter-type.c \ filters/filter-usable.c \ + filters/filter-internal.c \ format_text/archive.c \ format_text/archiver.c \ format_text/export.c \ diff --git a/lib/commands/toolcontext.c b/lib/commands/toolcontext.c index 75f19c9f4..3ee941082 100644 --- a/lib/commands/toolcontext.c +++ b/lib/commands/toolcontext.c @@ -1053,7 +1053,7 @@ static int _init_dev_cache(struct cmd_context *cmd) return 1; } -#define MAX_FILTERS 8 +#define MAX_FILTERS 9 static struct dev_filter *_init_lvmetad_filter_chain(struct cmd_context *cmd) { @@ -1078,6 +1078,13 @@ static struct dev_filter *_init_lvmetad_filter_chain(struct cmd_context *cmd) nr_filt++; } + /* internal filter used by command processing. */ + if (!(filters[nr_filt] = internal_filter_create())) { + log_error("Failed to create internal device filter"); + goto bad; + } + nr_filt++; + /* global regex filter. Optional. */ if ((cn = find_config_tree_node(cmd, devices_global_filter_CFG, NULL))) { if (!(filters[nr_filt] = regex_filter_create(cn->v))) { diff --git a/lib/filters/filter-internal.c b/lib/filters/filter-internal.c new file mode 100644 index 000000000..1a58a37b2 --- /dev/null +++ b/lib/filters/filter-internal.c @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2006 Red Hat, Inc. All rights reserved. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "lib.h" +#include "filter.h" + +static DM_LIST_INIT(_allow_devs); + +int internal_filter_allow(struct dm_pool *mem, struct device *dev) +{ + struct device_list *devl; + + if (!(devl = dm_pool_alloc(mem, sizeof(*devl)))) { + log_error("device_list element allocation failed"); + return 0; + } + devl->dev = dev; + + dm_list_add(&_allow_devs, &devl->list); + return 1; +} + +void internal_filter_clear(void) +{ + dm_list_init(&_allow_devs); +} + +static int _passes_internal(struct dev_filter *f __attribute__((unused)), + struct device *dev) +{ + struct device_list *devl; + + if (!internal_filtering()) + return 1; + + dm_list_iterate_items(devl, &_allow_devs) { + if (devl->dev == dev) + return 1; + } + + log_debug_devs("%s: Skipping for internal filtering.", dev_name(dev)); + return 0; +} + +static void _destroy(struct dev_filter *f) +{ + if (f->use_count) + log_error(INTERNAL_ERROR "Destroying internal filter while in use %u times.", f->use_count); + + dm_free(f); +} + +struct dev_filter *internal_filter_create(void) +{ + struct dev_filter *f; + + if (!(f = dm_zalloc(sizeof(*f)))) { + log_error("md filter allocation failed"); + return NULL; + } + + f->passes_filter = _passes_internal; + f->destroy = _destroy; + f->use_count = 0; + + log_debug_devs("internal filter initialised."); + + return f; +} + diff --git a/lib/filters/filter.h b/lib/filters/filter.h index ebd25e2be..d75f6e11c 100644 --- a/lib/filters/filter.h +++ b/lib/filters/filter.h @@ -32,6 +32,10 @@ struct dev_filter *persistent_filter_create(struct dev_types *dt, const char *file); struct dev_filter *sysfs_filter_create(void); +struct dev_filter *internal_filter_create(void); +int internal_filter_allow(struct dm_pool *mem, struct device *dev); +void internal_filter_clear(void); + /* * patterns must be an array of strings of the form: * [ra], eg, diff --git a/lib/metadata/metadata.c b/lib/metadata/metadata.c index ea42fb70d..9b1fd3148 100644 --- a/lib/metadata/metadata.c +++ b/lib/metadata/metadata.c @@ -3306,6 +3306,9 @@ static int _check_old_pv_ext_for_vg(struct volume_group *vg) !pvl->pv->fmt->ops->pv_needs_rewrite) continue; + if (_pv_in_pv_list(pvl->pv, &vg->pv_write_list)) + continue; + if (!pvl->pv->fmt->ops->pv_needs_rewrite(pvl->pv->fmt, pvl->pv, &pv_needs_rewrite)) return_0; diff --git a/lib/misc/lvm-globals.c b/lib/misc/lvm-globals.c index b7af353ae..9c78f3e33 100644 --- a/lib/misc/lvm-globals.c +++ b/lib/misc/lvm-globals.c @@ -26,6 +26,7 @@ static int _verbose_level = VERBOSE_BASE_LEVEL; static int _silent = 0; static int _test = 0; static int _md_filtering = 0; +static int _internal_filtering = 0; static int _fwraid_filtering = 0; static int _pvmove = 0; static int _full_scan_done = 0; /* Restrict to one full scan during each cmd */ @@ -79,6 +80,11 @@ void init_md_filtering(int level) _md_filtering = level; } +void init_internal_filtering(int level) +{ + _internal_filtering = level; +} + void init_fwraid_filtering(int level) { _fwraid_filtering = level; @@ -242,6 +248,11 @@ int md_filtering(void) return _md_filtering; } +int internal_filtering(void) +{ + return _internal_filtering; +} + int fwraid_filtering(void) { return _fwraid_filtering; diff --git a/lib/misc/lvm-globals.h b/lib/misc/lvm-globals.h index 77c01b413..14a7d4366 100644 --- a/lib/misc/lvm-globals.h +++ b/lib/misc/lvm-globals.h @@ -26,6 +26,7 @@ void init_verbose(int level); void init_silent(int silent); void init_test(int level); void init_md_filtering(int level); +void init_internal_filtering(int level); void init_fwraid_filtering(int level); void init_pvmove(int level); void init_full_scan_done(int level); @@ -60,6 +61,7 @@ void set_sysfs_dir_path(const char *path); int test_mode(void); int md_filtering(void); +int internal_filtering(void); int fwraid_filtering(void); int pvmove_mode(void); int full_scan_done(void); diff --git a/scripts/Makefile.in b/scripts/Makefile.in index 9c3d72d9d..e6742393b 100644 --- a/scripts/Makefile.in +++ b/scripts/Makefile.in @@ -31,7 +31,7 @@ endif LVMLIBS = @LVM2APP_LIB@ -ldevmapper endif -LVM_SCRIPTS = lvmdump.sh lvmconf.sh vgimportclone.sh +LVM_SCRIPTS = lvmdump.sh lvmconf.sh DM_SCRIPTS = ifeq ("@FSADM@", "yes") diff --git a/test/Makefile.in b/test/Makefile.in index 59fcc96c9..f48d6ead9 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -321,7 +321,6 @@ LIB = $(addprefix lib/, $(LIB_SHARED) $(LIB_LOCAL) $(LIB_NOT) $(LIB_LINK_NOT) $( $(LN_S) -f $(abs_top_builddir)/tools/dmsetup lib/dmstats $(LN_S) -f $(abs_top_srcdir)/conf/thin-performance.profile lib/ $(LN_S) -f $(abs_top_srcdir)/scripts/fsadm.sh lib/fsadm - $(LN_S) -f $(abs_top_srcdir)/scripts/vgimportclone.sh lib/vgimportclone test "$(srcdir)" = . || for i in $(LIB_LVMLOCKD_CONF); do \ $(LN_S) -f $(abs_top_srcdir)/test/lib/$$i lib/; done touch $@ @@ -333,7 +332,7 @@ endif CLEAN_TARGETS += .lib-dir-stamp .tests-stamp $(LIB) $(addprefix lib/,\ $(CMDS) clvmd dmeventd dmsetup dmstats lvmetad lvmpolld \ - harness thin-performance.profile fsadm vgimportclone \ + harness thin-performance.profile fsadm \ dm-version-expected version-expected \ paths-installed paths-installed-t paths-common paths-common-t) diff --git a/tools/Makefile.in b/tools/Makefile.in index 8dfac7fd7..d93c31b4d 100644 --- a/tools/Makefile.in +++ b/tools/Makefile.in @@ -60,6 +60,7 @@ SOURCES =\ vgmerge.c \ vgmknodes.c \ lvpoll.c \ + vgimportclone.c \ vgreduce.c \ vgremove.c \ vgrename.c \ diff --git a/tools/args.h b/tools/args.h index 670d6a0e0..59025139d 100644 --- a/tools/args.h +++ b/tools/args.h @@ -154,6 +154,7 @@ arg(autobackup_ARG, 'A', "autobackup", yes_no_arg, 0, 0) arg(activevolumegroups_ARG, 'A', "activevolumegroups", NULL, 0, 0) arg(background_ARG, 'b', "background", NULL, 0, 0) arg(backgroundfork_ARG, 'b', "background", NULL, 0, 0) +arg(basevgname_ARG, 'n', "basevgname", string_arg, 0, 0) arg(blockdevice_ARG, 'b', "blockdevice", NULL, 0, 0) arg(chunksize_ARG, 'c', "chunksize", size_kb_arg, 0, 0) arg(clustered_ARG, 'c', "clustered", yes_no_arg, 0, 0) @@ -170,6 +171,7 @@ arg(help_ARG, 'h', "help", NULL, 0, 0) arg(cache_ARG, 'H', "cache", NULL, 0, 0) arg(history_ARG, 'H', "history", NULL, 0, 0) arg(help2_ARG, '?', "", NULL, 0, 0) +arg(import_ARG, 'i', "import", NULL, 0, 0) arg(interval_ARG, 'i', "interval", int_arg, 0, 0) arg(iop_version_ARG, 'i', "iop_version", NULL, 0, 0) arg(stripes_ARG, 'i', "stripes", int_arg, 0, 0) diff --git a/tools/commands.h b/tools/commands.h index c0b3f3b8f..ed86b7fc2 100644 --- a/tools/commands.h +++ b/tools/commands.h @@ -1358,6 +1358,21 @@ xx(vgimport, all_ARG, force_ARG, reportformat_ARG, select_ARG, test_ARG) +xx(vgimportclone, + "Import a VG from cloned PVs", + NO_LVMETAD_AUTOSCAN, + "vgimportclone\n" + "\t[-d|--debug]\n" + "\t[-h|--help]\n" + "\t[-i|--import]\n" + "\t[-n|--basevgname]\n" + "\t[-t|--test]\n" + "\t[-v|--verbose]\n" + "\t[--version]\n" + "\t[PhysicalVolumePath...]\n", + + basevgname_ARG, test_ARG, import_ARG) + xx(vgmerge, "Merge volume groups", 0, diff --git a/tools/vgimportclone.c b/tools/vgimportclone.c new file mode 100644 index 000000000..7028fbb0a --- /dev/null +++ b/tools/vgimportclone.c @@ -0,0 +1,363 @@ +/* + * Copyright (C) 2016 Red Hat, Inc. All rights reserved. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "tools.h" +#include "lvmcache.h" +#include "lvmetad-client.h" +#include "filter.h" + +struct vgimportclone_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 vgimportclone_device { + struct dm_list list; + struct device *dev; + unsigned found_in_vg : 1; +}; + +static int _vgimportclone_pv_single(struct cmd_context *cmd, struct volume_group *vg, + struct physical_volume *pv, struct processing_handle *handle) +{ + struct vgimportclone_params *vp = (struct vgimportclone_params *) handle->custom_handle; + struct vgimportclone_device *vd; + + if (vg && is_orphan_vg(vg->name)) { + log_error("Cannot import clone of orphan PV %s.", dev_name(pv->dev)); + return ECMD_FAILED; + } + + if (!(vd = dm_pool_zalloc(cmd->mem, sizeof(*vd)))) { + log_error("alloc failed."); + return ECMD_FAILED; + } + + vd->dev = pv->dev; + dm_list_add(&vp->arg_import, &vd->list); + + log_debug("vgimportclone dev %s VG %s found to import", + dev_name(vd->dev), vg ? vg->name : ""); + + vp->found_args++; + + return ECMD_PROCESSED; +} + +static int _vgimportclone_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 vgimportclone_params *vp = (struct vgimportclone_params *) handle->custom_handle; + struct vgimportclone_device *vd; + struct pv_list *pvl, *new_pvl; + struct lv_list *lvl; + int devs_used_for_lv = 0; + int found; + + if (vg_is_exported(vg) && !vp->import_vg) { + log_error("VG %s is exported, use the --import option.", vg->name); + goto_bad; + } + + if (vg_status(vg) & PARTIAL_VG) { + log_error("VG %s is partial, it must be complete.", vg->name); + goto_bad; + } + + /* + * N.B. lvs_in_vg_activated() is not smart enough to distinguish + * between LVs that are active in the original VG vs the cloned VG + * that's being imported, so check DEV_USED_FOR_LV. + */ + dm_list_iterate_items(pvl, &vg->pvs) { + if (pvl->pv->dev->flags & DEV_USED_FOR_LV) { + log_error("Device %s has active LVs, deactivate first.", dev_name(pvl->pv->dev)); + devs_used_for_lv++; + } + } + + if (devs_used_for_lv) + goto_bad; + + /* + * The arg_import list must match the PVs in VG. + */ + + dm_list_iterate_items(pvl, &vg->pvs) { + found = 0; + + dm_list_iterate_items(vd, &vp->arg_import) { + if (pvl->pv->dev != vd->dev) + continue; + vd->found_in_vg = 1; + found = 1; + break; + } + + if (!found) { + if (!id_write_format(&pvl->pv->id, uuid, sizeof(uuid))) + goto_bad; + + /* 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_bad; + } + } + + dm_list_iterate_items(vd, &vp->arg_import) { + if (!vd->found_in_vg) { + /* device arg is not in the VG. */ + log_error("Device %s was not found in VG %s.", dev_name(vd->dev), vg->name); + log_error("The devices to import must match the devices in the VG."); + goto_bad; + } + } + + /* + * Write changes. + */ + + if (!archive(vg)) + return_ECMD_FAILED; + + if (vp->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, vp->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, vp->new_vgname))) + goto_bad; + + if (vp->import_vg) + new_pvl->pv->status &= ~EXPORTED_VG; + + /* Low level pv_write code needs old_id to be set! */ + memcpy(&new_pvl->pv->old_id, &new_pvl->pv->id, sizeof(new_pvl->pv->id)); + + if (!id_create(&new_pvl->pv->id)) + goto_bad; + + 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; +} + +int vgimportclone(struct cmd_context *cmd, int argc, char **argv) +{ + struct vgimportclone_params vp = { 0 }; + struct processing_handle *handle = NULL; + struct dm_list vgnameids_on_system; /* vgnameid_list */ + struct vgnameid_list *vgnl; + struct vgimportclone_device *vd; + struct lvmcache_info *info; + const char *vgname; + char base_vgname[NAME_LEN] = { 0 }; + char tmp_vgname[NAME_LEN] = { 0 }; + unsigned int vgname_count; + int lvmetad_rescan = 0; + int ret = ECMD_FAILED; + + if (!argc) { + log_error("PV names required."); + return EINVALID_CMD_LINE; + } + + dm_list_init(&vgnameids_on_system); + dm_list_init(&vp.arg_import); + + set_pv_notify(cmd); + + vp.import_vg = arg_is_set(cmd, import_ARG); + + if (lvmetad_used()) { + lvmetad_set_disabled(cmd, LVMETAD_DISABLE_REASON_DUPLICATES); + lvmetad_disconnect(); + lvmetad_rescan = 1; + } + + if (!(handle = init_processing_handle(cmd, NULL))) { + log_error("Failed to initialize processing handle."); + return ECMD_FAILED; + } + handle->custom_handle = &vp; + + if (!lock_vol(cmd, VG_GLOBAL, LCK_VG_WRITE, NULL)) { + log_error("Unable to obtain global lock."); + destroy_processing_handle(cmd, handle); + return ECMD_FAILED; + } + + if (!lockd_gl(cmd, "ex", 0)) + goto_out; + 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; + process_each_pv(cmd, argc, argv, NULL, 0, READ_ALLOW_EXPORTED, handle, _vgimportclone_pv_single); + + if (vp.found_args != argc) { + log_error("Failed to find all devices."); + goto_out; + } + + /* + * Find the VG name of the PVs being imported, save as old_vgname. + * N.B. If vd->dev is a duplicate, then it may not match info->dev. + */ + + dm_list_iterate_items(vd, &vp.arg_import) { + if (!(info = lvmcache_info_from_pvid(vd->dev->pvid, NULL, 0))) { + log_error("Failed to find PVID for device %s in lvmcache.", dev_name(vd->dev)); + goto_out; + } + + if (!(vgname = lvmcache_vgname_from_info(info))) { + log_error("Failed to find VG name for device %s in lvmcache.", dev_name(vd->dev)); + goto_out; + } + + if (!vp.old_vgname) { + if (!(vp.old_vgname = dm_pool_strdup(cmd->mem, vgname))) + goto_out; + } else { + if (strcmp(vp.old_vgname, vgname)) { + log_error("Devices must be from the same VG."); + goto_out; + } + } + } + + /* + * 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 (arg_is_set(cmd, basevgname_ARG)) { + snprintf(base_vgname, sizeof(base_vgname) - 1, "%s", arg_str_value(cmd, basevgname_ARG, "")); + memcpy(tmp_vgname, base_vgname, NAME_LEN); + vgname_count = 0; + } else { + snprintf(base_vgname, sizeof(base_vgname) - 1, "%s", vp.old_vgname); + snprintf(tmp_vgname, sizeof(tmp_vgname) - 1, "%s1", vp.old_vgname); + vgname_count = 1; + } + + if (!get_vgnameids(cmd, &vgnameids_on_system, NULL, 0)) + goto_out; + +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 (!(vp.new_vgname = dm_pool_strdup(cmd->mem, tmp_vgname))) + goto_out; + log_debug("Using new VG name %s.", vp.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. + */ + + init_internal_filtering(1); + dm_list_iterate_items(vd, &vp.arg_import) + internal_filter_allow(cmd->mem, vd->dev); + lvmcache_destroy(cmd, 1, 0); + dev_cache_full_scan(cmd->full_filter); + + log_debug("Changing VG %s to %s.", vp.old_vgname, vp.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, vp.new_vgname, LCK_VG_WRITE, NULL)) { + log_error("Can't get lock for new VG name %s", vp.new_vgname); + goto out; + } + + ret = process_each_vg(cmd, 0, NULL, vp.old_vgname, NULL, READ_FOR_UPDATE | READ_ALLOW_EXPORTED, 0, handle, _vgimportclone_vg_single); + + unlock_vg(cmd, vp.new_vgname); +out: + unlock_vg(cmd, VG_GLOBAL); + internal_filter_clear(); + init_internal_filtering(0); + lvmcache_lock_ordering(1); + destroy_processing_handle(cmd, handle); + + /* Enable lvmetad again if no duplicates are left. */ + if (lvmetad_rescan) { + if (!lvmetad_connect(cmd)) { + log_warn("WARNING: Failed to connect to lvmetad."); + log_warn("WARNING: Update lvmetad with pvscan --cache."); + return ret; + } + + if (!refresh_filters(cmd)) + stack; + + if (!lvmetad_pvscan_all_devs(cmd, 1)) { + log_warn("WARNING: Failed to scan devices."); + log_warn("WARNING: Update lvmetad with pvscan --cache."); + } + } + + return ret; +} + -- 2.43.5