diff --git a/src/disk_tool/CMakeLists.txt b/src/disk_tool/CMakeLists.txt index b54145e1..6a4fc0c0 100644 --- a/src/disk_tool/CMakeLists.txt +++ b/src/disk_tool/CMakeLists.txt @@ -5,7 +5,8 @@ project(vitastor) # vitastor-disk add_executable(vitastor-disk disk_tool.cpp disk_simple_offsets.cpp - disk_tool_journal.cpp disk_tool_meta.cpp disk_tool_prepare.cpp disk_tool_resize.cpp disk_tool_udev.cpp disk_tool_utils.cpp disk_tool_upgrade.cpp + disk_tool_journal.cpp disk_tool_meta.cpp disk_tool_prepare.cpp disk_tool_resize.cpp + disk_tool_resize_auto.cpp disk_tool_udev.cpp disk_tool_utils.cpp disk_tool_upgrade.cpp ../util/crc32c.c ../util/str_util.cpp ../util/json_util.cpp ../../json11/json11.cpp ../util/rw_blocking.cpp ../util/allocator.cpp ../util/ringloop.cpp ../blockstore/blockstore_disk.cpp ) target_link_libraries(vitastor-disk diff --git a/src/disk_tool/disk_tool.cpp b/src/disk_tool/disk_tool.cpp index fb1c0c53..295f0d16 100644 --- a/src/disk_tool/disk_tool.cpp +++ b/src/disk_tool/disk_tool.cpp @@ -92,8 +92,22 @@ static const char *help_text = " \n" " Requires the `sfdisk` utility.\n" "\n" - "vitastor-disk resize [--iodepth 32]\n" - " Resize data area and/or rewrite/move journal and metadata\n" + "vitastor-disk resize | [OPTIONS]\n" + " Resize data area and/or move journal and metadata:\n" + " --move-journal TARGET move journal to TARGET\n" + " --move-meta TARGET move metadata to TARGET\n" + " --journal-size NEW_SIZE resize journal to NEW_SIZE\n" + " --data-size NEW_SIZE resize data device to NEW_SIZE\n" + " --dry-run only show new layout, do not apply it\n" + " \n" + " NEW_SIZE may include k/m/g/t suffixes.\n" + " TARGET may be one of:\n" + " move journal/metadata to an existing GPT partition\n" + " create a GPT partition on and move journal/metadata to it\n" + " \"\" (empty string) move journal/metadata back to the data device\n" + "\n" + "vitastor-disk raw-resize [--iodepth 32]\n" + " Resize data area and/or rewrite/move journal and metadata (manual format).\n" " ALL_OSD_PARAMETERS must include all (at least all disk-related)\n" " parameters from OSD command line (i.e. from systemd unit or superblock).\n" " NEW_LAYOUT may include new disk layout parameters:\n" @@ -236,6 +250,10 @@ int main(int argc, char *argv[]) { self.options["force"] = "1"; } + else if (!strcmp(argv[i], "--dry-run") || !strcmp(argv[i], "--dry_run")) + { + self.options["dry_run"] = "1"; + } else if (!strcmp(argv[i], "--allow-data-loss")) { self.options["allow_data_loss"] = "1"; @@ -243,7 +261,7 @@ int main(int argc, char *argv[]) else if (argv[i][0] == '-' && argv[i][1] == '-' && i < argc-1) { char *key = argv[i]+2; - self.options[key] = argv[++i]; + self.options[str_replace(key, "-", "_")] = argv[++i]; } else { @@ -375,7 +393,16 @@ int main(int argc, char *argv[]) } else if (!strcmp(cmd[0], "resize")) { - return self.resize_data(); + if (cmd.size() != 2) + { + fprintf(stderr, "Exactly 1 OSD number or OSD device path argument is required\n"); + return 1; + } + return self.resize_data(cmd[1]); + } + else if (!strcmp(cmd[0], "raw-resize")) + { + return self.raw_resize(); } else if (!strcmp(cmd[0], "simple-offsets")) { diff --git a/src/disk_tool/disk_tool.h b/src/disk_tool/disk_tool.h index 2774c4fb..70167a3e 100644 --- a/src/disk_tool/disk_tool.h +++ b/src/disk_tool/disk_tool.h @@ -98,7 +98,11 @@ struct disk_tool_t int write_json_journal(json11::Json entries); int write_json_meta(json11::Json meta); - int resize_data(); + int resize_data(std::string device); + int resize_parse_move_journal(std::map & move_options, bool dry_run); + int resize_parse_move_meta(std::map & move_options, bool dry_run); + + int raw_resize(); int resize_parse_params(); void resize_init(blockstore_meta_header_v2_t *hdr); int resize_remap_blocks(); diff --git a/src/disk_tool/disk_tool_meta.cpp b/src/disk_tool/disk_tool_meta.cpp index fe9258d9..61898e4f 100644 --- a/src/disk_tool/disk_tool_meta.cpp +++ b/src/disk_tool/disk_tool_meta.cpp @@ -159,7 +159,7 @@ int disk_tool_t::dump_load_check_superblock(const std::string & device) { auto cfg = json_to_string_map(sb["params"].object_items()); dsk.parse_config(cfg); - dsk.data_io = dsk.meta_io = dsk.journal_io = "cached"; + dsk.data_io = dsk.meta_io = dsk.journal_io = "direct"; dsk.open_data(); dsk.open_meta(); dsk.open_journal(); diff --git a/src/disk_tool/disk_tool_resize.cpp b/src/disk_tool/disk_tool_resize.cpp index 73101097..58a7bfbc 100644 --- a/src/disk_tool/disk_tool_resize.cpp +++ b/src/disk_tool/disk_tool_resize.cpp @@ -18,7 +18,7 @@ struct resizer_data_moving_t uint64_t old_loc, new_loc; }; -int disk_tool_t::resize_data() +int disk_tool_t::raw_resize() { int r; // Parse parameters diff --git a/src/disk_tool/disk_tool_resize_auto.cpp b/src/disk_tool/disk_tool_resize_auto.cpp new file mode 100644 index 00000000..551625e4 --- /dev/null +++ b/src/disk_tool/disk_tool_resize_auto.cpp @@ -0,0 +1,296 @@ +// Copyright (c) Vitaliy Filippov, 2019+ +// License: VNPL-1.1 (see README.md for details) + +#include "disk_tool.h" +#include "rw_blocking.h" +#include "str_util.h" +#include "json_util.h" + +int disk_tool_t::resize_data(std::string device) +{ + if (options.find("move_journal") == options.end() && + options.find("move_data") == options.end() && + options.find("journal_size") == options.end() && + options.find("data_size") == options.end()) + { + fprintf(stderr, "None of --move-journal, --move-data, --journal-size, --data-size options are specified - nothing to do!\n"); + return 1; + } + if (stoull_full(device)) + device = "/dev/vitastor/osd"+device+"-data"; + json11::Json sb = read_osd_superblock(device, true, false); + if (sb.is_null()) + return 1; + auto sb_params = json_to_string_map(sb["params"].object_items()); + try + { + dsk.parse_config(sb_params); + dsk.data_io = dsk.meta_io = dsk.journal_io = "cached"; + dsk.open_data(); + dsk.open_meta(); + dsk.open_journal(); + dsk.calc_lengths(true); + } + catch (std::exception & e) + { + dsk.close_all(); + fprintf(stderr, "%s\n", e.what()); + return 1; + } + dsk.close_all(); + bool dry_run = options.find("dry_run") != options.end(); + auto old_journal_device = dsk.journal_device; + auto old_meta_device = dsk.meta_device; + new_journal_len = dsk.journal_len; + if (options.find("journal_size") != options.end()) + { + new_journal_len = parse_size(options["journal_size"]); + if (options.find("move_journal") == options.end()) + options["move_journal"] = dsk.journal_device == dsk.data_device ? "" : dsk.journal_device; + } + std::map move_options; + if (options.find("move_journal") != options.end()) + { + if (resize_parse_move_journal(move_options, dry_run) != 0) + return 1; + } + if (options.find("move_meta") != options.end()) + { + if (resize_parse_move_meta(move_options, dry_run) != 0) + return 1; + } + auto new_journal_device = move_options.find("new_journal_device") != move_options.end() + ? move_options["new_journal_device"] : dsk.journal_device; + auto new_meta_device = move_options.find("new_meta_device") != move_options.end() + ? move_options["new_meta_device"] : dsk.meta_device; + // Calculate new data & meta offsets + new_data_offset = 4096 + (new_journal_device == dsk.data_device ? new_journal_len : 0) + + (new_meta_device == dsk.data_device ? dsk.meta_len : 0); + new_data_offset += ((dsk.data_offset-new_data_offset) % dsk.data_block_size); + if (new_data_offset != dsk.data_offset) + move_options["new_data_offset"] = std::to_string(new_data_offset); + if (options.find("data_size") != options.end()) + { + auto new_data_dev_size = parse_size(options["data_size"]); + new_data_dev_size = options["data_size"] == "max" || new_data_dev_size > dsk.data_device_size + ? dsk.data_device_size : new_data_dev_size; + if (new_data_dev_size-dsk.data_offset != dsk.data_len) + move_options["new_data_len"] = std::to_string(new_data_dev_size-new_data_offset); + } + new_meta_offset = 4096 + (new_meta_device == new_journal_device ? new_journal_len : 0); + if (new_meta_offset != dsk.meta_offset) + move_options["new_meta_offset"] = std::to_string(new_meta_offset); + // Run resize + auto orig_options = std::move(options); + options = sb_params; + for (auto & kv: move_options) + options[kv.first] = kv.second; + if (!json) + { + std::string cmd; + for (auto & kv: move_options) + cmd += " "+kv.first+" = "+kv.second+"\n"; + fprintf(stderr, "Running resize:\n%s", cmd.c_str()); + } + if (!dry_run && raw_resize() != 0) + return 1; + // Write new superblocks + json11::Json::object new_sb_params = sb["params"].object_items(); + if (move_options.find("new_journal_device") != move_options.end()) + new_sb_params["journal_device"] = move_options["new_journal_device"]; + if (move_options.find("new_meta_device") != move_options.end()) + new_sb_params["meta_device"] = move_options["new_meta_device"]; + new_sb_params["data_offset"] = new_data_offset; + new_sb_params["meta_offset"] = new_meta_offset; + if (move_options.find("new_data_len") != move_options.end()) + new_sb_params["data_size"] = stoull_full(move_options["new_data_len"]); + std::set clear_superblocks, write_superblocks; + write_superblocks.insert(dsk.data_device); + write_superblocks.insert(new_journal_device); + write_superblocks.insert(new_meta_device); + if (write_superblocks.find(old_journal_device) == write_superblocks.end()) + clear_superblocks.insert(old_journal_device); + if (write_superblocks.find(old_meta_device) == write_superblocks.end()) + clear_superblocks.insert(old_meta_device); + for (auto & dev: clear_superblocks) + { + if (!json) + fprintf(stderr, "Clearing OSD superblock on %s\n", dev.c_str()); + if (!dry_run && clear_osd_superblock(dev) != 0) + return 1; + } + for (auto & dev: write_superblocks) + { + if (!json) + fprintf(stderr, "Writing new OSD superblock to %s\n", dev.c_str()); + if (!dry_run && !write_osd_superblock(dev, new_sb_params)) + return 1; + } + if (json) + { + printf("%s\n", json11::Json(json11::Json::object { + { "new_sb_params", new_sb_params }, + }).dump().c_str()); + } + return 0; +} + +int disk_tool_t::resize_parse_move_journal(std::map & move_options, bool dry_run) +{ + if (options["move_journal"] == "") + { + // move back to the data device + // but first check if not already there :) + if (dsk.journal_device == dsk.data_device && new_journal_len == dsk.journal_len) + { + // already there + fprintf(stderr, "journal is already on data device and has the same size\n"); + return 0; + } + move_options["new_journal_device"] = dsk.data_device; + move_options["new_journal_offset"] = "4096"; + move_options["new_journal_len"] = std::to_string(new_journal_len); + } + else + { + std::string real_dev = realpath_str(options["move_journal"], false); + if (real_dev == "") + return 1; + std::string parent_dev = get_parent_device(real_dev); + if (parent_dev == "") + return 1; + if (parent_dev == real_dev) + { + // whole disk - create partition + std::string old_real_dev = realpath_str(dsk.journal_device); + if (old_real_dev == "") + return 1; + if (options.find("force") == options.end() && + get_parent_device(old_real_dev) == parent_dev) + { + // already there + fprintf(stderr, "journal is already on a partition of %s, add --force to create a new partition\n", options["move_journal"].c_str()); + return 0; + } + new_journal_len = ((new_journal_len+1024*1024-1)/1024/1024)*1024*1024; + if (!dry_run) + { + auto devinfos = collect_devices({ real_dev }); + if (devinfos.size() == 0) + return 1; + std::vector sizes; + sizes.push_back(std::to_string(new_journal_len/1024/1024)+"MiB"); + auto new_parts = add_partitions(devinfos[0], sizes); + if (!new_parts.array_items().size()) + return 1; + options["move_journal"] = "/dev/disk/by-partuuid/"+strtolower(new_parts[0]["uuid"].string_value()); + } + else + options["move_journal"] = ""; + } + else if (options["move_journal"].substr(0, 22) != "/dev/disk/by-partuuid/") + { + // Partitions should be identified by GPT partition UUID + fprintf(stderr, "%s does not start with /dev/disk/by-partuuid/. Partitions should be identified by GPT partition UUIDs\n", options["move_journal"].c_str()); + return 1; + } + else + { + // already a partition - check that it's a GPT partition with correct type + if (options.find("force") == options.end() && + check_existing_partition(real_dev) != 0) + { + return 1; + } + new_journal_len = get_device_size(options["move_journal"], true); + if (new_journal_len == UINT64_MAX) + return 1; + } + new_journal_len -= 4096; + move_options["new_journal_device"] = options["move_journal"]; + move_options["new_journal_offset"] = "4096"; + move_options["new_journal_len"] = std::to_string(new_journal_len); + } + return 0; +} + +int disk_tool_t::resize_parse_move_meta(std::map & move_options, bool dry_run) +{ + if (options["move_meta"] == "") + { + // move back to the data device + // but first check if not already there :) + if (dsk.meta_device == dsk.data_device) + { + // already there + fprintf(stderr, "metadata is already on data device\n"); + return 0; + } + auto new_journal_device = move_options.find("new_journal_device") != move_options.end() + ? move_options["new_journal_device"] : dsk.journal_device; + move_options["new_meta_device"] = dsk.data_device; + move_options["new_meta_len"] = std::to_string(dsk.meta_len); + } + else + { + std::string real_dev = realpath_str(options["move_meta"], false); + if (real_dev == "") + return 1; + std::string parent_dev = get_parent_device(real_dev); + if (parent_dev == "") + return 1; + uint64_t new_meta_len = 0; + if (parent_dev == real_dev) + { + // whole disk - create partition + std::string old_real_dev = realpath_str(dsk.meta_device); + if (old_real_dev == "") + return 1; + if (options.find("force") == options.end() && + get_parent_device(old_real_dev) == parent_dev) + { + // already there + fprintf(stderr, "metadata is already on a partition of %s\n", options["move_meta"].c_str()); + return 0; + } + new_meta_len = ((dsk.meta_len+1024*1024-1)/1024/1024)*1024*1024; + if (!dry_run) + { + auto devinfos = collect_devices({ real_dev }); + if (devinfos.size() == 0) + return 1; + std::vector sizes; + sizes.push_back(std::to_string(new_meta_len/1024/1024)+"MiB"); + auto new_parts = add_partitions(devinfos[0], sizes); + if (!new_parts.array_items().size()) + return 1; + options["move_meta"] = "/dev/disk/by-partuuid/"+strtolower(new_parts[0]["uuid"].string_value()); + } + else + options["move_meta"] = ""; + } + else if (options["move_meta"].substr(0, 22) != "/dev/disk/by-partuuid/") + { + // Partitions should be identified by GPT partition UUID + fprintf(stderr, "%s does not start with /dev/disk/by-partuuid/. Partitions should be identified by GPT partition UUIDs\n", options["move_meta"].c_str()); + return 1; + } + else + { + // already a partition - check that it's a GPT partition with correct type + if (options.find("force") == options.end() && + check_existing_partition(real_dev) != 0) + { + return 1; + } + new_meta_len = get_device_size(options["move_meta"], true); + if (new_meta_len == UINT64_MAX) + return 1; + } + new_meta_len -= 4096; + move_options["new_meta_len"] = std::to_string(new_meta_len); + move_options["new_meta_device"] = options["move_meta"]; + move_options["new_meta_offset"] = "4096"; + } + return 0; +} diff --git a/src/disk_tool/disk_tool_upgrade.cpp b/src/disk_tool/disk_tool_upgrade.cpp index c18aae9f..6b5c61a5 100644 --- a/src/disk_tool/disk_tool_upgrade.cpp +++ b/src/disk_tool/disk_tool_upgrade.cpp @@ -101,7 +101,7 @@ int disk_tool_t::upgrade_simple_unit(std::string unit) resizer.options = options; for (auto & kv: resize) resizer.options[kv.first] = std::to_string(kv.second); - if (resizer.resize_data() != 0) + if (resizer.raw_resize() != 0) { // FIXME: Resize with backup or journal fprintf(