// ----------------------------------------------------------------------
// File: eos-config-export.cc
// Author: Georgios Bitzes - CERN
// ----------------------------------------------------------------------
/************************************************************************
* EOS - the CERN Disk Storage System *
* Copyright (C) 2011 CERN/Switzerland *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see .*
************************************************************************/
#include "namespace/ns_quarkdb/QdbContactDetails.hh"
#include "common/config/ConfigParsing.hh"
#include "common/CLI11.hpp"
#include "common/PasswordHandler.hh"
#include "common/StringUtils.hh"
#include "mgm/config/QuarkConfigHandler.hh"
#include
#include
#include
#define SSTR(message) static_cast(std::ostringstream().flush() << message).str()
struct MemberValidator : public CLI::Validator {
MemberValidator() : Validator("MEMBER")
{
func_ = [](const std::string & str) {
qclient::Members members;
if (!members.parse(str)) {
return SSTR("Could not parse members: '" << str <<
"'. Expected format is a comma-separated list of servers: example1:1111,example2:2222");
}
return std::string();
};
}
};
//------------------------------------------------------------------------------
// Given a subcommand, add common-to-all options such as --members
// and --password
//----------------------------------------------------------------------------
void addClusterOptions(CLI::App* subcmd, std::string& membersStr,
MemberValidator& memberValidator, std::string& password,
std::string& passwordFile)
{
subcmd->add_option("--members", membersStr,
"One or more members of the QDB cluster")
->required()
->check(memberValidator);
auto passwordGroup = subcmd->add_option_group("Authentication",
"Specify QDB authentication options");
passwordGroup->add_option("--password", password,
"The password for connecting to the QDB cluster - can be empty");
passwordGroup->add_option("--password-file", passwordFile,
"The passwordfile for connecting to the QDB cluster - can be empty");
passwordGroup->require_option(0, 1);
}
//------------------------------------------------------------------------------
// Read source configuration file
//------------------------------------------------------------------------------
bool readConfigurationFile(const std::string& sourceFile,
std::string& fullContents)
{
std::ifstream infile(sourceFile.c_str());
if (!infile.is_open()) {
return false;
}
std::ostringstream ss;
while (!infile.eof()) {
std::string s;
std::getline(infile, s);
if (!s.empty()) {
ss << s << "\n";
}
}
infile.close();
fullContents = ss.str();
return true;
}
//------------------------------------------------------------------------------
// Read and parse configuration file
//------------------------------------------------------------------------------
bool readAndParseConfiguration(const std::string& path,
std::map& configuration)
{
//----------------------------------------------------------------------------
// Read source configuration file
//----------------------------------------------------------------------------
std::string fullContents;
if (!readConfigurationFile(path, fullContents) || fullContents.empty()) {
std::cerr << "could not read configuration file: " << path << std::endl;
return false;
}
//----------------------------------------------------------------------------
// Parse
//----------------------------------------------------------------------------
std::string err;
if (!eos::common::ConfigParsing::parseConfigurationFile(fullContents,
configuration, err)) {
std::cerr << "Could not parse configuration file: " << err << std::endl;
return false;
}
std::cerr << "--- Successfully parsed configuration file" << std::endl;
return true;
}
int runDumpSubcommand(const std::string& configEntry,
eos::mgm::QuarkConfigHandler& configHandler)
{
std::map configuration;
eos::common::Status status = configHandler.fetchConfiguration(configEntry,
configuration);
if (!status) {
std::cerr << "error while fetching configuration '" << configEntry << "' : " <<
status.toString() << std::endl;
return 1;
}
for (auto it = configuration.begin(); it != configuration.end(); it++) {
std::cout << it->first << " => " << it->second << std::endl;
}
return 0;
}
int runExportSubcommand(const std::string& sourceFile,
eos::mgm::QuarkConfigHandler& configHandler,
bool overwrite)
{
std::map configuration;
if (!readAndParseConfiguration(sourceFile, configuration)) {
return 1;
}
eos::common::Status st = configHandler.writeConfiguration("default",
configuration, overwrite).get();
if (!st) {
std::cerr << "ERROR: " << st.toString() << std::endl;
return 1;
}
std::cerr << "--- Operation successful - wrote configuration 'default' with " <<
configuration.size() << " entries" << std::endl;
return 0;
}
int runListSubcommand(eos::mgm::QuarkConfigHandler& configHandler)
{
std::vector configs, backups;
eos::common::Status st = configHandler.listConfigurations(configs, backups);
if (!st) {
std::cerr << "ERROR: " << st.toString() << std::endl;
return 1;
}
std::cout << "Stored configurations:" << std::endl;
for (auto it = configs.begin(); it != configs.end(); it++) {
std::cout << " " << *it << std::endl;
}
std::cout << std::endl;
std::cout << "Stored backups:" << std::endl;
for (auto it = backups.begin(); it != backups.end(); it++) {
std::cout << " " << *it << std::endl;
}
return 0;
}
int runTailSubcommand(size_t nlines,
eos::mgm::QuarkConfigHandler& configHandler)
{
std::vector changelog;
eos::common::Status st = configHandler.tailChangelog(nlines, changelog);
if (!st) {
std::cerr << st.toString() << std::endl;
return 1;
}
for (auto it = changelog.begin(); it != changelog.end(); it++) {
std::cout << *it << std::endl;
}
return 0;
}
int runRelocateFilesystemSubcommand(eos::mgm::QuarkConfigHandler& configHandler,
uint32_t fsid, const std::string& newhost, int newport)
{
std::map configMap;
eos::common::Status st = configHandler.fetchConfiguration("default", configMap);
if (!st) {
std::cerr << "could not fetch configuration: " << st.toString() << std::endl;
return 1;
}
for (auto it = configMap.begin(); it != configMap.end(); it++) {
if (eos::common::startsWith(it->first, "fs:")) {
std::map configEntry;
if (!eos::common::ConfigParsing::parseFilesystemConfig(it->second,
configEntry)) {
std::cerr << "could not parse fs entry: " << it->first << it->second <<
std::endl;
return 1;
}
if (configEntry["id"] == SSTR(fsid)) {
// We have a match
std::cout << "Found filesystem with fsid=" << fsid << ": " <<
configEntry["queue"] << std::endl;
std::cout << it->first << " " << it->second << std::endl;
st = eos::common::ConfigParsing::relocateFilesystem(newhost, newport,
configEntry);
if (!st) {
std::cerr << "filesystem relocation failed: " << st.toString() << std::endl;
return 1;
}
std::string configKey = SSTR("fs:" << configEntry["queuepath"]);
std::string newConfig = eos::common::joinMap(configEntry, " ");
std::cout << "After relocation: " << configKey << " " << newConfig << std::endl;
configMap.erase(it);
configMap[configKey] = newConfig;
char buff[128];
time_t timestamp = time(NULL);
strftime(buff, 127, "%Y%m%d%H%M%S", localtime(×tamp));
std::string backupName = SSTR("default-" << buff << "-relocation");
st = configHandler.writeConfiguration("default", configMap, true,
backupName).get();
if (!st) {
std::cerr << "writing configuration failed: " << st.toString() << std::endl;
return 1;
}
std::cout << "Successfully wrote configuration, backup key: " << backupName <<
std::endl;
return 0;
}
}
}
std::cerr << "no filesystem found with fsid=" << fsid << std::endl;
return 1;
}
//------------------------------------------------------------------------------
// Trim backups subcommand
//------------------------------------------------------------------------------
int runTrimBackupsSubcommand(size_t limit,
eos::mgm::QuarkConfigHandler& configHandler)
{
size_t deleted = 0;
eos::common::Status st = configHandler.trimBackups("default", limit, deleted);
if (!st) {
std::cerr << st.toString() << std::endl;
return st.getErrc();
}
std::cout << "deleted " << deleted << " config backups" << std::endl;
return 0;
}
int main(int argc, char* argv[])
{
CLI::App app("Tool to inspect contents of the QuarkDB-based EOS configuration.");
app.require_subcommand();
//----------------------------------------------------------------------------
// Parameters common to all subcommands
//----------------------------------------------------------------------------
std::string membersStr;
MemberValidator memberValidator;
std::string password;
std::string passwordFile;
//----------------------------------------------------------------------------
// Set-up export subcommand..
//----------------------------------------------------------------------------
auto exportSubcommand = app.add_subcommand("export",
"[DANGEROUS] Read a legacy file-based configuration file, and export to QDB. Ensure the MGM is not running while you run this command!");
std::string sourceFile;
exportSubcommand->add_option("--source", sourceFile,
"Path to the source configuration file to export")
->required();
bool overwrite = false;
exportSubcommand->add_flag("--overwrite", overwrite,
"Overwrite already-existing configuration in QDB.");
addClusterOptions(exportSubcommand, membersStr, memberValidator, password,
passwordFile);
//----------------------------------------------------------------------------
// Set-up relocate-filesystem subcommand..
//----------------------------------------------------------------------------
auto relocateFilesystemSubcommand = app.add_subcommand("relocate-filesystem",
"[DANGEROUS] Change the FST to which a filesystem belongs to");
uint32_t fsid;
relocateFilesystemSubcommand->add_option("--fsid", fsid,
"The ID of the filesystem to change")
->required();
std::string newFstHost;
relocateFilesystemSubcommand->add_option("--new-fst-host", newFstHost,
"The new FST host")
->required();
int newFstPort;
relocateFilesystemSubcommand->add_option("--new-fst-port", newFstPort,
"The new FST port")
->required();
addClusterOptions(relocateFilesystemSubcommand, membersStr, memberValidator,
password,
passwordFile);
//----------------------------------------------------------------------------
// Set-up dump subcommand..
//----------------------------------------------------------------------------
auto dumpSubcommand = app.add_subcommand("dump",
"Dump the contents of a given configuration stored in QDB");
std::string configEntry = "default";
dumpSubcommand->add_option("--config", configEntry,
"Configuration to dump (from 'list'), default is actual");
addClusterOptions(dumpSubcommand, membersStr, memberValidator, password,
passwordFile);
//----------------------------------------------------------------------------
// Set-up list subcommand..
//----------------------------------------------------------------------------
auto listSubcommand = app.add_subcommand("list",
"List all stored configurations, including backups");
addClusterOptions(listSubcommand, membersStr, memberValidator, password,
passwordFile);
//----------------------------------------------------------------------------
// Set-up tail-changelog subcommand..
//----------------------------------------------------------------------------
auto tailSubcommand = app.add_subcommand("tail-changelog",
"Tail configuration changelog");
size_t nlines = 1000;
tailSubcommand->add_option("--nlines", nlines,
"The maximum number of changelog entries to print");
addClusterOptions(tailSubcommand, membersStr, memberValidator, password,
passwordFile);
//----------------------------------------------------------------------------
// Set-up trim-backups subcommand..
//----------------------------------------------------------------------------
auto trimBackupsSubcommand = app.add_subcommand("trim-backups",
"Trim number of configuration backups");
size_t nbackups = 1000;
trimBackupsSubcommand->add_option("--limit", nbackups,
"The maximum number of backups to keep");
addClusterOptions(trimBackupsSubcommand, membersStr, memberValidator, password,
passwordFile);
//----------------------------------------------------------------------------
// Parse
//----------------------------------------------------------------------------
try {
app.parse(argc, argv);
} catch (const CLI::ParseError& e) {
return app.exit(e);
}
//----------------------------------------------------------------------------
// Validate --password and --password-file options..
//----------------------------------------------------------------------------
if (!passwordFile.empty()) {
if (!eos::common::PasswordHandler::readPasswordFile(passwordFile, password)) {
std::cerr << "Could not read passwordfile: '" << passwordFile <<
"'. Ensure the file exists, and its permissions are 400." << std::endl;
return 1;
}
}
//----------------------------------------------------------------------------
// Set-up QClient object towards QDB, ensure sanity
//----------------------------------------------------------------------------
qclient::Members members = qclient::Members::fromString(membersStr);
eos::QdbContactDetails contactDetails(members, password);
eos::mgm::QuarkConfigHandler configHandler(contactDetails);
//----------------------------------------------------------------------------
// Ensure connection is sane
//----------------------------------------------------------------------------
eos::common::Status status = configHandler.checkConnection(std::chrono::seconds(
3));
if (!status) {
std::cerr << "could not connect to QDB backend: " << status.toString() <<
std::endl;
return 1;
}
if (exportSubcommand->parsed()) {
return runExportSubcommand(sourceFile, configHandler, overwrite);
} else if (relocateFilesystemSubcommand->parsed()) {
return runRelocateFilesystemSubcommand(configHandler, fsid, newFstHost,
newFstPort);
} else if (dumpSubcommand->parsed()) {
return runDumpSubcommand(configEntry, configHandler);
} else if (listSubcommand->parsed()) {
return runListSubcommand(configHandler);
} else if (tailSubcommand->parsed()) {
return runTailSubcommand(nlines, configHandler);
} else if (trimBackupsSubcommand->parsed()) {
return runTrimBackupsSubcommand(nbackups, configHandler);
}
std::cerr << "No subcommand was supplied - should never reach here" <<
std::endl;
return 1;
}