diff --git a/mon/make-osd-hybrid.js b/mon/make-osd-hybrid.js index 91e50efd..f760804c 100755 --- a/mon/make-osd-hybrid.js +++ b/mon/make-osd-hybrid.js @@ -1,4 +1,5 @@ #!/usr/bin/nodejs +// DEPRECATED, DO NOT USE - use vitastor-disk prepare instead // systemd unit generator for hybrid (HDD+SSD) vitastor OSDs // Copyright (c) Vitaliy Filippov, 2019+ // License: VNPL-1.1 diff --git a/mon/make-osd.sh b/mon/make-osd.sh index 61ca7735..72be88f8 100755 --- a/mon/make-osd.sh +++ b/mon/make-osd.sh @@ -1,6 +1,6 @@ #!/bin/bash +# DEPRECATED, DO NOT USE - use vitastor-disk prepare instead # Very simple systemd unit generator for vitastor-osd services -# Not the final solution yet, mostly for tests # Copyright (c) Vitaliy Filippov, 2019+ # License: MIT diff --git a/mon/upgrade-simple.js b/mon/upgrade-simple.js deleted file mode 100755 index c6111d57..00000000 --- a/mon/upgrade-simple.js +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/node -// Upgrade tool for OSD units generated with make-osd.sh and make-osd-hybrid.js - -const fsp = require('fs').promises; -const child_process = require('child_process'); - -upgrade_osd(process.argv[2]).catch(e => -{ - console.error(e.message); - process.exit(1); -}); - -async function upgrade_osd(unit) -{ - if (!unit) - throw new Error('USAGE: '+process.argv[0]+' '+process.argv[1]+' /etc/systemd/system/vitastor-osd.service'); - let service_name = /^.*\/(vitastor-osd\d+)\.service$/.exec(unit); - if (!service_name) - throw new Error(unit+' is not a service named vitastor-osd'); - service_name = service_name[1]; - // Parse the unit - const text = await fsp.readFile(unit, { encoding: 'utf-8' }); - let cmd = /\nExecStart\s*=[^\n]+vitastor-osd\s*(([^\\\n&>\d]+|\\[ \t\r]*\n|\d[^>])+)/.exec(text); - if (!cmd) - throw new Error('Failed to extract ExecStart command from '+unit); - cmd = cmd[1].replace(/\\[ \t\r]*\n/g, '').split(/\s+/); - const options = {}; - for (let i = 0; i < cmd.length-1; i += 2) - { - if (cmd[i].substr(0, 2) == '--') - options[cmd[i].substr(2)] = cmd[i+1]; - else - i--; - } - if (!options['osd_num'] || !options['data_device']) - throw new Error('osd_num or data_device are missing in '+unit); - if (options['data_device'].substr(0, '/dev/disk/by-partuuid/'.length) != '/dev/disk/by-partuuid/' || - options['meta_device'] && options['meta_device'].substr(0, '/dev/disk/by-partuuid/'.length) != '/dev/disk/by-partuuid/' || - options['journal_device'] && options['journal_device'].substr(0, '/dev/disk/by-partuuid/'.length) != '/dev/disk/by-partuuid/') - { - throw new Error( - 'data_device, meta_device and journal_device must begin with'+ - ' /dev/disk/by-partuuid/ i.e. they must be GPT partitions identified by UUIDs' - ); - } - // Stop and disable the service - await system_or_die("systemctl disable --now "+service_name); - const j_o = BigInt(options['journal_offset'] || 0); - const m_o = BigInt(options['meta_offset'] || 0); - const d_o = BigInt(options['data_offset'] || 0); - const m_is_d = !options['meta_device'] || options['meta_device'] == options['data_device']; - const j_is_m = !options['journal_device'] || options['journal_device'] == options['meta_device']; - const j_is_d = (options['journal_device'] || options['meta_device'] || options['data_device']) == options['data_device']; - if (d_o < 4096 || j_o < 4096 || m_o < 4096) - { - // Resize data - let blk = BigInt(options['block_size'] || 128*1024); - let resize = {}; - if (d_o < 4096 || m_is_d && m_o < 4096 && m_o < d_o || j_is_d && j_o < 4096 && j_o < d_o) - { - resize.new_data_offset = d_o+blk; - if (m_is_d && m_o < d_o) - resize.new_meta_offset = m_o+blk; - if (j_is_d && j_o < d_o) - resize.new_journal_offset = j_o+blk; - } - if (!m_is_d && m_o < 4096) - { - resize.new_meta_offset = m_o+4096n; - if (j_is_m && m_o < j_o) - resize.new_journal_offset = j_o+4096n; - } - if (!j_is_d && !j_is_m && j_o < 4096) - resize.new_journal_offset = j_o+4096n; - const resize_opts = Object.keys(resize).map(k => ` --${k} ${resize[k]}`).join(''); - const resize_cmd = 'vitastor-disk resize'+ - Object.keys(options).map(k => ` --${k} ${options[k]}`).join('')+resize_opts; - await system_or_die(resize_cmd, { no_cmd_on_err: true }); - for (let k in resize) - options[k.substr(4)] = ''+resize[k]; - } - // Write superblock - const sb = JSON.stringify(options); - await system_or_die('vitastor-disk write-sb '+options['data_device'], { input: sb }); - if (!m_is_d) - await system_or_die('vitastor-disk write-sb '+options['meta_device'], { input: sb }); - if (!j_is_d && !j_is_m) - await system_or_die('vitastor-disk write-sb '+options['journal_device'], { input: sb }); - // Change partition type - await fix_partition_type(options['data_device']); - if (!m_is_d) - await fix_partition_type(options['meta_device']); - if (!j_is_d && !j_is_m) - await fix_partition_type(options['journal_device']); - // Enable the new unit - await system_or_die("systemctl enable --now vitastor-osd@"+options['osd_num']); - console.log('\nOK: Converted OSD '+options['osd_num']+' to the new scheme. The new service name is vitastor-osd@'+options['osd_num']); -} - -async function fix_partition_type(dev) -{ - const uuid = dev.replace(/^.*\//, '').toLowerCase(); - const parent_dev = (await fsp.realpath(dev)).replace(/((\d)p|(\D))?\d+$/, '$2$3'); - const pt = JSON.parse(await system_or_die('sfdisk --dump '+parent_dev+' --json', { get_out: true })).partitiontable; - let script = 'label: gpt\n\n'; - for (const part of pt.partitions) - { - if (part.uuid.toLowerCase() == uuid) - part.type = 'e7009fac-a5a1-4d72-af72-53de13059903'; - script += part.node+': '+Object.keys(part).map(k => k == 'node' ? '' : k+'='+part[k]).filter(k => k).join(', ')+'\n'; - } - await system_or_die('sfdisk --force '+parent_dev, { input: script, get_out: true }); -} - -async function system_or_die(cmd, options = {}) -{ - let [ exitcode, stdout, stderr ] = await system(cmd, options); - if (exitcode != 0) - throw new Error((!options.no_cmd_on_err ? cmd : 'Command')+' failed'+(options.get_err ? ': '+stderr : '')); - return stdout; -} - -async function system(cmd, options = {}) -{ - process.stderr.write('Running: '+cmd+(options.input != null ? " < stdout += buf.toString()); - if (options.get_err) - cp.stderr.on('data', buf => stderr += buf.toString()); - cp.on('exit', () => finish_cb && finish_cb()); - if (options.input != null) - cp.stdin.write(options.input); - cp.stdin.end(); - if (cp.exitCode == null) - await new Promise(ok => finish_cb = ok); - return [ cp.exitCode, stdout, stderr ]; -} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f672987b..c9735914 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -195,7 +195,7 @@ configure_file(vitastor.pc.in vitastor.pc @ONLY) # 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_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 crc32c.c str_util.cpp ../json11/json11.cpp rw_blocking.cpp allocator.cpp ringloop.cpp blockstore_disk.cpp ) target_link_libraries(vitastor-disk diff --git a/src/disk_tool.cpp b/src/disk_tool.cpp index 830ad309..9efede1e 100644 --- a/src/disk_tool.cpp +++ b/src/disk_tool.cpp @@ -18,6 +18,7 @@ static const char *help_text = " In the second form, you omit and pass --data_device, --journal_device\n" " and/or --meta_device which must be already existing partitions. In this case\n" " a single OSD is created.\n" + " Requires `vitastor-cli`, `blkid` and `sfdisk` utilities.\n" " OPTIONS may include:\n" " --hybrid\n" " Prepare hybrid (HDD+SSD) OSDs using provided devices. SSDs will be used for\n" @@ -45,6 +46,13 @@ static const char *help_text = " Use disks for OSD data even if they already have non-Vitastor partitions,\n" " but only if these take up no more than this percent of disk space.\n" "\n" + "vitastor-disk upgrade-simple \n" + " Upgrade an OSD created by old (0.7.1 and older) make-osd.sh or make-osd-hybrid.js scripts.\n" + " Adds superblocks to OSD devices, disables old vitastor-osdN unit and replaces it with vitastor-osd@N.\n" + " UNIT_FILE must be /etc/systemd/system/vitastor-osd.service.\n" + " Note that the procedure isn't atomic and may ruin OSD data if an error happens.\n" + " Requires the `sfdisk` utility.\n" + "\n" "vitastor-disk resize [--iodepth 32]\n" " Resize data area and/or rewrite/move journal and metadata\n" " ALL_OSD_PARAMETERS must include all (at least all disk-related)\n" @@ -263,6 +271,24 @@ int main(int argc, char *argv[]) } return self.pre_exec_osd(cmd[1]); } + else if (!strcmp(cmd[0], "prepare")) + { + std::vector devs; + for (int i = 1; i < cmd.size(); i++) + { + devs.push_back(cmd[i]); + } + return self.prepare(devs); + } + else if (!strcmp(cmd[0], "upgrade-simple")) + { + if (cmd.size() != 2) + { + fprintf(stderr, "Exactly 1 OSD number or systemd unit path is required\n"); + return 1; + } + return self.upgrade_simple_unit(cmd[1]); + } else { print_help(help_text, "vitastor-disk", cmd.size() > 1 ? cmd[1] : "", self.all); diff --git a/src/disk_tool.h b/src/disk_tool.h index 7e65d00a..6209322d 100644 --- a/src/disk_tool.h +++ b/src/disk_tool.h @@ -120,6 +120,8 @@ struct disk_tool_t json11::Json add_partitions(vitastor_dev_info_t & devinfo, std::vector sizes); std::vector get_new_data_parts(vitastor_dev_info_t & dev, uint64_t osd_per_disk, uint64_t max_other_percent); int get_meta_partition(std::vector & ssds, std::map & options); + + int upgrade_simple_unit(std::string unit); }; void disk_tool_simple_offsets(json11::Json cfg, bool json_output); diff --git a/src/disk_tool_prepare.cpp b/src/disk_tool_prepare.cpp index 3d9c183c..ee888770 100644 --- a/src/disk_tool_prepare.cpp +++ b/src/disk_tool_prepare.cpp @@ -255,7 +255,7 @@ json11::Json disk_tool_t::add_partitions(vitastor_dev_info_t & devinfo, std::vec { script += "+ "+size+" "+std::string(VITASTOR_PART_TYPE)+"\n"; } - if (shell_exec({ "/sbin/sfdisk", devinfo.path }, script, NULL, NULL) != 0) + if (shell_exec({ "/sbin/sfdisk", "--force", devinfo.path }, script, NULL, NULL) != 0) { fprintf(stderr, "Failed to add %lu partition(s) with sfdisk\n", sizes.size()); return {}; diff --git a/src/disk_tool_udev.cpp b/src/disk_tool_udev.cpp index 97638bad..caa023f3 100644 --- a/src/disk_tool_udev.cpp +++ b/src/disk_tool_udev.cpp @@ -1,6 +1,7 @@ // Copyright (c) Vitaliy Filippov, 2019+ // License: VNPL-1.1 (see README.md for details) +#include #include #include "disk_tool.h" @@ -109,6 +110,11 @@ uint32_t disk_tool_t::write_osd_superblock(std::string device, json11::Json para free(buf); return 0; } + // Lock the file + if (flock(fd, LOCK_EX|LOCK_NB) < 0) + { + fprintf(stderr, "Warning: Failed to lock %s with flock - udev autodetection may fail. Error: %s\n", device.c_str(), strerror(errno)); + } int r = write_blocking(fd, buf, buf_len); if (r < 0) { diff --git a/src/disk_tool_upgrade.cpp b/src/disk_tool_upgrade.cpp new file mode 100644 index 00000000..cad2d4ab --- /dev/null +++ b/src/disk_tool_upgrade.cpp @@ -0,0 +1,178 @@ +// Copyright (c) Vitaliy Filippov, 2019+ +// License: VNPL-1.1 (see README.md for details) + +#include +#include "disk_tool.h" +#include "str_util.h" + +static std::map read_vitastor_unit(std::string unit) +{ + std::smatch m; + if (unit == "" || !std::regex_match(unit, m, std::regex(".*/vitastor-osd\\d+\\.service"))) + { + fprintf(stderr, "unit file name does not match /vitastor-osd.service\n"); + return {}; + } + std::string text = read_file(unit); + if (!std::regex_search(text, m, std::regex("\nExecStart\\s*=[^\n]+vitastor-osd\\s*(([^\\\\\n&>\\d]+|\\\\[ \t\r]*\n|\\d[^>])+)"))) + { + fprintf(stderr, "Failed to extract ExecStart command from %s\n", unit.c_str()); + return {}; + } + std::string cmd = trim(m[1]); + cmd = str_replace(cmd, "\\\n", " "); + std::string key; + std::map r; + auto ns = std::regex("\\S+"); + for (auto it = std::sregex_token_iterator(cmd.begin(), cmd.end(), ns, 0), end = std::sregex_token_iterator(); + it != end; it++) + { + if (key == "" && ((std::string)(*it)).substr(0, 2) == "--") + key = ((std::string)(*it)).substr(2); + else if (key != "") + { + r[key] = *it; + key = ""; + } + } + return r; +} + +static int fix_partition_type(std::string dev_by_uuid) +{ + auto uuid = strtolower(dev_by_uuid.substr(dev_by_uuid.rfind('/')+1)); + std::string parent_dev = get_parent_device(realpath_str(dev_by_uuid, false)); + if (parent_dev == "") + return 1; + auto pt = read_parttable("/dev/"+parent_dev); + if (pt.is_null()) + return 1; + std::string script = "label: gpt\n\n"; + for (const auto & part: pt["partitions"].array_items()) + { + bool this_part = (strtolower(part["uuid"].string_value()) == uuid); + if (this_part && strtolower(part["type"].string_value()) == "e7009fac-a5a1-4d72-af72-53de13059903") + { + // Already correct type + return 0; + } + script += part["node"].string_value()+": "; + bool first = true; + for (const auto & kv: part.object_items()) + { + if (kv.first != "node") + { + script += (first ? "" : ", ")+kv.first+"="+ + (kv.first == "type" && this_part + ? "e7009fac-a5a1-4d72-af72-53de13059903" + : (kv.second.is_string() ? kv.second.string_value() : kv.second.dump())); + first = false; + } + } + script += "\n"; + } + return shell_exec({ "/sbin/sfdisk", "--no-reread", "--force", "/dev/"+parent_dev }, script, NULL, NULL); +} + +int disk_tool_t::upgrade_simple_unit(std::string unit) +{ + if (stoull_full(unit) != 0) + { + // OSD number + unit = "/etc/systemd/system/vitastor-osd"+unit+".service"; + } + auto options = read_vitastor_unit(unit); + if (!options.size()) + return 1; + if (!stoull_full(options["osd_num"], 10) || options["data_device"] == "") + { + fprintf(stderr, "osd_num or data_device are missing in %s\n", unit.c_str()); + return 1; + } + if (options["data_device"].substr(0, 22) != "/dev/disk/by-partuuid/" || + options["meta_device"] != "" && options["meta_device"].substr(0, 22) != "/dev/disk/by-partuuid/" || + options["journal_device"] != "" && options["journal_device"].substr(0, 22) != "/dev/disk/by-partuuid/") + { + fprintf( + stderr, "data_device, meta_device and journal_device must begin with" + " /dev/disk/by-partuuid/ i.e. they must be GPT partitions identified by UUIDs" + ); + return 1; + } + // Stop and disable the service + auto service_name = unit.substr(unit.rfind('/') + 1); + if (shell_exec({ "systemctl", "disable", "--now", service_name }, "", NULL, NULL) != 0) + { + return 1; + } + uint64_t j_o = stoull_full(options["journal_offset"]); + uint64_t m_o = stoull_full(options["meta_offset"]); + uint64_t d_o = stoull_full(options["data_offset"]); + bool m_is_d = options["meta_device"] == "" || options["meta_device"] == options["data_device"]; + bool j_is_m = options["journal_device"] == "" || options["journal_device"] == options["meta_device"]; + bool j_is_d = j_is_m && m_is_d || options["journal_device"] == options["data_device"]; + if (d_o < 4096 || j_o < 4096 || m_o < 4096) + { + // Resize data + uint64_t blk = stoull_full(options["block_size"]); + blk = blk ? blk : 128*1024; + std::map resize; + if (d_o < 4096 || m_is_d && m_o < 4096 && m_o < d_o || j_is_d && j_o < 4096 && j_o < d_o) + { + resize["new_data_offset"] = d_o+blk; + if (m_is_d && m_o < d_o) + resize["new_meta_offset"] = m_o+blk; + if (j_is_d && j_o < d_o) + resize["new_journal_offset"] = j_o+blk; + } + if (!m_is_d && m_o < 4096) + { + resize["new_meta_offset"] = m_o+4096; + if (j_is_m && m_o < j_o) + resize["new_journal_offset"] = j_o+4096; + } + if (!j_is_d && !j_is_m && j_o < 4096) + resize["new_journal_offset"] = j_o+4096; + disk_tool_t resizer; + resizer.options = options; + for (auto & kv: resize) + resizer.options[kv.first] = std::to_string(kv.second); + if (resizer.resize_data() != 0) + { + // FIXME: Resize with backup or journal + fprintf( + stderr, "Failed to resize data to make space for the superblock\n" + "Sorry, but your OSD may now be corrupted depending on what went wrong during resize :-(\n" + "Please review the messages above and take action accordingly\n" + ); + return 1; + } + for (auto & kv: resize) + options[kv.first.substr(4)] = std::to_string(kv.second); + } + // Write superblocks + if (!write_osd_superblock(options["data_device"], options) || + (!m_is_d && !write_osd_superblock(options["meta_device"], options)) || + (!j_is_m && !j_is_d && !write_osd_superblock(options["journal_device"], options))) + { + return 1; + } + // Change partition types + if (fix_partition_type(options["data_device"]) != 0 || + (!m_is_d && fix_partition_type(options["meta_device"]) != 0) || + (!j_is_m && !j_is_d && fix_partition_type(options["journal_device"]) != 0)) + { + return 1; + } + // Enable the new unit + if (shell_exec({ "systemctl", "enable", "--now", "vitastor-osd@"+options["osd_num"] }, "", NULL, NULL) != 0) + { + fprintf(stderr, "Failed to enable systemd unit vitastor-osd@%s\n", options["osd_num"].c_str()); + return 1; + } + fprintf( + stderr, "\nOK: Converted OSD %s to the new scheme. The new service name is vitastor-osd@%s\n", + options["osd_num"].c_str(), options["osd_num"].c_str() + ); + return 0; +} diff --git a/src/disk_tool_utils.cpp b/src/disk_tool_utils.cpp index 8bd39688..9750a544 100644 --- a/src/disk_tool_utils.cpp +++ b/src/disk_tool_utils.cpp @@ -148,13 +148,12 @@ int shell_exec(const std::vector & cmd, const std::string & in, std close(child_stdout[1]); close(child_stderr[0]); close(child_stderr[1]); - //char *argv[] = { (char*)"/bin/sh", (char*)"-c", (char*)cmd.c_str(), NULL }; char *argv[cmd.size()+1]; for (int i = 0; i < cmd.size(); i++) { argv[i] = (char*)cmd[i].c_str(); } - argv[cmd.size()-1] = NULL; + argv[cmd.size()] = NULL; execvp(argv[0], argv); std::string full_cmd; for (int i = 0; i < cmd.size(); i++)