# 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],
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
*
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
*/
SEG_SNAPSHOT_MERGE,
SEG_STRIPED,
SEG_ZERO,
+ SEG_WRITECACHE,
SEG_THIN_POOL,
SEG_THIN,
SEG_VDO,
{ 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" },
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 */
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)
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) {
case SEG_THIN_POOL:
case SEG_THIN:
case SEG_CACHE:
+ case SEG_WRITECACHE:
break;
case SEG_CRYPT:
case SEG_LINEAR:
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,
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;
SOURCES =\
activate/activate.c \
cache/lvmcache.c \
+ writecache/writecache.c \
cache_segtype/cache.c \
commands/toolcontext.c \
config/config.c \
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
*
SEG_STATUS_THIN,
SEG_STATUS_THIN_POOL,
SEG_STATUS_VDO_POOL,
+ SEG_STATUS_WRITECACHE,
SEG_STATUS_UNKNOWN
} lv_seg_status_type_t;
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;
};
};
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,
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"
#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"
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!
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)
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 */
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) &&
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);
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() &&
#include "lib/device/device-types.h"
+/*
+ * dev is pmem if /sys/dev/block/<major>:<minor>/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)
{
int dev_is_rotational(struct dev_types *dt, struct device *dev);
+int dev_is_pmem(struct device *dev);
+
#endif
{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}
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;
}
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';
{
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,
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;
}
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++;
#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 */
#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)
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 */
int vg_strip_outdated_historical_lvs(struct volume_group *vg);
+int lv_on_pmem(struct logical_volume *lv);
+
#endif
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;
+}
+
#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)
#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"
#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)
#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)
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)
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+#!/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
+
"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"
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
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
---
-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
---
lvt(raid10_LVT, "raid10", NULL)
lvt(error_LVT, "error", NULL)
lvt(zero_LVT, "zero", NULL)
+lvt(writecache_LVT, "writecache", NULL)
lvt(LVT_COUNT, "", NULL)
#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 */
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;
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)
{
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;
}
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,
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.
{ 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 },
* 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;
}
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:
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;
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;
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);