From 3ae55695708b5d702f21daf776607d30cebe69c3 Mon Sep 17 00:00:00 2001 From: David Teigland Date: Mon, 27 Aug 2018 14:53:09 -0500 Subject: [PATCH] Add dm-writecache support dm-writecache is used like dm-cache with a standard LV as the cache. $ lvcreate -n main -L 128M -an foo /dev/loop0 $ lvcreate -n fast -L 32M -an foo /dev/pmem0 $ lvconvert --type writecache --cachepool fast foo/main $ lvs -a foo -o+devices LV VG Attr LSize Origin Devices [fast] foo -wi------- 32.00m /dev/pmem0(0) main foo Cwi------- 128.00m [main_wcorig] main_wcorig(0) [main_wcorig] foo -wi------- 128.00m /dev/loop0(0) $ lvchange -ay foo/main $ dmsetup table foo-main_wcorig: 0 262144 linear 7:0 2048 foo-main: 0 262144 writecache p 253:4 253:3 4096 0 foo-fast: 0 65536 linear 259:0 2048 $ lvchange -an foo/main $ lvconvert --splitcache foo/main $ lvs -a foo -o+devices LV VG Attr LSize Devices fast foo -wi------- 32.00m /dev/pmem0(0) main foo -wi------- 128.00m /dev/loop0(0) --- configure.ac | 18 + device_mapper/all.h | 48 +++ device_mapper/libdm-deptree.c | 134 +++++++ device_mapper/libdm-targets.c | 32 ++ lib/Makefile.in | 1 + lib/activate/activate.c | 20 + lib/activate/activate.h | 5 + lib/activate/dev_manager.c | 47 +++ lib/activate/dev_manager.h | 3 + lib/commands/toolcontext.c | 5 + lib/device/dev-type.c | 42 +++ lib/device/dev-type.h | 2 + lib/format_text/flags.c | 1 + lib/metadata/lv.c | 4 +- lib/metadata/lv_manip.c | 10 + lib/metadata/merge.c | 2 +- lib/metadata/metadata-exported.h | 8 + lib/metadata/metadata.c | 35 ++ lib/metadata/segtype.h | 6 + lib/writecache/writecache.c | 314 ++++++++++++++++ test/shell/writecache.sh | 129 +++++++ tools/args.h | 3 + tools/command-lines.in | 24 +- tools/lv_types.h | 1 + tools/lvconvert.c | 621 +++++++++++++++++++++++++++++-- tools/lvmcmdline.c | 7 +- tools/toollib.c | 13 +- tools/tools.h | 3 +- 28 files changed, 1481 insertions(+), 57 deletions(-) create mode 100644 lib/writecache/writecache.c create mode 100644 test/shell/writecache.sh diff --git a/configure.ac b/configure.ac index 70fd674b5..8549a80ae 100644 --- a/configure.ac +++ b/configure.ac @@ -642,6 +642,24 @@ AC_DEFINE_UNQUOTED([VDO_FORMAT_CMD], ["$VDO_FORMAT_CMD"], # VDO_LIB=$withval, VDO_LIB="/usr/lib") #AC_MSG_RESULT($VDO_LIB) +################################################################################ +dnl -- writecache inclusion type +AC_MSG_CHECKING(whether to include writecache) +AC_ARG_WITH(writecache, + AC_HELP_STRING([--with-writecache=TYPE], + [writecache support: internal/none [internal]]), + WRITECACHE=$withval, WRITECACHE="none") + +AC_MSG_RESULT($WRITECACHE) + +case "$WRITECACHE" in + none) ;; + internal) + AC_DEFINE([WRITECACHE_INTERNAL], 1, [Define to 1 to include built-in support for writecache.]) + ;; + *) AC_MSG_ERROR([--with-writecache parameter invalid]) ;; +esac + ################################################################################ dnl -- Disable readline AC_ARG_ENABLE([readline], diff --git a/device_mapper/all.h b/device_mapper/all.h index 0f01075ac..6fe80f8c3 100644 --- a/device_mapper/all.h +++ b/device_mapper/all.h @@ -378,6 +378,16 @@ struct dm_status_cache { int dm_get_status_cache(struct dm_pool *mem, const char *params, struct dm_status_cache **status); +struct dm_status_writecache { + uint32_t error; + uint64_t total_blocks; + uint64_t free_blocks; + uint64_t writeback_blocks; +}; + +int dm_get_status_writecache(struct dm_pool *mem, const char *params, + struct dm_status_writecache **status); + /* * Parse params from STATUS call for snapshot target * @@ -918,6 +928,44 @@ int dm_tree_node_add_cache_target(struct dm_tree_node *node, uint64_t data_len, uint32_t data_block_size); +struct writecache_settings { + uint64_t high_watermark; + uint64_t low_watermark; + uint64_t writeback_jobs; + uint64_t autocommit_blocks; + uint64_t autocommit_time; /* in milliseconds */ + uint32_t fua; + uint32_t nofua; + + /* + * Allow an unrecognized key and its val to be passed to the kernel for + * cases where a new kernel setting is added but lvm doesn't know about + * it yet. + */ + char *new_key; + char *new_val; + + /* + * Flag is 1 if a value has been set. + */ + unsigned high_watermark_set:1; + unsigned low_watermark_set:1; + unsigned writeback_jobs_set:1; + unsigned autocommit_blocks_set:1; + unsigned autocommit_time_set:1; + unsigned fua_set:1; + unsigned nofua_set:1; +}; + +int dm_tree_node_add_writecache_target(struct dm_tree_node *node, + uint64_t size, + const char *origin_uuid, + const char *cache_uuid, + int pmem, + uint32_t writecache_block_size, + struct writecache_settings *settings); + + /* * VDO target */ diff --git a/device_mapper/libdm-deptree.c b/device_mapper/libdm-deptree.c index 13239c7ba..89a0a4855 100644 --- a/device_mapper/libdm-deptree.c +++ b/device_mapper/libdm-deptree.c @@ -37,6 +37,7 @@ enum { SEG_SNAPSHOT_MERGE, SEG_STRIPED, SEG_ZERO, + SEG_WRITECACHE, SEG_THIN_POOL, SEG_THIN, SEG_VDO, @@ -76,6 +77,7 @@ static const struct { { SEG_SNAPSHOT_MERGE, "snapshot-merge" }, { SEG_STRIPED, "striped" }, { SEG_ZERO, "zero"}, + { SEG_WRITECACHE, "writecache"}, { SEG_THIN_POOL, "thin-pool"}, { SEG_THIN, "thin"}, { SEG_VDO, "vdo" }, @@ -212,6 +214,11 @@ struct load_segment { struct dm_tree_node *vdo_data; /* VDO */ struct dm_vdo_target_params vdo_params; /* VDO */ const char *vdo_name; /* VDO - device name is ALSO passed as table arg */ + + struct dm_tree_node *writecache_node; /* writecache */ + int writecache_pmem; /* writecache, 1 if pmem, 0 if ssd */ + uint32_t writecache_block_size; /* writecache, in bytes */ + struct writecache_settings writecache_settings; /* writecache */ }; /* Per-device properties */ @@ -2605,6 +2612,88 @@ static int _cache_emit_segment_line(struct dm_task *dmt, return 1; } +static int _writecache_emit_segment_line(struct dm_task *dmt, + struct load_segment *seg, + char *params, size_t paramsize) +{ + int pos = 0; + int count = 0; + uint32_t block_size; + char origin_dev[DM_FORMAT_DEV_BUFSIZE]; + char cache_dev[DM_FORMAT_DEV_BUFSIZE]; + + if (!_build_dev_string(origin_dev, sizeof(origin_dev), seg->origin)) + return_0; + + if (!_build_dev_string(cache_dev, sizeof(cache_dev), seg->writecache_node)) + return_0; + + if (seg->writecache_settings.high_watermark_set) + count += 2; + if (seg->writecache_settings.low_watermark_set) + count += 2; + if (seg->writecache_settings.writeback_jobs_set) + count += 2; + if (seg->writecache_settings.autocommit_blocks_set) + count += 2; + if (seg->writecache_settings.autocommit_time_set) + count += 2; + if (seg->writecache_settings.fua_set) + count += 1; + if (seg->writecache_settings.nofua_set) + count += 1; + if (seg->writecache_settings.new_key) + count += 2; + + if (!(block_size = seg->writecache_block_size)) + block_size = 4096; + + EMIT_PARAMS(pos, "%s %s %s %u %d", + seg->writecache_pmem ? "p" : "s", + origin_dev, cache_dev, block_size, count); + + if (seg->writecache_settings.high_watermark_set) { + EMIT_PARAMS(pos, " high_watermark %llu", + (unsigned long long)seg->writecache_settings.high_watermark); + } + + if (seg->writecache_settings.low_watermark_set) { + EMIT_PARAMS(pos, " low_watermark %llu", + (unsigned long long)seg->writecache_settings.low_watermark); + } + + if (seg->writecache_settings.writeback_jobs_set) { + EMIT_PARAMS(pos, " writeback_jobs %llu", + (unsigned long long)seg->writecache_settings.writeback_jobs); + } + + if (seg->writecache_settings.autocommit_blocks_set) { + EMIT_PARAMS(pos, " autocommit_blocks %llu", + (unsigned long long)seg->writecache_settings.autocommit_blocks); + } + + if (seg->writecache_settings.autocommit_time_set) { + EMIT_PARAMS(pos, " autocommit_time %llu", + (unsigned long long)seg->writecache_settings.autocommit_time); + } + + if (seg->writecache_settings.fua_set) { + EMIT_PARAMS(pos, " fua"); + } + + if (seg->writecache_settings.nofua_set) { + EMIT_PARAMS(pos, " nofua"); + } + + if (seg->writecache_settings.new_key) { + EMIT_PARAMS(pos, " %s %s", + seg->writecache_settings.new_key, + seg->writecache_settings.new_val); + } + + return 1; +} + static int _thin_pool_emit_segment_line(struct dm_task *dmt, struct load_segment *seg, char *params, size_t paramsize) @@ -2784,6 +2873,10 @@ static int _emit_segment_line(struct dm_task *dmt, uint32_t major, if (!_cache_emit_segment_line(dmt, seg, params, paramsize)) return_0; break; + case SEG_WRITECACHE: + if (!_writecache_emit_segment_line(dmt, seg, params, paramsize)) + return_0; + break; } switch(seg->type) { @@ -2795,6 +2888,7 @@ static int _emit_segment_line(struct dm_task *dmt, uint32_t major, case SEG_THIN_POOL: case SEG_THIN: case SEG_CACHE: + case SEG_WRITECACHE: break; case SEG_CRYPT: case SEG_LINEAR: @@ -3583,6 +3677,46 @@ int dm_tree_node_add_cache_target(struct dm_tree_node *node, return 1; } +int dm_tree_node_add_writecache_target(struct dm_tree_node *node, + uint64_t size, + const char *origin_uuid, + const char *cache_uuid, + int pmem, + uint32_t writecache_block_size, + struct writecache_settings *settings) +{ + struct load_segment *seg; + + if (!(seg = _add_segment(node, SEG_WRITECACHE, size))) + return_0; + + seg->writecache_pmem = pmem; + seg->writecache_block_size = writecache_block_size; + + if (!(seg->writecache_node = dm_tree_find_node_by_uuid(node->dtree, cache_uuid))) { + log_error("Missing writecache's cache uuid %s.", cache_uuid); + return 0; + } + if (!_link_tree_nodes(node, seg->writecache_node)) + return_0; + + if (!(seg->origin = dm_tree_find_node_by_uuid(node->dtree, origin_uuid))) { + log_error("Missing writecache's origin uuid %s.", origin_uuid); + return 0; + } + if (!_link_tree_nodes(node, seg->origin)) + return_0; + + memcpy(&seg->writecache_settings, settings, sizeof(struct writecache_settings)); + + if (settings->new_key && settings->new_val) { + seg->writecache_settings.new_key = dm_pool_strdup(node->dtree->mem, settings->new_key); + seg->writecache_settings.new_val = dm_pool_strdup(node->dtree->mem, settings->new_val); + } + + return 1; +} + int dm_tree_node_add_replicator_target(struct dm_tree_node *node, uint64_t size, const char *rlog_uuid, diff --git a/device_mapper/libdm-targets.c b/device_mapper/libdm-targets.c index 5ab4701bb..607f429e9 100644 --- a/device_mapper/libdm-targets.c +++ b/device_mapper/libdm-targets.c @@ -346,6 +346,38 @@ bad: return 0; } +/* + * From linux/Documentation/device-mapper/writecache.txt + * + * Status: + * 1. error indicator - 0 if there was no error, otherwise error number + * 2. the number of blocks + * 3. the number of free blocks + * 4. the number of blocks under writeback + */ + +int dm_get_status_writecache(struct dm_pool *mem, const char *params, + struct dm_status_writecache **status) +{ + struct dm_status_writecache *s; + + if (!(s = dm_pool_zalloc(mem, sizeof(struct dm_status_writecache)))) + return_0; + + if (sscanf(params, "%u %llu %llu %llu", + &s->error, + (unsigned long long *)&s->total_blocks, + (unsigned long long *)&s->free_blocks, + (unsigned long long *)&s->writeback_blocks) != 4) { + log_error("Failed to parse writecache params: %s.", params); + dm_pool_free(mem, s); + return 0; + } + + *status = s; + return 1; +} + int parse_thin_pool_status(const char *params, struct dm_status_thin_pool *s) { int pos; diff --git a/lib/Makefile.in b/lib/Makefile.in index 1b170ee56..722e95450 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -19,6 +19,7 @@ top_builddir = @top_builddir@ SOURCES =\ activate/activate.c \ cache/lvmcache.c \ + writecache/writecache.c \ cache_segtype/cache.c \ commands/toolcontext.c \ config/config.c \ diff --git a/lib/activate/activate.c b/lib/activate/activate.c index 1e195b6f3..b1f739136 100644 --- a/lib/activate/activate.c +++ b/lib/activate/activate.c @@ -1173,6 +1173,26 @@ out: return r; } +int lv_writecache_message(const struct logical_volume *lv, const char *msg) +{ + int r = 0; + struct dev_manager *dm; + + if (!lv_info(lv->vg->cmd, lv, 0, NULL, 0, 0)) { + log_error("Unable to send message to an inactive logical volume."); + return 0; + } + + if (!(dm = dev_manager_create(lv->vg->cmd, lv->vg->name, 1))) + return_0; + + r = dev_manager_writecache_message(dm, lv, msg); + + dev_manager_destroy(dm); + + return r; +} + /* * Return dm_status_cache for cache volume, accept also cache pool * diff --git a/lib/activate/activate.h b/lib/activate/activate.h index 9530c3649..8f9c91898 100644 --- a/lib/activate/activate.h +++ b/lib/activate/activate.h @@ -38,6 +38,7 @@ typedef enum { SEG_STATUS_THIN, SEG_STATUS_THIN_POOL, SEG_STATUS_VDO_POOL, + SEG_STATUS_WRITECACHE, SEG_STATUS_UNKNOWN } lv_seg_status_type_t; @@ -51,6 +52,7 @@ struct lv_seg_status { struct dm_status_snapshot *snapshot; struct dm_status_thin *thin; struct dm_status_thin_pool *thin_pool; + struct dm_status_writecache *writecache; struct lv_status_vdo vdo_pool; }; }; @@ -184,6 +186,7 @@ int lv_raid_dev_health(const struct logical_volume *lv, char **dev_health); int lv_raid_mismatch_count(const struct logical_volume *lv, uint64_t *cnt); int lv_raid_sync_action(const struct logical_volume *lv, char **sync_action); int lv_raid_message(const struct logical_volume *lv, const char *msg); +int lv_writecache_message(const struct logical_volume *lv, const char *msg); int lv_cache_status(const struct logical_volume *cache_lv, struct lv_status_cache **status); int lv_thin_pool_percent(const struct logical_volume *lv, int metadata, @@ -255,6 +258,7 @@ int device_is_usable(struct device *dev, struct dev_usable_check_params check); void fs_unlock(void); #define TARGET_NAME_CACHE "cache" +#define TARGET_NAME_WRITECACHE "writecache" #define TARGET_NAME_ERROR "error" #define TARGET_NAME_ERROR_OLD "erro" /* Truncated in older kernels */ #define TARGET_NAME_LINEAR "linear" @@ -271,6 +275,7 @@ void fs_unlock(void); #define MODULE_NAME_CLUSTERED_MIRROR "clog" #define MODULE_NAME_CACHE TARGET_NAME_CACHE +#define MODULE_NAME_WRITECACHE TARGET_NAME_WRITECACHE #define MODULE_NAME_ERROR TARGET_NAME_ERROR #define MODULE_NAME_LOG_CLUSTERED "log-clustered" #define MODULE_NAME_LOG_USERSPACE "log-userspace" diff --git a/lib/activate/dev_manager.c b/lib/activate/dev_manager.c index 124958158..49f425bbd 100644 --- a/lib/activate/dev_manager.c +++ b/lib/activate/dev_manager.c @@ -213,6 +213,10 @@ static int _get_segment_status_from_target_params(const char *target_name, if (!parse_vdo_pool_status(seg_status->mem, seg->lv, params, &seg_status->vdo_pool)) return_0; seg_status->type = SEG_STATUS_VDO_POOL; + } else if (segtype_is_writecache(segtype)) { + if (!dm_get_status_writecache(seg_status->mem, params, &(seg_status->writecache))) + return_0; + seg_status->type = SEG_STATUS_WRITECACHE; } else /* * TODO: Add support for other segment types too! @@ -1557,6 +1561,40 @@ out: return r; } +int dev_manager_writecache_message(struct dev_manager *dm, + const struct logical_volume *lv, + const char *msg) +{ + int r = 0; + const char *dlid; + struct dm_task *dmt; + const char *layer = lv_layer(lv); + + if (!lv_is_writecache(lv)) { + log_error(INTERNAL_ERROR "%s is not a writecache logical volume.", + display_lvname(lv)); + return 0; + } + + if (!(dlid = build_dm_uuid(dm->mem, lv, layer))) + return_0; + + if (!(dmt = _setup_task_run(DM_DEVICE_TARGET_MSG, NULL, NULL, dlid, 0, 0, 0, 0, 1, 0))) + return_0; + + if (!dm_task_set_message(dmt, msg)) + goto_out; + + if (!dm_task_run(dmt)) + goto_out; + + r = 1; +out: + dm_task_destroy(dmt); + + return r; +} + int dev_manager_cache_status(struct dev_manager *dm, const struct logical_volume *lv, struct lv_status_cache **status) @@ -2601,6 +2639,10 @@ static int _add_lv_to_dtree(struct dev_manager *dm, struct dm_tree *dtree, if (seg->metadata_lv && !_add_lv_to_dtree(dm, dtree, seg->metadata_lv, 0)) return_0; + if (seg->writecache && seg_is_writecache(seg)) { + if (!_add_lv_to_dtree(dm, dtree, seg->writecache, dm->activation ? origin_only : 1)) + return_0; + } if (seg->pool_lv && (lv_is_cache_pool(seg->pool_lv) || lv_is_cache_single(seg->pool_lv) || dm->track_external_lv_deps) && /* When activating and not origin_only detect linear 'overlay' over pool */ @@ -3053,6 +3095,11 @@ static int _add_segment_to_dtree(struct dev_manager *dm, lv_layer(seg->pool_lv))) return_0; + if (seg->writecache && !laopts->origin_only && + !_add_new_lv_to_dtree(dm, dtree, seg->writecache, laopts, + lv_layer(seg->writecache))) + return_0; + /* Add any LVs used by this segment */ for (s = 0; s < seg->area_count; ++s) { if ((seg_type(seg, s) == AREA_LV) && diff --git a/lib/activate/dev_manager.h b/lib/activate/dev_manager.h index b669bd260..ca8c0d38a 100644 --- a/lib/activate/dev_manager.h +++ b/lib/activate/dev_manager.h @@ -63,6 +63,9 @@ int dev_manager_raid_status(struct dev_manager *dm, int dev_manager_raid_message(struct dev_manager *dm, const struct logical_volume *lv, const char *msg); +int dev_manager_writecache_message(struct dev_manager *dm, + const struct logical_volume *lv, + const char *msg); int dev_manager_cache_status(struct dev_manager *dm, const struct logical_volume *lv, struct lv_status_cache **status); diff --git a/lib/commands/toolcontext.c b/lib/commands/toolcontext.c index 9f0059355..58d46ac00 100644 --- a/lib/commands/toolcontext.c +++ b/lib/commands/toolcontext.c @@ -1367,6 +1367,11 @@ static int _init_segtypes(struct cmd_context *cmd) return_0; #endif +#ifdef WRITECACHE_INTERNAL + if (!init_writecache_segtypes(cmd, &seglib)) + return 0; +#endif + #ifdef HAVE_LIBDL /* Load any formats in shared libs unless static */ if (!is_static() && diff --git a/lib/device/dev-type.c b/lib/device/dev-type.c index f2d193b2b..1f74fdcb7 100644 --- a/lib/device/dev-type.c +++ b/lib/device/dev-type.c @@ -35,6 +35,48 @@ #include "lib/device/device-types.h" +/* + * dev is pmem if /sys/dev/block/:/queue/dax is 1 + */ + +int dev_is_pmem(struct device *dev) +{ + FILE *fp; + char path[PATH_MAX]; + char buffer[64]; + int is_pmem = 0; + + if (dm_snprintf(path, sizeof(path), "%sdev/block/%d:%d/queue/dax", + dm_sysfs_dir(), + (int) MAJOR(dev->dev), + (int) MINOR(dev->dev)) < 0) { + log_warn("Sysfs path for %s dax is too long.", dev_name(dev)); + return 0; + } + + if (!(fp = fopen(path, "r"))) + return 0; + + if (!fgets(buffer, sizeof(buffer), fp)) { + log_warn("Failed to read %s.", path); + fclose(fp); + return 0; + } else if (sscanf(buffer, "%d", &is_pmem) != 1) { + log_warn("Failed to parse %s '%s'.", path, buffer); + fclose(fp); + return 0; + } + + fclose(fp); + + if (is_pmem) { + log_debug("%s is pmem", dev_name(dev)); + return 1; + } + + return 0; +} + struct dev_types *create_dev_types(const char *proc_dir, const struct dm_config_node *cn) { diff --git a/lib/device/dev-type.h b/lib/device/dev-type.h index 0e418d687..e8f0fcb7f 100644 --- a/lib/device/dev-type.h +++ b/lib/device/dev-type.h @@ -92,4 +92,6 @@ unsigned long dev_discard_granularity(struct dev_types *dt, struct device *dev); int dev_is_rotational(struct dev_types *dt, struct device *dev); +int dev_is_pmem(struct device *dev); + #endif diff --git a/lib/format_text/flags.c b/lib/format_text/flags.c index d7c43184e..cf5be0070 100644 --- a/lib/format_text/flags.c +++ b/lib/format_text/flags.c @@ -102,6 +102,7 @@ static const struct flag _lv_flags[] = { {LV_VDO, NULL, 0}, {LV_VDO_POOL, NULL, 0}, {LV_VDO_POOL_DATA, NULL, 0}, + {WRITECACHE, NULL, 0}, {LV_PENDING_DELETE, NULL, 0}, /* FIXME Display like COMPATIBLE_FLAG */ {LV_REMOVED, NULL, 0}, {0, NULL, 0} diff --git a/lib/metadata/lv.c b/lib/metadata/lv.c index cb064d851..0e543238e 100644 --- a/lib/metadata/lv.c +++ b/lib/metadata/lv.c @@ -578,6 +578,8 @@ struct logical_volume *lv_origin_lv(const struct logical_volume *lv) origin = first_seg(lv)->origin; else if (lv_is_thin_volume(lv) && first_seg(lv)->external_lv) origin = first_seg(lv)->external_lv; + else if (lv_is_writecache(lv) && first_seg(lv)->origin) + origin = first_seg(lv)->origin; return origin; } @@ -1192,7 +1194,7 @@ char *lv_attr_dup_with_info_and_seg_status(struct dm_pool *mem, const struct lv_ lv_is_pool_metadata_spare(lv) || lv_is_raid_metadata(lv)) repstr[0] = 'e'; - else if (lv_is_cache_type(lv)) + else if (lv_is_cache_type(lv) || lv_is_writecache(lv)) repstr[0] = 'C'; else if (lv_is_raid(lv)) repstr[0] = (lv_is_not_synced(lv)) ? 'R' : 'r'; diff --git a/lib/metadata/lv_manip.c b/lib/metadata/lv_manip.c index 8e64dac7d..d5603c775 100644 --- a/lib/metadata/lv_manip.c +++ b/lib/metadata/lv_manip.c @@ -1572,6 +1572,11 @@ int lv_reduce(struct logical_volume *lv, uint32_t extents) { struct lv_segment *seg = first_seg(lv); + if (lv_is_writecache(lv)) { + log_error("Remove not yet allowed on LVs with writecache attached."); + return 0; + } + /* Ensure stripe boundary extents on RAID LVs */ if (lv_is_raid(lv) && extents != lv->le_count) extents =_round_to_stripe_boundary(lv->vg, extents, @@ -5562,6 +5567,11 @@ int lv_resize(struct logical_volume *lv, int ret = 0; int status; + if (lv_is_writecache(lv)) { + log_error("Resize not yet allowed on LVs with writecache attached."); + return 0; + } + if (!_lvresize_check(lv, lp)) return_0; diff --git a/lib/metadata/merge.c b/lib/metadata/merge.c index d95da1f8d..bdd9c6757 100644 --- a/lib/metadata/merge.c +++ b/lib/metadata/merge.c @@ -710,7 +710,7 @@ int check_lv_segments(struct logical_volume *lv, int complete_vg) } if (seg->log_lv == lv) seg_found++; - if (seg->metadata_lv == lv || seg->pool_lv == lv) + if (seg->metadata_lv == lv || seg->pool_lv == lv || seg->writecache == lv) seg_found++; if (seg_is_thin_volume(seg) && (seg->origin == lv || seg->external_lv == lv)) seg_found++; diff --git a/lib/metadata/metadata-exported.h b/lib/metadata/metadata-exported.h index 30ab356ff..7f673cdf1 100644 --- a/lib/metadata/metadata-exported.h +++ b/lib/metadata/metadata-exported.h @@ -95,6 +95,7 @@ #define MERGING UINT64_C(0x0000000010000000) /* LV SEG */ #define UNLABELLED_PV UINT64_C(0x0000000080000000) /* PV -this PV had no label written yet */ +#define WRITECACHE UINT64_C(0x0000000080000000) /* LV - shared with UNLABELLED_PV */ #define RAID UINT64_C(0x0000000100000000) /* LV - Internal use only */ #define RAID_META UINT64_C(0x0000000200000000) /* LV - Internal use only */ @@ -258,6 +259,7 @@ #define lv_is_pool_metadata(lv) (((lv)->status & (CACHE_POOL_METADATA | THIN_POOL_METADATA)) ? 1 : 0) #define lv_is_pool_metadata_spare(lv) (((lv)->status & POOL_METADATA_SPARE) ? 1 : 0) #define lv_is_lockd_sanlock_lv(lv) (((lv)->status & LOCKD_SANLOCK_LV) ? 1 : 0) +#define lv_is_writecache(lv) (((lv)->status & WRITECACHE) ? 1 : 0) #define lv_is_vdo(lv) (((lv)->status & LV_VDO) ? 1 : 0) #define lv_is_vdo_pool(lv) (((lv)->status & LV_VDO_POOL) ? 1 : 0) @@ -509,6 +511,10 @@ struct lv_segment { struct dm_config_node *policy_settings; /* For cache_pool */ unsigned cleaner_policy; /* For cache */ + struct logical_volume *writecache; /* For writecache */ + uint32_t writecache_block_size; /* For writecache */ + struct writecache_settings writecache_settings; /* For writecache */ + struct dm_vdo_target_params vdo_params; /* For VDO-pool */ uint32_t vdo_pool_header_size; /* For VDO-pool */ uint32_t vdo_pool_virtual_extents; /* For VDO-pool */ @@ -1360,4 +1366,6 @@ int is_system_id_allowed(struct cmd_context *cmd, const char *system_id); int vg_strip_outdated_historical_lvs(struct volume_group *vg); +int lv_on_pmem(struct logical_volume *lv); + #endif diff --git a/lib/metadata/metadata.c b/lib/metadata/metadata.c index 57e184268..6c71c7439 100644 --- a/lib/metadata/metadata.c +++ b/lib/metadata/metadata.c @@ -5545,3 +5545,38 @@ int vg_strip_outdated_historical_lvs(struct volume_group *vg) { return 1; } + +int lv_on_pmem(struct logical_volume *lv) +{ + struct lv_segment *seg; + struct physical_volume *pv; + uint32_t s; + int pmem_devs = 0, other_devs = 0; + + dm_list_iterate_items(seg, &lv->segments) { + for (s = 0; s < seg->area_count; s++) { + pv = seg_pv(seg, s); + + if (dev_is_pmem(pv->dev)) { + log_debug("LV %s dev %s is pmem.", lv->name, dev_name(pv->dev)); + pmem_devs++; + } else { + log_debug("LV %s dev %s not pmem.", lv->name, dev_name(pv->dev)); + other_devs++; + } + } + } + + if (pmem_devs && other_devs) { + log_error("Invalid mix of cache device types in %s.", display_lvname(lv)); + return -1; + } + + if (pmem_devs) { + log_debug("LV %s on pmem", lv->name); + return 1; + } + + return 0; +} + diff --git a/lib/metadata/segtype.h b/lib/metadata/segtype.h index 6fdf075dd..22a511eac 100644 --- a/lib/metadata/segtype.h +++ b/lib/metadata/segtype.h @@ -66,6 +66,7 @@ struct dev_manager; #define SEG_RAID6_RS_6 (1ULL << 34) #define SEG_RAID6_N_6 (1ULL << 35) #define SEG_RAID6 SEG_RAID6_ZR +#define SEG_WRITECACHE (1ULL << 36) #define SEG_STRIPED_TARGET (1ULL << 39) #define SEG_LINEAR_TARGET (1ULL << 40) @@ -82,6 +83,7 @@ struct dev_manager; #define SEG_TYPE_NAME_THIN_POOL "thin-pool" #define SEG_TYPE_NAME_CACHE "cache" #define SEG_TYPE_NAME_CACHE_POOL "cache-pool" +#define SEG_TYPE_NAME_WRITECACHE "writecache" #define SEG_TYPE_NAME_ERROR "error" #define SEG_TYPE_NAME_FREE "free" #define SEG_TYPE_NAME_ZERO "zero" @@ -114,6 +116,7 @@ struct dev_manager; #define segtype_is_striped_target(segtype) ((segtype)->flags & SEG_STRIPED_TARGET ? 1 : 0) #define segtype_is_cache(segtype) ((segtype)->flags & SEG_CACHE ? 1 : 0) #define segtype_is_cache_pool(segtype) ((segtype)->flags & SEG_CACHE_POOL ? 1 : 0) +#define segtype_is_writecache(segtype) ((segtype)->flags & SEG_WRITECACHE ? 1 : 0) #define segtype_is_mirrored(segtype) ((segtype)->flags & SEG_AREAS_MIRRORED ? 1 : 0) #define segtype_is_mirror(segtype) ((segtype)->flags & SEG_MIRROR ? 1 : 0) #define segtype_is_pool(segtype) ((segtype)->flags & (SEG_CACHE_POOL | SEG_THIN_POOL) ? 1 : 0) @@ -175,6 +178,7 @@ struct dev_manager; #define seg_is_striped_target(seg) segtype_is_striped_target((seg)->segtype) #define seg_is_cache(seg) segtype_is_cache((seg)->segtype) #define seg_is_cache_pool(seg) segtype_is_cache_pool((seg)->segtype) +#define seg_is_writecache(seg) segtype_is_writecache((seg)->segtype) #define seg_is_used_cache_pool(seg) (seg_is_cache_pool(seg) && (!dm_list_empty(&(seg->lv)->segs_using_this_lv))) #define seg_is_linear(seg) (seg_is_striped(seg) && ((seg)->area_count == 1)) #define seg_is_mirror(seg) segtype_is_mirror((seg)->segtype) @@ -341,6 +345,8 @@ int init_cache_segtypes(struct cmd_context *cmd, struct segtype_library *seglib) int init_vdo_segtypes(struct cmd_context *cmd, struct segtype_library *seglib); #endif +int init_writecache_segtypes(struct cmd_context *cmd, struct segtype_library *seglib); + #define CACHE_FEATURE_POLICY_MQ (1U << 0) #define CACHE_FEATURE_POLICY_SMQ (1U << 1) #define CACHE_FEATURE_METADATA2 (1U << 2) diff --git a/lib/writecache/writecache.c b/lib/writecache/writecache.c new file mode 100644 index 000000000..e9d337b1a --- /dev/null +++ b/lib/writecache/writecache.c @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2013-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 "base/memory/zalloc.h" +#include "lib/misc/lib.h" +#include "lib/commands/toolcontext.h" +#include "lib/metadata/segtype.h" +#include "lib/display/display.h" +#include "lib/format_text/text_export.h" +#include "lib/config/config.h" +#include "lib/datastruct/str_list.h" +#include "lib/misc/lvm-string.h" +#include "lib/activate/activate.h" +#include "lib/metadata/metadata.h" +#include "lib/metadata/lv_alloc.h" +#include "lib/config/defaults.h" + +static const char _writecache_module[] = "writecache"; + +#define SEG_LOG_ERROR(t, p...) \ + log_error(t " segment %s of logical volume %s.", ## p, \ + dm_config_parent_name(sn), seg->lv->name), 0; + +static void _writecache_display(const struct lv_segment *seg) +{ + /* TODO: lvdisplay segments */ +} + +static int _writecache_text_import(struct lv_segment *seg, + const struct dm_config_node *sn, + struct dm_hash_table *pv_hash __attribute__((unused))) +{ + struct logical_volume *origin_lv = NULL; + struct logical_volume *fast_lv; + const char *origin_name = NULL; + const char *fast_name = NULL; + + if (!dm_config_has_node(sn, "origin")) + return SEG_LOG_ERROR("origin not specified in"); + + if (!dm_config_get_str(sn, "origin", &origin_name)) + return SEG_LOG_ERROR("origin must be a string in"); + + if (!(origin_lv = find_lv(seg->lv->vg, origin_name))) + return SEG_LOG_ERROR("Unknown LV specified for writecache origin %s in", origin_name); + + if (!set_lv_segment_area_lv(seg, 0, origin_lv, 0, 0)) + return_0; + + if (!dm_config_has_node(sn, "writecache")) + return SEG_LOG_ERROR("writecache not specified in"); + + if (!dm_config_get_str(sn, "writecache", &fast_name)) + return SEG_LOG_ERROR("writecache must be a string in"); + + if (!(fast_lv = find_lv(seg->lv->vg, fast_name))) + return SEG_LOG_ERROR("Unknown logical volume %s specified for writecache in", + fast_name); + + if (!dm_config_get_uint32(sn, "writecache_block_size", &seg->writecache_block_size)) + return SEG_LOG_ERROR("writecache block_size must be set in"); + + seg->origin = origin_lv; + seg->writecache = fast_lv; + seg->lv->status |= WRITECACHE; + + if (!add_seg_to_segs_using_this_lv(fast_lv, seg)) + return_0; + + memset(&seg->writecache_settings, 0, sizeof(struct writecache_settings)); + + if (dm_config_has_node(sn, "high_watermark")) { + if (!dm_config_get_uint64(sn, "high_watermark", &seg->writecache_settings.high_watermark)) + return SEG_LOG_ERROR("Unknown writecache_setting in"); + seg->writecache_settings.high_watermark_set = 1; + } + + if (dm_config_has_node(sn, "low_watermark")) { + if (!dm_config_get_uint64(sn, "low_watermark", &seg->writecache_settings.low_watermark)) + return SEG_LOG_ERROR("Unknown writecache_setting in"); + seg->writecache_settings.low_watermark_set = 1; + } + + if (dm_config_has_node(sn, "writeback_jobs")) { + if (!dm_config_get_uint64(sn, "writeback_jobs", &seg->writecache_settings.writeback_jobs)) + return SEG_LOG_ERROR("Unknown writecache_setting in"); + seg->writecache_settings.writeback_jobs_set = 1; + } + + if (dm_config_has_node(sn, "autocommit_blocks")) { + if (!dm_config_get_uint64(sn, "autocommit_blocks", &seg->writecache_settings.autocommit_blocks)) + return SEG_LOG_ERROR("Unknown writecache_setting in"); + seg->writecache_settings.autocommit_blocks_set = 1; + } + + if (dm_config_has_node(sn, "autocommit_time")) { + if (!dm_config_get_uint64(sn, "autocommit_time", &seg->writecache_settings.autocommit_time)) + return SEG_LOG_ERROR("Unknown writecache_setting in"); + seg->writecache_settings.autocommit_time_set = 1; + } + + if (dm_config_has_node(sn, "fua")) { + if (!dm_config_get_uint32(sn, "fua", &seg->writecache_settings.fua)) + return SEG_LOG_ERROR("Unknown writecache_setting in"); + seg->writecache_settings.fua_set = 1; + } + + if (dm_config_has_node(sn, "nofua")) { + if (!dm_config_get_uint32(sn, "nofua", &seg->writecache_settings.nofua)) + return SEG_LOG_ERROR("Unknown writecache_setting in"); + seg->writecache_settings.nofua_set = 1; + } + + if (dm_config_has_node(sn, "writecache_setting_key")) { + const char *key; + const char *val; + + if (!dm_config_get_str(sn, "writecache_setting_key", &key)) + return SEG_LOG_ERROR("Unknown writecache_setting in"); + if (!dm_config_get_str(sn, "writecache_setting_val", &val)) + return SEG_LOG_ERROR("Unknown writecache_setting in"); + + seg->writecache_settings.new_key = dm_pool_strdup(seg->lv->vg->vgmem, key); + seg->writecache_settings.new_val = dm_pool_strdup(seg->lv->vg->vgmem, val); + } + + return 1; +} + +static int _writecache_text_import_area_count(const struct dm_config_node *sn, + uint32_t *area_count) +{ + *area_count = 1; + + return 1; +} + +static int _writecache_text_export(const struct lv_segment *seg, + struct formatter *f) +{ + outf(f, "writecache = \"%s\"", seg->writecache->name); + outf(f, "origin = \"%s\"", seg_lv(seg, 0)->name); + outf(f, "writecache_block_size = %u", seg->writecache_block_size); + + if (seg->writecache_settings.high_watermark_set) { + outf(f, "high_watermark = %llu", + (unsigned long long)seg->writecache_settings.high_watermark); + } + + if (seg->writecache_settings.low_watermark_set) { + outf(f, "low_watermark = %llu", + (unsigned long long)seg->writecache_settings.low_watermark); + } + + if (seg->writecache_settings.writeback_jobs_set) { + outf(f, "writeback_jobs = %llu", + (unsigned long long)seg->writecache_settings.writeback_jobs); + } + + if (seg->writecache_settings.autocommit_blocks_set) { + outf(f, "autocommit_blocks = %llu", + (unsigned long long)seg->writecache_settings.autocommit_blocks); + } + + if (seg->writecache_settings.autocommit_time_set) { + outf(f, "autocommit_time = %llu", + (unsigned long long)seg->writecache_settings.autocommit_time); + } + + if (seg->writecache_settings.fua_set) { + outf(f, "fua = %u", seg->writecache_settings.fua); + } + + if (seg->writecache_settings.nofua_set) { + outf(f, "nofua = %u", seg->writecache_settings.nofua); + } + + if (seg->writecache_settings.new_key && seg->writecache_settings.new_val) { + outf(f, "writecache_setting_key = \"%s\"", + seg->writecache_settings.new_key); + + outf(f, "writecache_setting_val = \"%s\"", + seg->writecache_settings.new_val); + } + + return 1; +} + +static void _destroy(struct segment_type *segtype) +{ + free((void *) segtype); +} + +#ifdef DEVMAPPER_SUPPORT + +static int _target_present(struct cmd_context *cmd, + const struct lv_segment *seg __attribute__((unused)), + unsigned *attributes __attribute__((unused))) +{ + static int _writecache_checked = 0; + static int _writecache_present = 0; + + if (!activation()) + return 0; + + if (!_writecache_checked) { + _writecache_checked = 1; + _writecache_present = target_present(cmd, TARGET_NAME_WRITECACHE, 0); + } + + return _writecache_present; +} + +static int _modules_needed(struct dm_pool *mem, + const struct lv_segment *seg __attribute__((unused)), + struct dm_list *modules) +{ + if (!str_list_add(mem, modules, MODULE_NAME_WRITECACHE)) { + log_error("String list allocation failed for writecache module."); + return 0; + } + + return 1; +} +#endif /* DEVMAPPER_SUPPORT */ + +#ifdef DEVMAPPER_SUPPORT +static int _writecache_add_target_line(struct dev_manager *dm, + struct dm_pool *mem, + struct cmd_context *cmd __attribute__((unused)), + void **target_state __attribute__((unused)), + struct lv_segment *seg, + const struct lv_activate_opts *laopts __attribute__((unused)), + struct dm_tree_node *node, uint64_t len, + uint32_t *pvmove_mirror_count __attribute__((unused))) +{ + char *origin_uuid; + char *fast_uuid; + int pmem; + + if (!seg_is_writecache(seg)) { + log_error(INTERNAL_ERROR "Passed segment is not writecache."); + return 0; + } + + if (!seg->writecache) { + log_error(INTERNAL_ERROR "Passed segment has no writecache."); + return 0; + } + + if ((pmem = lv_on_pmem(seg->writecache)) < 0) + return_0; + + if (!(origin_uuid = build_dm_uuid(mem, seg_lv(seg, 0), NULL))) + return_0; + + if (!(fast_uuid = build_dm_uuid(mem, seg->writecache, NULL))) + return_0; + + if (!dm_tree_node_add_writecache_target(node, len, + origin_uuid, fast_uuid, + pmem, + seg->writecache_block_size, + &seg->writecache_settings)) + return_0; + + return 1; +} +#endif /* DEVMAPPER_SUPPORT */ + +static struct segtype_handler _writecache_ops = { + .display = _writecache_display, + .text_import = _writecache_text_import, + .text_import_area_count = _writecache_text_import_area_count, + .text_export = _writecache_text_export, +#ifdef DEVMAPPER_SUPPORT + .add_target_line = _writecache_add_target_line, + .target_present = _target_present, + .modules_needed = _modules_needed, +#endif + .destroy = _destroy, +}; + +int init_writecache_segtypes(struct cmd_context *cmd, + struct segtype_library *seglib) +{ + struct segment_type *segtype = zalloc(sizeof(*segtype)); + + if (!segtype) { + log_error("Failed to allocate memory for writecache segtype"); + return 0; + } + + segtype->name = SEG_TYPE_NAME_WRITECACHE; + segtype->flags = SEG_WRITECACHE; + segtype->ops = &_writecache_ops; + + if (!lvm_register_segtype(seglib, segtype)) + return_0; + log_very_verbose("Initialised segtype: %s", segtype->name); + + return 1; +} diff --git a/test/shell/writecache.sh b/test/shell/writecache.sh new file mode 100644 index 000000000..19cc93bfb --- /dev/null +++ b/test/shell/writecache.sh @@ -0,0 +1,129 @@ +#!/usr/bin/env bash + +# Copyright (C) 2017 Red Hat, Inc. All rights reserved. +# +# 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 General Public License v.2. +# +# You should have received a copy of the GNU 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 + +# Test writecache usage + +SKIP_WITH_LVMPOLLD=1 + +. lib/inittest + +mount_dir="mnt" +mkdir -p $mount_dir + +# generate random data +dmesg > pattern1 +ps aux >> pattern1 + +aux prepare_devs 2 64 + +vgcreate $SHARED $vg "$dev1" + +vgextend $vg "$dev2" + +lvcreate -n $lv1 -l 8 -an $vg "$dev1" + +lvcreate -n $lv2 -l 4 -an $vg "$dev2" + +# test1: create fs on LV before writecache is attached + +lvchange -ay $vg/$lv1 + +mkfs.xfs -f -s size=4096 "$DM_DEV_DIR/$vg/$lv1" + +mount "$DM_DEV_DIR/$vg/$lv1" $mount_dir + +cp pattern1 $mount_dir/pattern1 + +umount $mount_dir +lvchange -an $vg/$lv1 + +lvconvert --type writecache --cachepool $lv2 $vg/$lv1 + +check lv_field $vg/$lv1 segtype writecache + +lvs -a $vg/$lv2 --noheadings -o segtype >out +grep linear out + +lvchange -ay $vg/$lv1 + +mount "$DM_DEV_DIR/$vg/$lv1" $mount_dir + +diff pattern1 $mount_dir/pattern1 + +cp pattern1 $mount_dir/pattern1b + +ls -l $mount_dir + +umount $mount_dir + +lvchange -an $vg/$lv1 + +lvconvert --splitcache $vg/$lv1 + +check lv_field $vg/$lv1 segtype linear +check lv_field $vg/$lv2 segtype linear + +lvchange -ay $vg/$lv1 +lvchange -ay $vg/$lv2 + +mount "$DM_DEV_DIR/$vg/$lv1" $mount_dir + +ls -l $mount_dir + +diff pattern1 $mount_dir/pattern1 +diff pattern1 $mount_dir/pattern1b + +umount $mount_dir +lvchange -an $vg/$lv1 +lvchange -an $vg/$lv2 + +# test2: create fs on LV after writecache is attached + +lvconvert --type writecache --cachepool $lv2 $vg/$lv1 + +check lv_field $vg/$lv1 segtype writecache + +lvs -a $vg/$lv2 --noheadings -o segtype >out +grep linear out + +lvchange -ay $vg/$lv1 + +mkfs.xfs -f -s size=4096 "$DM_DEV_DIR/$vg/$lv1" + +mount "$DM_DEV_DIR/$vg/$lv1" $mount_dir + +cp pattern1 $mount_dir/pattern1 +ls -l $mount_dir + +umount $mount_dir +lvchange -an $vg/$lv1 + +lvconvert --splitcache $vg/$lv1 + +check lv_field $vg/$lv1 segtype linear +check lv_field $vg/$lv2 segtype linear + +lvchange -ay $vg/$lv1 +lvchange -ay $vg/$lv2 + +mount "$DM_DEV_DIR/$vg/$lv1" $mount_dir + +ls -l $mount_dir + +diff pattern1 $mount_dir/pattern1 + +umount $mount_dir +lvchange -an $vg/$lv1 +lvchange -an $vg/$lv2 + +vgremove -ff $vg + diff --git a/tools/args.h b/tools/args.h index adca84b2e..cc65be541 100644 --- a/tools/args.h +++ b/tools/args.h @@ -816,6 +816,9 @@ arg(withversions_ARG, '\0', "withversions", 0, 0, 0, "each configuration node. If the setting is deprecated, also display\n" "the version since which it is deprecated.\n") +arg(writecacheblocksize_ARG, '\0', "writecacheblocksize", sizekb_VAL, 0, 0, + "The block size to use for cache blocks in writecache.\n") + arg(writebehind_ARG, '\0', "writebehind", number_VAL, 0, 0, "The maximum number of outstanding writes that are allowed to\n" "devices in a RAID1 LV that is marked write-mostly.\n" diff --git a/tools/command-lines.in b/tools/command-lines.in index 840d771df..a8e72725b 100644 --- a/tools/command-lines.in +++ b/tools/command-lines.in @@ -454,7 +454,7 @@ RULE: --poolmetadata not --readahead --stripesize --stripes_long lvconvert --type cache --cachepool LV LV_linear_striped_raid_thinpool OO: --cache, OO_LVCONVERT_CACHE, OO_LVCONVERT_POOL, OO_LVCONVERT ID: lvconvert_to_cache_vol -DESC: Convert LV to type cache. +DESC: Attach a cache to an LV, converts the LV to type cache. RULE: all and lv_is_visible RULE: --poolmetadata not --readahead --stripesize --stripes_long @@ -462,13 +462,21 @@ RULE: --poolmetadata not --readahead --stripesize --stripes_long lvconvert --cache --cachepool LV LV_linear_striped_raid_thinpool OO: --type cache, OO_LVCONVERT_CACHE, OO_LVCONVERT_POOL, OO_LVCONVERT ID: lvconvert_to_cache_vol -DESC: Convert LV to type cache (infers --type cache). +DESC: Attach a cache to an LV (infers --type cache). RULE: all and lv_is_visible RULE: --poolmetadata not --readahead --stripesize --stripes_long FLAGS: SECONDARY_SYNTAX --- +lvconvert --type writecache --cachepool LV LV_linear_striped_raid +OO: OO_LVCONVERT, --cachesettings String, --writecacheblocksize SizeKB +ID: lvconvert_to_writecache_vol +DESC: Attach a writecache to an LV, converts the LV to type writecache. +RULE: all and lv_is_visible + +--- + lvconvert --type thin-pool LV_linear_striped_raid_cache OO: --stripes_long Number, --stripesize SizeKB, --discards Discards, OO_LVCONVERT_POOL, OO_LVCONVERT @@ -573,17 +581,17 @@ FLAGS: SECONDARY_SYNTAX --- -lvconvert --splitcache LV_cachepool_cache_thinpool +lvconvert --splitcache LV_cachepool_cache_thinpool_writecache OO: OO_LVCONVERT -ID: lvconvert_split_and_keep_cachepool -DESC: Separate and keep the cache pool from a cache LV. +ID: lvconvert_split_and_keep_cache +DESC: Detach a cache from an LV. --- -lvconvert --uncache LV_cache_thinpool +lvconvert --uncache LV_cache_thinpool_writecache OO: OO_LVCONVERT -ID: lvconvert_split_and_remove_cachepool -DESC: Separate and delete the cache pool from a cache LV. +ID: lvconvert_split_and_remove_cache +DESC: Detach and delete a cache from an LV. FLAGS: SECONDARY_SYNTAX --- diff --git a/tools/lv_types.h b/tools/lv_types.h index 494776b81..778cd541d 100644 --- a/tools/lv_types.h +++ b/tools/lv_types.h @@ -33,5 +33,6 @@ lvt(raid6_LVT, "raid6", NULL) lvt(raid10_LVT, "raid10", NULL) lvt(error_LVT, "error", NULL) lvt(zero_LVT, "zero", NULL) +lvt(writecache_LVT, "writecache", NULL) lvt(LVT_COUNT, "", NULL) diff --git a/tools/lvconvert.c b/tools/lvconvert.c index 7382ce00d..a207bd9eb 100644 --- a/tools/lvconvert.c +++ b/tools/lvconvert.c @@ -16,6 +16,7 @@ #include "lib/lvmpolld/polldaemon.h" #include "lib/metadata/lv_alloc.h" +#include "lib/metadata/metadata.h" #include "lvconvert_poll.h" #define MAX_PDATA_ARGS 10 /* Max number of accepted args for d-m-p-d tools */ @@ -1840,13 +1841,13 @@ static int _lvconvert_splitsnapshot(struct cmd_context *cmd, struct logical_volu return 1; } -static int _lvconvert_split_and_keep_cachepool(struct cmd_context *cmd, +static int _lvconvert_split_and_keep_cache(struct cmd_context *cmd, struct logical_volume *lv, - struct logical_volume *cachepool_lv) + struct logical_volume *lv_fast) { struct lv_segment *cache_seg = first_seg(lv); - log_debug("Detaching cache %s from LV %s.", display_lvname(cachepool_lv), display_lvname(lv)); + log_debug("Detaching cache %s from LV %s.", display_lvname(lv_fast), display_lvname(lv)); if (!archive(lv->vg)) return_0; @@ -1865,12 +1866,12 @@ static int _lvconvert_split_and_keep_cachepool(struct cmd_context *cmd, backup(lv->vg); log_print_unless_silent("Logical volume %s is not cached and cache pool %s is unused.", - display_lvname(lv), display_lvname(cachepool_lv)); + display_lvname(lv), display_lvname(lv_fast)); return 1; } -static int _lvconvert_split_and_remove_cachepool(struct cmd_context *cmd, +static int _lvconvert_split_and_remove_cache(struct cmd_context *cmd, struct logical_volume *lv, struct logical_volume *cachepool_lv) { @@ -4504,63 +4505,83 @@ int lvconvert_merge_thin_cmd(struct cmd_context *cmd, int argc, char **argv) NULL, NULL, &_lvconvert_merge_thin_single); } -static int _lvconvert_split_cachepool_single(struct cmd_context *cmd, +static int _lvconvert_detach_writecache(struct cmd_context *cmd, + struct logical_volume *lv, + struct logical_volume *lv_fast); + +static int _lvconvert_split_cache_single(struct cmd_context *cmd, struct logical_volume *lv, struct processing_handle *handle) { - struct logical_volume *cache_lv = NULL; - struct logical_volume *cachepool_lv = NULL; + struct logical_volume *lv_main = NULL; + struct logical_volume *lv_fast = NULL; struct lv_segment *seg; int ret; - if (lv_is_cache(lv)) { - cache_lv = lv; - cachepool_lv = first_seg(cache_lv)->pool_lv; + if (lv_is_writecache(lv)) { + lv_main = lv; + lv_fast = first_seg(lv_main)->writecache; + + } else if (lv_is_cache(lv)) { + lv_main = lv; + lv_fast = first_seg(lv_main)->pool_lv; } else if (lv_is_cache_pool(lv)) { - cachepool_lv = lv; + lv_fast = lv; - if ((dm_list_size(&cachepool_lv->segs_using_this_lv) == 1) && - (seg = get_only_segment_using_this_lv(cachepool_lv)) && + if ((dm_list_size(&lv_fast->segs_using_this_lv) == 1) && + (seg = get_only_segment_using_this_lv(lv_fast)) && seg_is_cache(seg)) - cache_lv = seg->lv; + lv_main = seg->lv; } else if (lv_is_thin_pool(lv)) { - cache_lv = seg_lv(first_seg(lv), 0); /* cached _tdata */ - cachepool_lv = first_seg(cache_lv)->pool_lv; + lv_main = seg_lv(first_seg(lv), 0); /* cached _tdata */ + lv_fast = first_seg(lv_main)->pool_lv; } - if (!cache_lv) { - log_error("Cannot find cache LV from %s.", display_lvname(lv)); + if (!lv_main) { + log_error("Cannot find LV with cache from %s.", display_lvname(lv)); return ECMD_FAILED; } - if (!cachepool_lv) { - log_error("Cannot find cache pool LV from %s.", display_lvname(lv)); + if (!lv_fast) { + log_error("Cannot find cache %s.", display_lvname(lv)); return ECMD_FAILED; } - if ((cmd->command->command_enum == lvconvert_split_and_remove_cachepool_CMD) && - lv_is_cache_single(cachepool_lv)) { - log_error("Detach cache from %s with --splitcache.", display_lvname(lv)); - log_error("The cache %s may then be removed with lvremove.", display_lvname(cachepool_lv)); - return 0; - } - /* If LV is inactive here, ensure it's not active elsewhere. */ - if (!lockd_lv(cmd, cache_lv, "ex", 0)) + if (!lockd_lv(cmd, lv_main, "ex", 0)) return_0; - switch (cmd->command->command_enum) { - case lvconvert_split_and_keep_cachepool_CMD: - ret = _lvconvert_split_and_keep_cachepool(cmd, cache_lv, cachepool_lv); - break; + if (lv_is_writecache(lv_main)) { + if (cmd->command->command_enum == lvconvert_split_and_remove_cache_CMD) { + log_error("Detach cache from %s with --splitcache.", display_lvname(lv)); + log_error("The writecache %s may then be removed with lvremove.", display_lvname(lv_fast)); + return 0; + } - case lvconvert_split_and_remove_cachepool_CMD: - ret = _lvconvert_split_and_remove_cachepool(cmd, cache_lv, cachepool_lv); - break; - default: - log_error(INTERNAL_ERROR "Unknown cache pool split."); + ret = _lvconvert_detach_writecache(cmd, lv_main, lv_fast); + + } else if (lv_is_cache(lv_main)) { + if ((cmd->command->command_enum == lvconvert_split_and_remove_cache_CMD) && + lv_is_cache_single(lv_fast)) { + log_error("Detach cache from %s with --splitcache.", display_lvname(lv)); + log_error("The cache %s may then be removed with lvremove.", display_lvname(lv_fast)); + return 0; + } + + if (cmd->command->command_enum == lvconvert_split_and_remove_cache_CMD) + ret = _lvconvert_split_and_remove_cache(cmd, lv_main, lv_fast); + + else if (cmd->command->command_enum == lvconvert_split_and_keep_cache_CMD) + ret = _lvconvert_split_and_keep_cache(cmd, lv_main, lv_fast); + + else { + log_error(INTERNAL_ERROR "Unknown cache split command."); + ret = 0; + } + } else { + log_error(INTERNAL_ERROR "Unknown cache split command."); ret = 0; } @@ -4570,15 +4591,15 @@ static int _lvconvert_split_cachepool_single(struct cmd_context *cmd, return ECMD_PROCESSED; } -int lvconvert_split_cachepool_cmd(struct cmd_context *cmd, int argc, char **argv) +int lvconvert_split_cache_cmd(struct cmd_context *cmd, int argc, char **argv) { - if (cmd->command->command_enum == lvconvert_split_and_remove_cachepool_CMD) { + if (cmd->command->command_enum == lvconvert_split_and_remove_cache_CMD) { cmd->handles_missing_pvs = 1; cmd->partial_activation = 1; } return process_each_lv(cmd, 1, cmd->position_argv, NULL, NULL, READ_FOR_UPDATE, - NULL, NULL, &_lvconvert_split_cachepool_single); + NULL, NULL, &_lvconvert_split_cache_single); } static int _lvconvert_raid_types_single(struct cmd_context *cmd, struct logical_volume *lv, @@ -5041,6 +5062,524 @@ int lvconvert_to_vdopool_param_cmd(struct cmd_context *cmd, int argc, char **arg NULL, NULL, &_lvconvert_to_vdopool_single); } +static int _lv_writecache_detach(struct cmd_context *cmd, struct logical_volume *lv, + struct logical_volume *lv_fast) +{ + struct lv_segment *seg = first_seg(lv); + struct logical_volume *origin; + + if (!seg_is_writecache(seg)) { + log_error("LV %s segment is not writecache.", display_lvname(lv)); + return 0; + } + + if (!seg->writecache) { + log_error("LV %s writecache segment has no writecache.", display_lvname(lv)); + return 0; + } + + if (!(origin = seg_lv(seg, 0))) { + log_error("LV %s writecache segment has no origin", display_lvname(lv)); + return 0; + } + + if (!remove_seg_from_segs_using_this_lv(seg->writecache, seg)) + return_0; + + lv_set_visible(seg->writecache); + + lv->status &= ~WRITECACHE; + seg->writecache = NULL; + + if (!remove_layer_from_lv(lv, origin)) + return_0; + + if (!lv_remove(origin)) + return_0; + + return 1; +} + +static int _get_writecache_kernel_error(struct cmd_context *cmd, + struct logical_volume *lv, + uint32_t *kernel_error) +{ + struct lv_with_info_and_seg_status status; + + memset(&status, 0, sizeof(status)); + status.seg_status.type = SEG_STATUS_NONE; + + status.seg_status.seg = first_seg(lv); + + /* FIXME: why reporter_pool? */ + if (!(status.seg_status.mem = dm_pool_create("reporter_pool", 1024))) { + log_error("Failed to get mem for LV status."); + return 0; + } + + if (!lv_info_with_seg_status(cmd, first_seg(lv), &status, 1, 1)) { + log_error("Failed to get device mapper status for %s", display_lvname(lv)); + goto fail; + } + + if (!status.info.exists) { + log_error("No device mapper info exists for %s", display_lvname(lv)); + goto fail; + } + + if (status.seg_status.type != SEG_STATUS_WRITECACHE) { + log_error("Invalid device mapper status type (%d) for %s", + (uint32_t)status.seg_status.type, display_lvname(lv)); + goto fail; + } + + *kernel_error = status.seg_status.writecache->error; + + dm_pool_destroy(status.seg_status.mem); + return 1; + +fail: + dm_pool_destroy(status.seg_status.mem); + return 0; +} + +/* + * TODO: add a new option that will skip activating and flushing the + * writecache and move directly to detaching. + */ + +static int _lvconvert_detach_writecache(struct cmd_context *cmd, + struct logical_volume *lv, + struct logical_volume *lv_fast) +{ + uint32_t kernel_error = 0; + + /* + * LV must be inactive externally before detaching cache. + */ + + if (lv_info(cmd, lv, 1, NULL, 0, 0)) { + log_error("LV %s must be inactive to detach writecache.", display_lvname(lv)); + return 0; + } + + if (!archive(lv->vg)) + goto_bad; + + /* + * Activate LV internally since the LV needs to be active to flush. + * LV_TEMPORARY should keep the LV from being exposed to the user + * and being accessed. + */ + + lv->status |= LV_TEMPORARY; + + if (!activate_lv(cmd, lv)) { + log_error("Failed to activate LV %s for flushing.", display_lvname(lv)); + return 0; + } + + sync_local_dev_names(cmd); + + if (!lv_writecache_message(lv, "flush")) { + log_error("Failed to flush writecache for %s.", display_lvname(lv)); + deactivate_lv(cmd, lv); + return 0; + } + + if (!_get_writecache_kernel_error(cmd, lv, &kernel_error)) { + log_error("Failed to get writecache error status for %s.", display_lvname(lv)); + deactivate_lv(cmd, lv); + return 0; + } + + if (kernel_error) { + log_error("Failed to flush writecache (error %u) for %s.", kernel_error, display_lvname(lv)); + deactivate_lv(cmd, lv); + return 0; + } + + if (!deactivate_lv(cmd, lv)) { + log_error("Failed to deactivate LV %s for detaching writecache.", display_lvname(lv)); + return 0; + } + + lv->status &= ~LV_TEMPORARY; + + if (!_lv_writecache_detach(cmd, lv, lv_fast)) { + log_error("Failed to detach writecache from %s", display_lvname(lv)); + return 0; + } + + if (!vg_write(lv->vg) || !vg_commit(lv->vg)) + return_0; + + backup(lv->vg); + + log_print_unless_silent("Logical volume %s write cache has been detached.", + display_lvname(lv)); + return ECMD_PROCESSED; +bad: + return ECMD_FAILED; + +} + +static int _writecache_zero(struct cmd_context *cmd, struct logical_volume *lv) +{ + struct device *dev; + char name[PATH_MAX]; + int ret = 0; + + if (!activate_lv(cmd, lv)) { + log_error("Failed to activate LV %s for zeroing.", lv->name); + return 0; + } + + sync_local_dev_names(cmd); + + if (dm_snprintf(name, sizeof(name), "%s%s/%s", + cmd->dev_dir, lv->vg->name, lv->name) < 0) { + log_error("Name too long - device not cleared (%s)", lv->name); + goto out; + } + + if (!(dev = dev_cache_get(cmd, name, NULL))) { + log_error("%s: not found: device not zeroed", name); + goto out; + } + + if (!label_scan_open(dev)) { + log_error("Failed to open %s/%s for zeroing.", lv->vg->name, lv->name); + goto out; + } + + if (!dev_write_zeros(dev, UINT64_C(0), (size_t) 1 << SECTOR_SHIFT)) + goto_out; + + log_debug("Zeroed the first sector of %s", lv->name); + + label_scan_invalidate(dev); + + ret = 1; +out: + if (!deactivate_lv(cmd, lv)) { + log_error("Failed to deactivate LV %s for zeroing.", lv->name); + ret = 0; + } + + return ret; +} + +static int _get_one_writecache_setting(struct cmd_context *cmd, struct writecache_settings *settings, + char *key, char *val) +{ + if (!strncmp(key, "high_watermark", strlen("high_watermark"))) { + if (sscanf(val, "%llu", (unsigned long long *)&settings->high_watermark) != 1) + goto_bad; + settings->high_watermark_set = 1; + return 1; + } + + if (!strncmp(key, "low_watermark", strlen("low_watermark"))) { + if (sscanf(val, "%llu", (unsigned long long *)&settings->low_watermark) != 1) + goto_bad; + settings->low_watermark_set = 1; + return 1; + } + + if (!strncmp(key, "writeback_jobs", strlen("writeback_jobs"))) { + if (sscanf(val, "%llu", (unsigned long long *)&settings->writeback_jobs) != 1) + goto_bad; + settings->writeback_jobs_set = 1; + return 1; + } + + if (!strncmp(key, "autocommit_blocks", strlen("autocommit_blocks"))) { + if (sscanf(val, "%llu", (unsigned long long *)&settings->autocommit_blocks) != 1) + goto_bad; + settings->autocommit_blocks_set = 1; + return 1; + } + + if (!strncmp(key, "autocommit_time", strlen("autocommit_time"))) { + if (sscanf(val, "%llu", (unsigned long long *)&settings->autocommit_time) != 1) + goto_bad; + settings->autocommit_time_set = 1; + return 1; + } + + if (!strncmp(key, "fua", strlen("fua"))) { + if (settings->nofua_set) { + log_error("Setting fua and nofua cannot both be set."); + return_0; + } + if (sscanf(val, "%u", &settings->fua) != 1) + goto_bad; + settings->fua_set = 1; + return 1; + } + + if (!strncmp(key, "nofua", strlen("nofua"))) { + if (settings->nofua_set) { + log_error("Setting fua and nofua cannot both be set."); + return_0; + } + if (sscanf(val, "%u", &settings->nofua) != 1) + goto_bad; + settings->nofua_set = 1; + return 1; + } + + if (settings->new_key) { + log_error("Setting %s is not recognized. Only one unrecognized setting is allowed.", key); + return 0; + } + + log_warn("Unrecognized writecache setting \"%s\" may cause activation failure.", key); + if (yes_no_prompt("Use unrecognized writecache setting? [y/n]: ") == 'n') { + log_error("Aborting writecache conversion."); + return_0; + } + + log_warn("Using unrecognized writecache setting: %s = %s.", key, val); + + settings->new_key = dm_pool_strdup(cmd->mem, key); + settings->new_val = dm_pool_strdup(cmd->mem, val); + return 1; + + bad: + log_error("Invalid setting: %s", key); + return 0; +} + +static int _get_writecache_settings(struct cmd_context *cmd, struct writecache_settings *settings) +{ + struct arg_value_group_list *group; + const char *str; + char key[64]; + char val[64]; + int num; + int pos; + + /* + * "grouped" means that multiple --cachesettings options can be used. + * Each option is also allowed to contain multiple key = val pairs. + */ + + dm_list_iterate_items(group, &cmd->arg_value_groups) { + if (!grouped_arg_is_set(group->arg_values, cachesettings_ARG)) + continue; + + if (!(str = grouped_arg_str_value(group->arg_values, cachesettings_ARG, NULL))) + break; + + pos = 0; + + while (pos < strlen(str)) { + /* scan for "key1=val1 key2 = val2 key3= val3" */ + + memset(key, 0, sizeof(key)); + memset(val, 0, sizeof(val)); + + if (sscanf(str + pos, " %63[^=]=%63s %n", key, val, &num) != 2) { + log_error("Invalid setting at: %s", str+pos); + return_0; + } + + pos += num; + + if (!_get_one_writecache_setting(cmd, settings, key, val)) + return_0; + } + } + + return 1; +} + +static struct logical_volume *_lv_writecache_create(struct cmd_context *cmd, + struct logical_volume *lv, + struct logical_volume *lv_fast, + uint32_t block_size_sectors, + struct writecache_settings *settings) +{ + struct logical_volume *lv_wcorig; + const struct segment_type *segtype; + struct lv_segment *seg; + + /* should lv_fast get a new status flag indicating it's the cache in a writecache LV? */ + + if (!(segtype = get_segtype_from_string(cmd, SEG_TYPE_NAME_WRITECACHE))) + return_NULL; + + /* + * "lv_wcorig" is a new LV with new id, but with the segments from "lv". + * "lv" keeps the existing name and id, but gets a new writecache segment, + * in place of the segments that were moved to lv_wcorig. + */ + + if (!(lv_wcorig = insert_layer_for_lv(cmd, lv, WRITECACHE, "_wcorig"))) + return_NULL; + + lv_set_hidden(lv_fast); + + seg = first_seg(lv); + seg->segtype = segtype; + + seg->writecache = lv_fast; + + /* writecache_block_size is in bytes */ + seg->writecache_block_size = block_size_sectors * 512; + + memcpy(&seg->writecache_settings, settings, sizeof(struct writecache_settings)); + + add_seg_to_segs_using_this_lv(lv_fast, seg); + + return lv_wcorig; +} + +static int _lvconvert_writecache_attach_single(struct cmd_context *cmd, + struct logical_volume *lv, + struct processing_handle *handle) +{ + struct volume_group *vg = lv->vg; + struct logical_volume *lv_wcorig; + struct logical_volume *lv_fast; + struct writecache_settings settings; + const char *fast_name; + uint32_t block_size_sectors; + char *lockd_fast_args = NULL; + char *lockd_fast_name = NULL; + struct id lockd_fast_id; + + fast_name = arg_str_value(cmd, cachepool_ARG, ""); + + if (!(lv_fast = find_lv(vg, fast_name))) { + log_error("LV %s not found.", fast_name); + goto bad; + } + + if (!seg_is_linear(first_seg(lv_fast))) { + log_error("LV %s must be linear to use as a writecache.", display_lvname(lv_fast)); + return 0; + } + + /* fast LV shouldn't generally be active by itself, but just in case. */ + if (lv_info(cmd, lv_fast, 1, NULL, 0, 0)) { + log_error("LV %s must be inactive to attach.", display_lvname(lv_fast)); + return 0; + } + + /* default block size is 4096 bytes (8 sectors) */ + block_size_sectors = arg_int_value(cmd, writecacheblocksize_ARG, 8); + if (block_size_sectors > 8) { + log_error("Max writecache block size is 4096 bytes."); + return 0; + } + + memset(&settings, 0, sizeof(settings)); + + if (!_get_writecache_settings(cmd, &settings)) { + log_error("Invalid writecache settings."); + return 0; + } + + /* Ensure the two LVs are not active elsewhere. */ + if (!lockd_lv(cmd, lv, "ex", 0)) + goto_bad; + if (!lockd_lv(cmd, lv_fast, "ex", 0)) + goto_bad; + + if (!archive(vg)) + goto_bad; + + /* + * TODO: use libblkid to get the sector size of lv. If it doesn't + * match the block_size we are using for the writecache, then warn that + * an existing file system on lv may become unmountable with the + * writecache attached because of the changing sector size. If this + * happens, then use --splitcache, and reattach the writecache using a + * --writecacheblocksize value matching the sector size of lv. + */ + + if (!_writecache_zero(cmd, lv_fast)) { + log_error("LV %s could not be zeroed.", display_lvname(lv_fast)); + return 0; + } + + /* + * Changes the vg struct to match the desired state. + * + * - lv keeps existing lv name and id, gets new segment with segtype + * "writecache". + * + * - lv_fast keeps its existing name and id, becomes hidden. + * + * - lv_wcorig gets new name (existing name + _wcorig suffix), + * gets new id, becomes hidden, gets segments from lv. + */ + + if (!(lv_wcorig = _lv_writecache_create(cmd, lv, lv_fast, block_size_sectors, &settings))) + goto_bad; + + /* + * lv keeps the same lockd lock it had before, the lock for + * lv_fast is freed, and lv_wcorig gets no lock. + */ + if (vg_is_shared(vg) && lv_fast->lock_args) { + lockd_fast_args = dm_pool_strdup(cmd->mem, lv_fast->lock_args); + lockd_fast_name = dm_pool_strdup(cmd->mem, lv_fast->name); + memcpy(&lockd_fast_id, &lv_fast->lvid.id[1], sizeof(struct id)); + lv_fast->lock_args = NULL; + } + + /* + * vg_write(), suspend_lv(), vg_commit(), resume_lv(), + * where the old LV is suspended and the new LV is resumed. + */ + + if (!lv_update_and_reload(lv)) + goto_bad; + + lockd_lv(cmd, lv, "un", 0); + + if (lockd_fast_name) { + /* unlock and free lockd lock for lv_fast */ + if (!lockd_lv_name(cmd, vg, lockd_fast_name, &lockd_fast_id, lockd_fast_args, "un", 0)) + log_error("Failed to unlock fast LV %s/%s", vg->name, lockd_fast_name); + lockd_free_lv(cmd, vg, lockd_fast_name, &lockd_fast_id, lockd_fast_args); + } + + log_print_unless_silent("Logical volume %s now has write cache.", + display_lvname(lv)); + return ECMD_PROCESSED; +bad: + return ECMD_FAILED; + +} + +int lvconvert_to_writecache_vol_cmd(struct cmd_context *cmd, int argc, char **argv) +{ + struct processing_handle *handle; + struct lvconvert_result lr = { 0 }; + int ret; + + if (!(handle = init_processing_handle(cmd, NULL))) { + log_error("Failed to initialize processing handle."); + return ECMD_FAILED; + } + + handle->custom_handle = &lr; + + cmd->cname->flags &= ~GET_VGNAME_FROM_OPTIONS; + + ret = process_each_lv(cmd, cmd->position_argc, cmd->position_argv, NULL, NULL, READ_FOR_UPDATE, handle, NULL, + &_lvconvert_writecache_attach_single); + + destroy_processing_handle(cmd, handle); + + return ret; +} + /* * All lvconvert command defs have their own function, * so the generic function name is unused. diff --git a/tools/lvmcmdline.c b/tools/lvmcmdline.c index b1d072353..f49843df4 100644 --- a/tools/lvmcmdline.c +++ b/tools/lvmcmdline.c @@ -124,12 +124,13 @@ static const struct command_function _command_functions[CMD_COUNT] = { { lvconvert_to_cachepool_CMD, lvconvert_to_pool_cmd }, { lvconvert_to_thin_with_external_CMD, lvconvert_to_thin_with_external_cmd }, { lvconvert_to_cache_vol_CMD, lvconvert_to_cache_vol_cmd }, + { lvconvert_to_writecache_vol_CMD, lvconvert_to_writecache_vol_cmd }, { lvconvert_swap_pool_metadata_CMD, lvconvert_swap_pool_metadata_cmd }, { lvconvert_to_thinpool_or_swap_metadata_CMD, lvconvert_to_pool_or_swap_metadata_cmd }, { lvconvert_to_cachepool_or_swap_metadata_CMD, lvconvert_to_pool_or_swap_metadata_cmd }, { lvconvert_merge_thin_CMD, lvconvert_merge_thin_cmd }, - { lvconvert_split_and_keep_cachepool_CMD, lvconvert_split_cachepool_cmd }, - { lvconvert_split_and_remove_cachepool_CMD, lvconvert_split_cachepool_cmd }, + { lvconvert_split_and_keep_cache_CMD, lvconvert_split_cache_cmd }, + { lvconvert_split_and_remove_cache_CMD, lvconvert_split_cache_cmd }, /* lvconvert raid-related type conversions */ { lvconvert_raid_types_CMD, lvconvert_raid_types_cmd }, @@ -2120,7 +2121,7 @@ static int _process_command_line(struct cmd_context *cmd, int *argc, char ***arg * value (e.g. foo_ARG) from the args array. */ if ((arg_enum = _find_arg(cmd->name, goval)) < 0) { - log_fatal("Unrecognised option."); + log_fatal("Unrecognised option %d (%c).", goval, goval); return 0; } diff --git a/tools/toollib.c b/tools/toollib.c index c2ffa427f..2ab4b6242 100644 --- a/tools/toollib.c +++ b/tools/toollib.c @@ -2562,6 +2562,8 @@ static int _lv_is_type(struct cmd_context *cmd, struct logical_volume *lv, int l return seg_is_any_raid6(seg); case raid10_LVT: return seg_is_raid10(seg); + case writecache_LVT: + return seg_is_writecache(seg); case error_LVT: return !strcmp(seg->segtype->name, SEG_TYPE_NAME_ERROR); case zero_LVT: @@ -2618,6 +2620,8 @@ int get_lvt_enum(struct logical_volume *lv) return raid6_LVT; if (seg_is_raid10(seg)) return raid10_LVT; + if (seg_is_writecache(seg)) + return writecache_LVT; if (!strcmp(seg->segtype->name, SEG_TYPE_NAME_ERROR)) return error_LVT; @@ -2740,8 +2744,13 @@ static int _check_lv_types(struct cmd_context *cmd, struct logical_volume *lv, i if (!ret) { int lvt_enum = get_lvt_enum(lv); struct lv_type *type = get_lv_type(lvt_enum); - log_warn("Command on LV %s does not accept LV type %s.", - display_lvname(lv), type ? type->name : "unknown"); + if (!type) { + log_warn("Command on LV %s does not accept LV type unknown (%d).", + display_lvname(lv), lvt_enum); + } else { + log_warn("Command on LV %s does not accept LV type %s.", + display_lvname(lv), type->name); + } } return ret; diff --git a/tools/tools.h b/tools/tools.h index 55d486b05..5e0cd3046 100644 --- a/tools/tools.h +++ b/tools/tools.h @@ -248,11 +248,12 @@ int lvconvert_start_poll_cmd(struct cmd_context *cmd, int argc, char **argv); int lvconvert_to_pool_cmd(struct cmd_context *cmd, int argc, char **argv); int lvconvert_to_cache_vol_cmd(struct cmd_context *cmd, int argc, char **argv); +int lvconvert_to_writecache_vol_cmd(struct cmd_context *cmd, int argc, char **argv); int lvconvert_to_thin_with_external_cmd(struct cmd_context *cmd, int argc, char **argv); int lvconvert_swap_pool_metadata_cmd(struct cmd_context *cmd, int argc, char **argv); int lvconvert_to_pool_or_swap_metadata_cmd(struct cmd_context *cmd, int argc, char **argv); int lvconvert_merge_thin_cmd(struct cmd_context *cmd, int argc, char **argv); -int lvconvert_split_cachepool_cmd(struct cmd_context *cmd, int argc, char **argv); +int lvconvert_split_cache_cmd(struct cmd_context *cmd, int argc, char **argv); int lvconvert_raid_types_cmd(struct cmd_context * cmd, int argc, char **argv); int lvconvert_split_mirror_images_cmd(struct cmd_context * cmd, int argc, char **argv);