Rewrite upgrade-simple to C++

rm-left-on-dead
Vitaliy Filippov 2022-08-17 23:57:56 +03:00
parent a16263e88c
commit 40d8d65188
10 changed files with 217 additions and 147 deletions

View File

@ -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

View File

@ -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

View File

@ -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<NUMBER>.service');
let service_name = /^.*\/(vitastor-osd\d+)\.service$/.exec(unit);
if (!service_name)
throw new Error(unit+' is not a service named vitastor-osd<number>');
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 ? " <<EOF\n"+options.input.replace(/\s*$/, '\n')+"EOF" : '')+'\n');
const cp = child_process.spawn(cmd, {
shell: true,
stdio: [ 'pipe', options.get_out ? 'pipe' : 1, options.get_err ? 'pipe' : 1 ],
});
let stdout = '', stderr = '', finish_cb;
if (options.get_out)
cp.stdout.on('data', buf => 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 ];
}

View File

@ -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

View File

@ -18,6 +18,7 @@ static const char *help_text =
" In the second form, you omit <devices> 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 <UNIT_FILE|OSD_NUMBER>\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<OSD_NUMBER>.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 <ALL_OSD_PARAMETERS> <NEW_LAYOUT> [--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<std::string> 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);

View File

@ -120,6 +120,8 @@ struct disk_tool_t
json11::Json add_partitions(vitastor_dev_info_t & devinfo, std::vector<std::string> sizes);
std::vector<std::string> 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<vitastor_dev_info_t> & ssds, std::map<std::string, std::string> & options);
int upgrade_simple_unit(std::string unit);
};
void disk_tool_simple_offsets(json11::Json cfg, bool json_output);

View File

@ -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 {};

View File

@ -1,6 +1,7 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)
#include <sys/file.h>
#include <dirent.h>
#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)
{

178
src/disk_tool_upgrade.cpp Normal file
View File

@ -0,0 +1,178 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)
#include <regex>
#include "disk_tool.h"
#include "str_util.h"
static std::map<std::string, std::string> 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 <path>/vitastor-osd<NUMBER>.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<std::string, std::string> 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<std::string, uint64_t> 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;
}

View File

@ -148,13 +148,12 @@ int shell_exec(const std::vector<std::string> & 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++)