summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Teigland <teigland@redhat.com>2016-05-16 14:35:43 -0500
committerDavid Teigland <teigland@redhat.com>2016-05-19 14:50:54 -0500
commit287530eea14ce859a015ee4c10b2a85b7011eb28 (patch)
tree1f08c7d13091cbe5f00c541609de6979708c2757
parentba9b7b69d9200d9d977089a0b813f508961fcbee (diff)
downloadlvm2-dev-dct-vgimportclone-1.tar.gz
pvchange: add importclone optiondev-dct-vgimportclone-1
This is a native implementation of vgimportclone. pvchange --importclone PV... All cloned PVs from the VG must be imported together.
-rw-r--r--lib/cache/lvmcache.c40
-rw-r--r--lib/cache/lvmcache.h5
-rw-r--r--lib/filters/filter-usable.c6
-rw-r--r--tools/args.h1
-rw-r--r--tools/commands.h2
-rw-r--r--tools/pvchange.c414
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 <libudev.h>
#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 : "<none>");
+
+ 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);