// ----------------------------------------------------------------------
// File: QuarkDBConfigEngine.cc
// Author: Andrea Manzi - 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 "mgm/config/QuarkDBConfigEngine.hh"
#include "mgm/config/QuarkConfigHandler.hh"
#include "mgm/XrdMgmOfs.hh"
#include "common/Timing.hh"
#include "common/StringUtils.hh"
#include
#include
#include "qclient/structures/QScanner.hh"
#include
#include
#include
using std::placeholders::_1;
EOSMGMNAMESPACE_BEGIN
//------------------------------------------------------------------------------
// **** QuarkDBCfgEngineChangelog class ****
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
QuarkDBCfgEngineChangelog::QuarkDBCfgEngineChangelog(qclient::QClient* client)
: mQcl(*client) {}
//------------------------------------------------------------------------------
// Add entry to the changelog
//------------------------------------------------------------------------------
void QuarkDBCfgEngineChangelog::AddEntry(const std::string& action,
const std::string& key, const std::string& value, const std::string& comment)
{
// Add entry to the set
std::ostringstream oss;
oss << std::time(NULL) << ": " << action;
if (key != "") {
oss << " " << key.c_str() << " => " << value.c_str();
}
if (!comment.empty()) {
oss << " [" << comment << "]";
}
mQcl.exec("deque-push-back", kChangelogKey, oss.str());
mQcl.exec("deque-trim-front", kChangelogKey, "500000");
}
//------------------------------------------------------------------------------
// Get tail of the changelog
//------------------------------------------------------------------------------
bool QuarkDBCfgEngineChangelog::Tail(unsigned int nlines, std::string& tail)
{
qclient::redisReplyPtr reply = mQcl.exec("deque-scan-back", kChangelogKey, "0",
"COUNT", SSTR(nlines)).get();
if (reply->type != REDIS_REPLY_ARRAY) {
return false;
}
if (reply->elements != 2) {
return false;
}
redisReply* array = reply->element[1];
std::ostringstream oss;
std::string stime;
for (size_t i = 0; i < array->elements; i++) {
if (array->element[i]->type != REDIS_REPLY_STRING) {
return false;
}
std::string line(array->element[i]->str, array->element[i]->len);
try {
time_t t = std::stoull(line.c_str());
stime = std::ctime(&t);
stime.erase(stime.length() - 1);
} catch (std::exception& e) {
stime = "unknown_timestamp";
}
for (size_t i = 0; i < line.size(); i++) {
if (line[i] == ':') {
line = line.substr(i + 2);
break;
}
}
oss << stime << ": " << line << std::endl;
}
tail = oss.str();
return true;
}
//------------------------------------------------------------------------------
// *** QuarkDBConfigEngine class ***
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
QuarkDBConfigEngine::QuarkDBConfigEngine(const QdbContactDetails&
contactDetails)
{
mQdbContactDetails = contactDetails;
mQcl = std::make_unique(mQdbContactDetails.members,
mQdbContactDetails.constructOptions());
mConfigHandler = std::make_unique(mQdbContactDetails);
mChangelog.reset(new QuarkDBCfgEngineChangelog(mQcl.get()));
mExecutor.reset(new folly::IOThreadPoolExecutor(2));
mCleanupThread.reset(&QuarkDBConfigEngine::CleanupThread, this);
}
//------------------------------------------------------------------------------
// Load a given configuration file
//------------------------------------------------------------------------------
bool
QuarkDBConfigEngine::LoadConfig(const std::string& filename, XrdOucString& err,
bool apply_stall_redirect)
{
eos_notice("msg=\"loading configuration\" name=%s ", filename.c_str());
if (filename.empty()) {
err = "error: you have to specify a configuration name";
return false;
}
ResetConfig(apply_stall_redirect);
common::Status st = PullFromQuarkDB(filename);
if (!st) {
err = st.toString().c_str();
return false;
}
if (!ApplyConfig(err, apply_stall_redirect)) {
mChangelog->AddEntry("loaded config", filename, SSTR("with failure : " << err));
return false;
} else {
mConfigFile = filename.c_str();
return true;
}
}
//------------------------------------------------------------------------------
// Store the current configuration to a given file or QuarkDB
//------------------------------------------------------------------------------
bool
QuarkDBConfigEngine::SaveConfig(std::string filename, bool overwrite,
const std::string& comment, XrdOucString& err)
{
using namespace std::chrono;
auto start = steady_clock::now();
if (filename.empty()) {
if (mConfigFile.length()) {
filename = mConfigFile.c_str();
overwrite = true;
} else {
err = "error: you have to specify a configuration name";
return false;
}
}
// Store a new hash
if (!overwrite) {
bool exists = true;
common::Status st = mConfigHandler->checkExistence(filename, exists);
if (!st.ok() || exists) {
errno = EEXIST;
err = "error: a configuration with name \"";
err += filename.c_str();
err += "\" exists already!";
return false;
}
}
StoreIntoQuarkDB(filename);
std::ostringstream changeLogValue;
if (overwrite) {
changeLogValue << "(force)";
}
changeLogValue << " successfully";
mChangelog->AddEntry("saved config", filename, changeLogValue.str(), comment);
mConfigFile = filename.c_str();
auto end = steady_clock::now();
auto duration = end - start;
eos_notice("msg=\"saved config\" name=\"%s\" comment=\"%s\" force=%d duration=\"%llu ms\"",
filename.c_str(), comment.c_str(), overwrite,
duration_cast(duration).count());
return true;
}
//------------------------------------------------------------------------------
// List the existing configurations
//------------------------------------------------------------------------------
bool
QuarkDBConfigEngine::ListConfigs(XrdOucString& configlist, bool showbackup)
{
std::vector configs, backups;
common::Status status = mConfigHandler->listConfigurations(configs, backups);
if (!status) {
configlist += "error: ";
configlist += status.toString().c_str();
return false;
}
configlist = "Existing Configurations on QuarkDB\n";
configlist += "================================\n";
for (auto it = configs.begin(); it != configs.end(); it++) {
configlist += "name: ";
configlist += it->c_str();
if (*it == mConfigFile.c_str()) {
configlist += " *";
}
configlist += "\n";
}
if (showbackup) {
configlist += "=======================================\n";
configlist += "Existing Backup Configurations on QuarkDB\n";
configlist += "=======================================\n";
for (auto it = backups.begin(); it != backups.end(); it++) {
configlist += "name: ";
configlist += it->c_str();
configlist += "\n";
}
}
return true;
}
//------------------------------------------------------------------------------
// Cleanup thread trimming the number of backups
//------------------------------------------------------------------------------
void QuarkDBConfigEngine::CleanupThread(ThreadAssistant& assistant)
{
while (!assistant.terminationRequested()) {
assistant.wait_for(std::chrono::minutes(30));
if (!assistant.terminationRequested()) {
size_t deleted;
common::Status st = mConfigHandler->trimBackups("default", 1000, deleted);
if (!st) {
eos_static_crit("unable to clean configuration backups: %s",
st.toString().c_str());
} else {
eos_static_info("deleted %d old configuration backups", deleted);
}
}
}
}
//------------------------------------------------------------------------------
// Pull the configuration from QuarkDB
//------------------------------------------------------------------------------
common::Status
QuarkDBConfigEngine::PullFromQuarkDB(const std::string& configName)
{
std::lock_guard lock(mMutex);
common::Status st = mConfigHandler->fetchConfiguration(configName,
sConfigDefinitions);
if (!st) {
return st;
}
sConfigDefinitions.erase("timestamp");
for (const auto& elem : sConfigDefinitions) {
eos_static_notice("msg=\"setting config\" key=\"%s\" value=\"%s\"",
elem.first.c_str(), elem.second.c_str());
}
return common::Status();
}
//------------------------------------------------------------------------------
// Filter the configuration and store in output string
//------------------------------------------------------------------------------
int
QuarkDBConfigEngine::FilterConfig(std::ostream& out,
const std::string& cfg_name)
{
std::map config;
common::Status st = mConfigHandler->fetchConfiguration(cfg_name, config);
if (!st) {
out << st.toString();
} else {
for (const auto& elem : config) {
out << elem.first << " => " << elem.second << "\n";
}
}
return st.getErrc();
}
//------------------------------------------------------------------------------
// Do an autosave
//------------------------------------------------------------------------------
bool
QuarkDBConfigEngine::AutoSave()
{
if (gOFS->mMaster->IsMaster() && mAutosave && mConfigFile.length()) {
std::string filename = mConfigFile.c_str();
bool overwrite = true;
XrdOucString err = "";
if (!SaveConfig(filename, overwrite, "", err)) {
eos_static_err("%s\n", err.c_str());
return false;
}
return true;
}
return false;
}
//------------------------------------------------------------------------------
// Set a configuration value
//------------------------------------------------------------------------------
void
QuarkDBConfigEngine::SetConfigValue(const char* prefix, const char* key,
const char* val, bool from_local,
bool save_config)
{
// If val is null or empty we don't save anything
if ((val == nullptr) || (strlen(val) == 0)) {
return;
}
eos_static_info("msg=\"store config\" key=\"%s\" val=\"%s\"", key, val);
std::string config_key = FormFullKey(prefix, key);
{
std::lock_guard lock(mMutex);
sConfigDefinitions[config_key] = val;
}
// In case the change is not coming from a broacast we can can broadcast it
// and add it to the changelog
if (from_local) {
// Make this value visible between MGM's
PublishConfigChange(config_key.c_str(), val);
mChangelog->AddEntry("set config", FormFullKey(prefix, key), val);
}
// If the change is not coming from a broacast we can can save it
if (from_local && save_config && mConfigFile.length()) {
std::string filename = mConfigFile.c_str();
bool overwrite = true;
XrdOucString err = "";
if (!SaveConfig(filename, overwrite, "", err)) {
eos_static_err("%s\n", err.c_str());
}
}
}
//------------------------------------------------------------------------------
// Delete a configuration value
//------------------------------------------------------------------------------
void
QuarkDBConfigEngine::DeleteConfigValue(const char* prefix, const char* key,
bool from_local)
{
std::string config_key = FormFullKey(prefix, key);
// In case the change is not coming from a broacast we can can broadcast it
if (from_local) {
// Make this value visible between MGM's
PublishConfigDeletion(config_key.c_str());
}
{
std::lock_guard lock(mMutex);
sConfigDefinitions.erase(config_key);
}
// If it's not coming from a broadcast we can add it to the changelog
if (from_local) {
mChangelog->AddEntry("del config", FormFullKey(prefix, key), "");
}
// If the change is not coming from a broacast we can can save it
if (from_local && mConfigFile.length()) {
std::string filename = mConfigFile.c_str();
bool overwrite = true;
XrdOucString err = "";
if (!SaveConfig(filename, overwrite, "", err)) {
eos_static_err("%s\n", err.c_str());
}
}
eos_static_debug("%s", key);
}
//------------------------------------------------------------------------------
// Check write configuration result
//------------------------------------------------------------------------------
void checkWriteConfigurationResult(common::Status st)
{
if (!st.ok()) {
eos_static_crit("Failed to save MGM configuration !!!! %s",
st.toString().c_str());
}
}
//------------------------------------------------------------------------------
// Store configuration into given keyname
//------------------------------------------------------------------------------
void QuarkDBConfigEngine::StoreIntoQuarkDB(const std::string& name)
{
std::lock_guard lock(mMutex);
FilterDeprecated(sConfigDefinitions);
mConfigHandler->writeConfiguration(name, sConfigDefinitions, true,
FormatBackupTime(time(NULL)))
.via(mExecutor.get())
.thenValue(std::bind(checkWriteConfigurationResult, _1));
}
EOSMGMNAMESPACE_END