blob: 6928b40cc5e2f13e3f1e2da9104f46defb24c4d3 [file]
/** @file
Code for class to manage configuration updates
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "mgmt/config/FileManager.h"
#include <vector>
#include <algorithm>
#include "tscore/ink_platform.h"
#include "tscore/ink_file.h"
#include "../../records/P_RecCore.h"
#include "tscore/Diags.h"
#include "tscore/Filenames.h"
#include "tscore/Layout.h"
#include "mgmt/config/ConfigRegistry.h"
#if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
#define TS_ARCHIVE_STAT_MTIME(t) ((t).st_mtime * 1000000000 + (t).st_mtimespec.tv_nsec)
#elif HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
#define TS_ARCHIVE_STAT_MTIME(t) ((t).st_mtime * 1000000000 + (t).st_mtim.tv_nsec)
#else
#define TS_ARCHIVE_STAT_MTIME(t) ((t).st_mtime * 1000000000)
#endif
namespace
{
DbgCtl dbg_ctl{"filemanager"};
swoc::Errata
process_config_update(std::string const &fileName, std::string const &configName)
{
Dbg(dbg_ctl, "Config update requested for '%s'. [%s]", fileName.empty() ? "Unknown" : fileName.c_str(),
configName.empty() ? "No config record associated" : configName.c_str());
swoc::Errata ret;
// records.yaml reload is now handled by its ConfigRegistry handler
// (registered in register_config_files() in traffic_server.cc).
// Delegate to ConfigRegistry::execute_reload("records") so the reload
// is traced and status-reported like every other config.
if (fileName == ts::filename::RECORDS) {
config::ConfigRegistry::Get_Instance().execute_reload("records");
} else if (!configName.empty()) { // Could be the case we have a child file to reload with no related config record.
RecT rec_type;
if (auto r = RecGetRecordType(configName.c_str(), &rec_type); r == REC_ERR_OKAY && rec_type == RECT_CONFIG) {
RecSetSyncRequired(configName.c_str());
} else {
Dbg(dbg_ctl, "Couldn't set RecSetSyncRequired for %s - RecGetRecordType ret = %d", configName.c_str(), r);
}
}
return ret;
}
// JSONRPC endpoint defs.
constexpr const char *CONFIG_REGISTRY_KEY_STR{"config_registry"};
constexpr const char *FILE_PATH_KEY_STR{"file_path"};
constexpr const char *RECORD_NAME_KEY_STR{"config_record_name"};
constexpr const char *PARENT_CONFIG_KEY_STR{"parent_config"};
constexpr const char *ROOT_ACCESS_NEEDED_KEY_STR{"root_access_needed"};
constexpr const char *IS_REQUIRED_KEY_STR{"is_required"};
constexpr const char *NA_STR{"N/A"};
} // namespace
FileManager::FileManager()
{
ink_mutex_init(&accessLock);
this->registerCallback(&process_config_update);
// Register the files registry jsonrpc endpoint
rpc::add_method_handler("filemanager.get_files_registry",
[this](std::string_view const &id, const YAML::Node &req) -> swoc::Rv<YAML::Node> {
return get_files_registry_rpc_endpoint(id, req);
},
&rpc::core_ats_rpc_service_provider_handle, {{rpc::NON_RESTRICTED_API}});
}
// FileManager::~FileManager
//
// There is only FileManager object in the process and it
// should never need to be destructed except at
// program exit
//
FileManager::~FileManager()
{
// Let other operations finish and do not start any new ones
ink_mutex_destroy(&accessLock);
}
// void FileManager::addFile(char* fileName, const configFileInfo* file_info,
// ConfigManager* parentConfig)
//
// for the baseFile, creates a ConfigManager object for it
//
// if file_info is not null, a WebFileEdit object is also created for
// the file
//
// Pointers to the new objects are stored in the bindings hashtable
//
void
FileManager::addFile(const char *fileName, const char *configName, bool root_access_needed, bool isRequired,
ConfigManager *parentConfig)
{
ink_mutex_acquire(&accessLock);
addFileHelper(fileName, configName, root_access_needed, isRequired, parentConfig);
ink_mutex_release(&accessLock);
}
// caller must hold the lock
void
FileManager::addFileHelper(const char *fileName, const char *configName, bool root_access_needed, bool isRequired,
ConfigManager *parentConfig)
{
ink_assert(fileName != nullptr);
auto configManager = std::make_unique<ConfigManager>(fileName, configName, root_access_needed, isRequired, parentConfig);
bindings.emplace(configManager->getFileName(), std::move(configManager));
}
swoc::Errata
FileManager::fileChanged(std::string const &fileName, std::string const &configName)
{
swoc::Errata ret;
std::lock_guard<std::mutex> guard(_callbacksMutex);
for (auto const &call : _configCallbacks) {
if (auto const &r = call(fileName, configName); !r) {
Dbg(dbg_ctl, "Something came back from the callback associated with %s", fileName.c_str());
ret.note(r);
}
}
return ret;
}
void
FileManager::registerConfigPluginCallbacks(std::function<void()> cb)
{
_pluginCallback = std::move(cb);
}
void
FileManager::invokeConfigPluginCallbacks()
{
Dbg(dbg_ctl, "invoke plugin callbacks");
if (_pluginCallback) {
_pluginCallback();
}
}
// void FileManger::rereadConfig()
//
// Iterates through the list of managed files and
// calls ConfigManager::checkForUserUpdate on them
//
// although it is tempting, DO NOT CALL FROM SIGNAL HANDLERS
// This function is not Async-Signal Safe. It
// is thread safe
swoc::Errata
FileManager::rereadConfig()
{
swoc::Errata ret;
ConfigManager *rb;
std::vector<ConfigManager *> changedFiles;
std::vector<ConfigManager *> parentFileNeedChange;
size_t n;
ink_mutex_acquire(&accessLock);
for (auto &&it : bindings) {
rb = it.second.get();
// ToDo: rb->isVersions() was always true before, because numberBackups was always >= 1. So ROLLBACK_CHECK_ONLY could not
// happen at all...
if (rb->checkForUserUpdate(FileManager::ROLLBACK_CHECK_AND_UPDATE)) {
Dbg(dbg_ctl, "File %s changed. Has a parent=%s, ", it.first.c_str(),
rb->getParentConfig() ? rb->getParentConfig()->getFileName() : "none");
if (auto const &r = fileChanged(rb->getFileName(), rb->getConfigName()); !r) {
ret.note(r);
}
changedFiles.push_back(rb);
if (rb->isChildManaged()) {
if (std::find(parentFileNeedChange.begin(), parentFileNeedChange.end(), rb->getParentConfig()) ==
parentFileNeedChange.end()) {
parentFileNeedChange.push_back(rb->getParentConfig());
}
}
}
}
std::vector<ConfigManager *> childFileNeedDelete;
n = changedFiles.size();
for (size_t i = 0; i < n; i++) {
if (changedFiles[i]->isChildManaged()) {
continue;
}
// for each parent file, if it is changed, then delete all its children
for (auto &&it : bindings) {
rb = it.second.get();
if (rb->getParentConfig() == changedFiles[i]) {
if (std::find(childFileNeedDelete.begin(), childFileNeedDelete.end(), rb) == childFileNeedDelete.end()) {
childFileNeedDelete.push_back(rb);
}
}
}
}
n = childFileNeedDelete.size();
for (size_t i = 0; i < n; i++) {
bindings.erase(childFileNeedDelete[i]->getFileName());
}
ink_mutex_release(&accessLock);
n = parentFileNeedChange.size();
for (size_t i = 0; i < n; i++) {
if (std::find(changedFiles.begin(), changedFiles.end(), parentFileNeedChange[i]) == changedFiles.end()) {
if (auto const &r = fileChanged(parentFileNeedChange[i]->getFileName(), parentFileNeedChange[i]->getConfigName()); !r) {
ret.note(r);
}
}
}
// INKqa11910
// need to first check that enable_customizations is enabled
auto enabled{RecGetRecordInt("proxy.config.body_factory.enable_customizations")};
auto found{enabled.has_value()};
if (found && enabled.value()) {
if (auto const &r = fileChanged("proxy.config.body_factory.template_sets_dir", "proxy.config.body_factory.template_sets_dir");
!r) {
ret.note(r);
}
}
if (auto const &r = fileChanged("proxy.config.ssl.server.ticket_key.filename", "proxy.config.ssl.server.ticket_key.filename");
!r) {
ret.note(r);
}
return ret;
}
bool
FileManager::isConfigStale()
{
ConfigManager *rb;
bool stale = false;
ink_mutex_acquire(&accessLock);
for (auto &&it : bindings) {
rb = it.second.get();
if (rb->checkForUserUpdate(FileManager::ROLLBACK_CHECK_ONLY)) {
stale = true;
break;
}
}
ink_mutex_release(&accessLock);
return stale;
}
// void configFileChild(const char *parent, const char *child)
//
// Add child to the bindings with parentConfig
void
FileManager::configFileChild(const char *parent, const char *child)
{
ConfigManager *parentConfig = nullptr;
ink_mutex_acquire(&accessLock);
if (auto it = bindings.find(parent); it != bindings.end()) {
Dbg(dbg_ctl, "Adding child file %s to %s parent", child, parent);
parentConfig = it->second.get();
addFileHelper(child, "", parentConfig->rootAccessNeeded(), parentConfig->getIsRequired(), parentConfig);
}
ink_mutex_release(&accessLock);
}
auto
FileManager::get_files_registry_rpc_endpoint(std::string_view const & /* id ATS_UNUSED */,
YAML::Node const & /* params ATS_UNUSED */) -> swoc::Rv<YAML::Node>
{
// If any error, the rpc manager will catch it and respond with it.
YAML::Node configs{YAML::NodeType::Sequence};
{
ink_scoped_mutex_lock lock(accessLock);
for (auto &&it : bindings) {
if (ConfigManager *cm = it.second.get(); cm) {
YAML::Node element{YAML::NodeType::Map};
std::string sysconfdir(RecConfigReadConfigDir());
element[FILE_PATH_KEY_STR] = Layout::get()->relative_to(sysconfdir, cm->getFileName());
element[RECORD_NAME_KEY_STR] = cm->getConfigName();
element[PARENT_CONFIG_KEY_STR] = (cm->isChildManaged() ? cm->getParentConfig()->getFileName() : NA_STR);
element[ROOT_ACCESS_NEEDED_KEY_STR] = cm->rootAccessNeeded();
element[IS_REQUIRED_KEY_STR] = cm->getIsRequired();
configs.push_back(element);
}
}
}
YAML::Node registry;
registry[CONFIG_REGISTRY_KEY_STR] = configs;
return registry;
}
/// ConfigFile
FileManager::ConfigManager::ConfigManager(const char *fileName_, const char *configName_, bool root_access_needed_,
bool isRequired_, ConfigManager *parentConfig_)
: root_access_needed(root_access_needed_), isRequired(isRequired_), parentConfig(parentConfig_)
{
// ExpandingArray existVer(25, true); // Existing versions
struct stat fileInfo;
ink_assert(fileName_ != nullptr);
// parent must not also have a parent
if (parentConfig) {
ink_assert(parentConfig->parentConfig == nullptr);
}
// Copy the file name.
fileName = std::string(fileName_);
configName = std::string(configName_);
ink_mutex_init(&fileAccessLock);
// Check to make sure that our configuration file exists
//
if (statFile(&fileInfo) < 0) {
Dbg(dbg_ctl, "%s Unable to load: %s", fileName.c_str(), strerror(errno));
if (isRequired) {
Dbg(dbg_ctl, " Unable to open required configuration file %s\n\t failed :%s", fileName.c_str(), strerror(errno));
}
} else {
fileLastModified = TS_ARCHIVE_STAT_MTIME(fileInfo);
}
}
//
//
// int ConfigManager::statFile()
//
// A wrapper for stat()
//
int
FileManager::ConfigManager::statFile(struct stat *buf)
{
int statResult;
std::string sysconfdir(RecConfigReadConfigDir());
std::string filePath = Layout::get()->relative_to(sysconfdir, fileName);
statResult = root_access_needed ? elevating_stat(filePath.c_str(), buf) : stat(filePath.c_str(), buf);
return statResult;
}
// bool ConfigManager::checkForUserUpdate(RollBackCheckType how)
//
// Called to check if the file has been changed by the user.
// Timestamps are compared to see if a change occurred
bool
FileManager::ConfigManager::checkForUserUpdate(FileManager::RollBackCheckType how)
{
struct stat fileInfo;
bool result;
ink_mutex_acquire(&fileAccessLock);
if (this->statFile(&fileInfo) < 0) {
// File doesn't exist. If it previously existed (fileLastModified > 0),
// treat the deletion as a change so the reload handler can fall back
// to an alternative config file (e.g. remap.yaml -> remap.config).
if (fileLastModified > 0) {
if (how == FileManager::ROLLBACK_CHECK_AND_UPDATE) {
fileLastModified = 0;
}
ink_mutex_release(&fileAccessLock);
return true;
}
ink_mutex_release(&fileAccessLock);
return false;
}
if (fileLastModified < TS_ARCHIVE_STAT_MTIME(fileInfo)) {
if (how == FileManager::ROLLBACK_CHECK_AND_UPDATE) {
fileLastModified = TS_ARCHIVE_STAT_MTIME(fileInfo);
// TODO: syslog????
}
result = true;
} else {
result = false;
}
ink_mutex_release(&fileAccessLock);
return result;
}