.RB [ \-h | \-? | \-\-help ]
.RB [ \-v | \-\-verbose ]
.RB [ \-\-version ]
+.RB [ \-T | \-\-thin
+.IR ExternalOriginLogicalVolume { Name | Path }
+.RB [ \-\-originname
+.IR NewExternalOriginVolumeName ]]
.RI [ PhysicalVolume [ Path ][ :PE [ -PE ]]...]
.sp
be specified on the commandline or a @tag may be used to specify
multiple snapshots be merged to their respective origin.
.TP
+.B \-\-originname \fINewExternalOriginVolumeName\fP
+The name for converted external origin volume.
+.br
+Without this option a default names of "lvol#" will be generated where
+# is the LVM internal number of the logical volume.
+Converted volume will be read-only.
+.TP
.BR \-\-poolmetadata " " \fIThinPoolMetadataLogicalVolume { \fIName | \fIPath }
Specifies thin pool metadata logical volume.
The size should be in between 2MiB and 16GiB.
For metadata in LVM2 format, the stripe size may be a larger
power of 2 but must not exceed the physical extent size.
.TP
+.IR \fB\-T ", " \fB\-\-thin " " ExternalOriginLogicalVolume { Name | Path }
+Changes the logical volume into a thin volume for the thin pool
+specified with the option \fB\-\-thinpool\fP. \fIExternalOriginLogicalVolume\fP
+is converted into a new read-only logical volume which will be used as an
+external origin volume for unprovisioned areas.
+The non-default name for this new volume can be specified with \fB\-\-originname\fP.
+.TP
.IR \fB\-\-thinpool " " ThinPoolLogicalVolume { Name | Path }
Changes logical volume into a thin pool volume. The volume
will store the pool's data.
.sp
.B lvconvert \-\-replace /dev/sdb1 vg00/my_raid1 /dev/sdf1
+Convert the logical volume "vg00/lvpool" into a thin pool with chunk size 128KiB
+and convert "vg00/lv1" into a thin volume using this pool. Original "vg00/lv1"
+is used as an external read-only origin, where all writes to such volume
+are stored in the "vg00/lvpool".
+.sp
+.B lvconvert \-\-thinpool vg00/lvpool -c 128 -T lv1
+
+Convert the logical volume "vg00/origin" into a thin volume from the thin pool
+"vg00/lvpool". This thin volume will use "vg00/origin" as an external origin
+volume for unprovisioned areas in this volume.
+For the read-only external origin use the new name "vg00/external".
+.sp
+.B lvconvert \-\-thinpool vg00/lvpool \-\-originname external -T vg00/origin
+
.SH SEE ALSO
.BR lvm (8),
.BR vgcreate (8),
/*
* Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
- * Copyright (C) 2004-2010 Red Hat, Inc. All rights reserved.
+ * Copyright (C) 2004-2013 Red Hat, Inc. All rights reserved.
*
* This file is part of LVM2.
*
arg(dataalignmentoffset_ARG, '\0', "dataalignmentoffset", size_kb_arg, 0)
arg(virtualoriginsize_ARG, '\0', "virtualoriginsize", size_mb_arg, 0)
arg(noudevsync_ARG, '\0', "noudevsync", NULL, 0)
+arg(originname_ARG, '\0', "originname", string_arg, 0)
arg(poll_ARG, '\0', "poll", yes_no_arg, 0)
arg(poolmetadata_ARG, '\0', "poolmetadata", string_arg, 0)
arg(poolmetadatasize_ARG, '\0', "poolmetadatasize", size_mb_arg, 0)
/*
* Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
- * Copyright (C) 2004-2012 Red Hat, Inc. All rights reserved.
+ * Copyright (C) 2004-2013 Red Hat, Inc. All rights reserved.
*
* This file is part of LVM2.
*
"\t [--poolmetadatasize size]\n"
"\t [-r|--readahead ReadAheadSectors|auto|none]\n"
"\t [--stripes Stripes [-I|--stripesize StripeSize]]]\n"
+ "\t[-T|--thin ExternalLogicalVolume[Path]\n"
+ "\t [--originname NewExternalOriginVolumeName]]\n"
"\t[-Z|--zero {y|n}]\n"
"\t[-d|--debug] [-h|-?|--help] [-v|--verbose]\n",
merge_ARG, mirrorlog_ARG, mirrors_ARG, name_ARG, noudevsync_ARG,
readahead_ARG, regionsize_ARG, repair_ARG, replace_ARG, snapshot_ARG, splitmirrors_ARG,
trackchanges_ARG, type_ARG, stripes_long_ARG, stripesize_ARG, test_ARG,
- chunksize_ARG, discards_ARG, poolmetadata_ARG, poolmetadatasize_ARG, thinpool_ARG,
+ chunksize_ARG, discards_ARG, poolmetadata_ARG, poolmetadatasize_ARG,
+ originname_ARG, thin_ARG, thinpool_ARG,
use_policies_ARG, yes_ARG, force_ARG, zero_ARG)
xx(lvcreate,
struct logical_volume *lv_to_poll;
uint64_t poolmetadata_size;
+ const char *origin_lv_name;
const char *pool_data_lv_name;
const char *pool_metadata_lv_name;
thin_discards_t discards;
{
char *ptr;
const char *vg_name = NULL;
+ const char *tmp_str;
if (lp->merge)
return 1;
if (lp->pool_data_lv_name) {
if (*pargc) {
- log_error("More then one logical volume name name specified.");
- return 0;
+ if (!arg_count(cmd, thin_ARG)) {
+ log_error("More then one logical volume name specified.");
+ return 0;
+ }
+ } else {
+ if (arg_count(cmd, thin_ARG)) {
+ log_error("External thin volume name is missing.");
+ return 0;
+ }
+
+ if (!lp->vg_name || !validate_name(lp->vg_name)) {
+ log_error("Please provide a valid volume group name.");
+ return 0;
+ }
+
+ lp->lv_name = lp->pool_data_lv_name;
+ return 1;
}
+ }
- if (!lp->vg_name || !validate_name(lp->vg_name)) {
- log_error("Please provide a valid volume group name.");
- return 0;
+ if (lp->origin_lv_name) {
+ /* FIXME: Using generic routine */
+ if (strchr(lp->origin_lv_name, '/')) {
+ if (!(lp->vg_name = extract_vgname(cmd, lp->origin_lv_name)))
+ return_0;
+ /* Strip VG from origin_lv_name */
+ if ((tmp_str = strrchr(lp->origin_lv_name, '/')))
+ lp->origin_lv_name = tmp_str + 1;
}
- lp->lv_name = lp->pool_data_lv_name;
- return 1; /* Create metadata LV on it's own */
}
if (!*pargc) {
return 0;
}
lp->discards = (thin_discards_t) arg_uint_value(cmd, discards_ARG, THIN_DISCARDS_PASSDOWN);
+ } else if (arg_count(cmd, thin_ARG)) {
+ log_error("--thin is only valid with --thinpool.");
+ return 0;
} else if (arg_count(cmd, discards_ARG)) {
log_error("--discards is only valid with --thinpool.");
return 0;
lp->pool_data_lv_name = tmp_str + 1;
}
+ if (arg_count(cmd, originname_ARG)) {
+ if (!(lp->origin_lv_name = arg_str_value(cmd, originname_ARG, NULL))) {
+ log_error("--originname is invalid.");
+ return 0;
+ }
+ }
+
lp->segtype = get_segtype_from_string(cmd, arg_str_value(cmd, type_ARG, "thin-pool"));
if (!lp->segtype)
return_0;
return r;
}
+/* Swap lvid and LV names */
+static int _swap_lv(struct cmd_context *cmd,
+ struct logical_volume *a, struct logical_volume *b)
+{
+ union lvid lvid;
+ const char *name;
+
+ lvid = a->lvid;
+ a->lvid = b->lvid;
+ b->lvid = lvid;
+
+ name = a->name;
+ a->name = b->name;
+ if (!lv_rename_update(cmd, b, name, 0))
+ return_0;
+
+ return 1;
+}
+
+static int _lvconvert_thinpool_external(struct cmd_context *cmd,
+ struct logical_volume *pool_lv,
+ struct logical_volume *external_lv,
+ struct lvconvert_params *lp)
+{
+ struct logical_volume *torigin_lv;
+ struct volume_group *vg = pool_lv->vg;
+ struct lvcreate_params lvc = { 0 };
+
+ dm_list_init(&lvc.tags);
+
+ if (!(lvc.segtype = get_segtype_from_string(cmd, "thin")))
+ return_0;
+
+ lvc.activate = CHANGE_AE;
+ lvc.alloc = ALLOC_INHERIT;
+ lvc.lv_name = lp->origin_lv_name;
+ lvc.major = -1;
+ lvc.minor = -1;
+ lvc.permission = LVM_READ;
+ lvc.pool = pool_lv->name;
+ lvc.pvh = &vg->pvs;
+ lvc.read_ahead = DM_READ_AHEAD_AUTO;
+ lvc.stripes = 1;
+ lvc.vg_name = vg->name;
+ lvc.voriginextents = external_lv->le_count;
+ lvc.voriginsize = external_lv->size;
+
+ /* New thin LV needs to be created (all messages sent to pool) */
+ if (!(torigin_lv = lv_create_single(vg, &lvc)))
+ return_0;
+
+ /* Activate again via -torigin, so this active LV is not needed */
+ if (!deactivate_lv(cmd, torigin_lv)) {
+ log_error("Aborting. Unable to deactivate new LV. "
+ "Manual intervention required.");
+ return 0;
+ }
+
+ /*
+ * Crashing till this point will leave plain thin volume
+ * which could be easily removed by the user after i.e. power-off
+ */
+
+ if (!_swap_lv(cmd, torigin_lv, external_lv)) {
+ stack;
+ goto revert_new_lv;
+ }
+
+ /* Preserve read-write status of original LV here */
+ torigin_lv->status |= (external_lv->status & LVM_WRITE);
+
+ if (!attach_thin_external_origin(first_seg(torigin_lv), external_lv)) {
+ stack;
+ goto revert_new_lv;
+ }
+
+ if (!_reload_lv(cmd, vg, torigin_lv)) {
+ stack;
+ goto deactivate_and_revert_new_lv;
+ }
+
+ log_print_unless_silent("Converted %s/%s to thin external origin.",
+ vg->name, external_lv->name);
+
+ return 1;
+
+deactivate_and_revert_new_lv:
+ if (!_swap_lv(cmd, torigin_lv, external_lv))
+ stack;
+
+ if (!deactivate_lv(cmd, torigin_lv)) {
+ log_error("Unable to deactivate failed new LV. "
+ "Manual intervention required.");
+ return 0;
+ }
+
+ if (!detach_thin_external_origin(first_seg(torigin_lv)))
+ return_0;
+
+revert_new_lv:
+ /* FIXME Better to revert to backup of metadata? */
+ if (!lv_remove(torigin_lv) || !vg_write(vg) || !vg_commit(vg))
+ log_error("Manual intervention may be required to remove "
+ "abandoned LV(s) before retrying.");
+ else
+ backup(vg);
+
+ return 0;
+}
+
/*
* Thin lvconvert version which
* rename metadata
struct logical_volume *data_lv;
struct logical_volume *metadata_lv;
struct logical_volume *pool_metadata_lv;
+ struct logical_volume *external_lv = NULL;
if (!lv_is_visible(pool_lv)) {
log_error("Can't convert internal LV %s/%s.",
return 0;
}
+ if (arg_count(cmd, thin_ARG)) {
+ external_lv = pool_lv;
+ if (!(pool_lv = find_lv(external_lv->vg, lp->pool_data_lv_name))) {
+ log_error("Can't find pool LV %s/%s.",
+ external_lv->vg->name, lp->pool_data_lv_name);
+ return 0;
+ }
+ if (lv_is_thin_pool(pool_lv)) {
+ r = 1; /* Already existing thin pool */
+ goto out;
+ }
+ }
+
if (lv_is_thin_type(pool_lv) && !lp->pool_metadata_lv_name) {
log_error("Can't use thin logical volume %s/%s for thin pool data.",
pool_lv->vg->name, pool_lv->name);
r = 1;
out:
+ if (r && external_lv &&
+ !(r = _lvconvert_thinpool_external(cmd, pool_lv, external_lv, lp)))
+ stack;
+
backup(pool_lv->vg);
+
return r;
}