icinga2/lib/icingadb/icingadb.cpp

310 lines
8.5 KiB
C++
Raw Normal View History

2019-11-02 14:00:06 +01:00
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2017-09-25 14:41:43 +02:00
2019-10-29 18:36:16 +01:00
#include "icingadb/icingadb.hpp"
#include "icingadb/icingadb-ti.cpp"
#include "icingadb/redisconnection.hpp"
#include "remote/apilistener.hpp"
2017-09-25 14:41:43 +02:00
#include "remote/eventqueue.hpp"
#include "base/configuration.hpp"
2017-09-25 14:41:43 +02:00
#include "base/json.hpp"
#include "base/perfdatavalue.hpp"
#include "base/statsfunction.hpp"
#include "base/tlsutility.hpp"
#include "base/utility.hpp"
#include "icinga/checkable.hpp"
#include "icinga/host.hpp"
2017-10-12 17:46:06 +02:00
#include <boost/algorithm/string.hpp>
#include <fstream>
2019-11-02 18:01:31 +01:00
#include <memory>
#include <utility>
2017-09-25 14:41:43 +02:00
using namespace icinga;
2017-10-12 17:46:06 +02:00
#define MAX_EVENTS_DEFAULT 5000
using Prio = RedisConnection::QueryPriority;
String IcingaDB::m_EnvironmentId;
std::mutex IcingaDB::m_EnvironmentIdInitMutex;
REGISTER_TYPE(IcingaDB);
2017-09-25 14:41:43 +02:00
IcingaDB::IcingaDB()
2019-11-02 18:01:31 +01:00
: m_Rcon(nullptr)
2017-10-12 11:47:49 +02:00
{
m_RconLocked.store(nullptr);
m_WorkQueue.SetName("IcingaDB");
m_PrefixConfigObject = "icinga:";
2019-06-14 11:52:34 +02:00
m_PrefixConfigCheckSum = "icinga:checksum:";
2017-10-12 11:47:49 +02:00
}
2017-09-25 14:41:43 +02:00
2021-07-22 14:34:07 +02:00
void IcingaDB::Validate(int types, const ValidationUtils& utils)
{
ObjectImpl<IcingaDB>::Validate(types, utils);
if (!(types & FAConfig))
return;
if (GetEnableTls() && GetCertPath().IsEmpty() != GetKeyPath().IsEmpty()) {
BOOST_THROW_EXCEPTION(ValidationError(this, std::vector<String>(), "Validation failed: Either both a client certificate (cert_path) and its private key (key_path) or none of them must be given."));
}
try {
InitEnvironmentId();
} catch (const std::exception& e) {
BOOST_THROW_EXCEPTION(ValidationError(this, std::vector<String>(),
String("Validation failed: ") + e.what()));
}
2021-07-22 14:34:07 +02:00
}
2017-09-25 14:41:43 +02:00
/**
* Starts the component.
*/
void IcingaDB::Start(bool runtimeCreated)
2017-09-25 14:41:43 +02:00
{
ObjectImpl<IcingaDB>::Start(runtimeCreated);
2017-09-25 14:41:43 +02:00
VERIFY(!m_EnvironmentId.IsEmpty());
PersistEnvironmentId();
Log(LogInformation, "IcingaDB")
2018-05-15 14:43:01 +02:00
<< "'" << GetName() << "' started.";
2017-09-25 14:41:43 +02:00
m_ConfigDumpInProgress = false;
m_ConfigDumpDone = false;
m_WorkQueue.SetExceptionCallback([this](boost::exception_ptr exp) { ExceptionHandler(std::move(exp)); });
2017-09-25 14:41:43 +02:00
2021-07-22 14:34:07 +02:00
m_Rcon = new RedisConnection(GetHost(), GetPort(), GetPath(), GetPassword(), GetDbIndex(),
GetEnableTls(), GetInsecureNoverify(), GetCertPath(), GetKeyPath(), GetCaPath(), GetCrlPath(),
2021-07-26 16:14:51 +02:00
GetTlsProtocolmin(), GetCipherList(), GetConnectTimeout(), GetDebugInfo());
m_RconLocked.store(m_Rcon);
for (const Type::Ptr& type : GetTypes()) {
auto ctype (dynamic_cast<ConfigType*>(type.get()));
if (!ctype)
continue;
RedisConnection::Ptr con = new RedisConnection(GetHost(), GetPort(), GetPath(), GetPassword(), GetDbIndex(),
GetEnableTls(), GetInsecureNoverify(), GetCertPath(), GetKeyPath(), GetCaPath(), GetCrlPath(),
GetTlsProtocolmin(), GetCipherList(), GetConnectTimeout(), GetDebugInfo(), m_Rcon);
con->SetConnectedCallback([this, con](boost::asio::yield_context& yc) {
con->SetConnectedCallback(nullptr);
size_t pending = --m_PendingRcons;
Log(LogDebug, "IcingaDB") << pending << " pending child connections remaining";
if (pending == 0) {
m_WorkQueue.Enqueue([this]() { OnConnectedHandler(); });
}
});
m_Rcons[ctype] = std::move(con);
}
m_PendingRcons = m_Rcons.size();
m_Rcon->SetConnectedCallback([this](boost::asio::yield_context& yc) {
m_Rcon->SetConnectedCallback(nullptr);
for (auto& kv : m_Rcons) {
kv.second->Start();
}
});
m_Rcon->Start();
2017-09-25 14:41:43 +02:00
m_StatsTimer = new Timer();
m_StatsTimer->SetInterval(1);
m_StatsTimer->OnTimerExpired.connect([this](const Timer * const&) { PublishStatsTimerHandler(); });
2017-09-25 14:41:43 +02:00
m_StatsTimer->Start();
m_WorkQueue.SetName("IcingaDB");
2017-10-02 09:59:11 +02:00
m_Rcon->SuppressQueryKind(Prio::CheckResult);
m_Rcon->SuppressQueryKind(Prio::RuntimeStateSync);
Ptr keepAlive (this);
m_HistoryThread = std::async(std::launch::async, [this, keepAlive]() { ForwardHistoryEntries(); });
2017-09-25 14:41:43 +02:00
}
void IcingaDB::ExceptionHandler(boost::exception_ptr exp)
2017-09-25 14:41:43 +02:00
{
Log(LogCritical, "IcingaDB", "Exception during redis query. Verify that Redis is operational.");
2017-09-25 14:41:43 +02:00
Log(LogDebug, "IcingaDB")
2018-05-15 14:43:01 +02:00
<< "Exception during redis operation: " << DiagnosticInformation(exp);
2017-09-25 14:41:43 +02:00
}
void IcingaDB::OnConnectedHandler()
2017-09-25 14:41:43 +02:00
{
AssertOnWorkQueue();
if (m_ConfigDumpInProgress || m_ConfigDumpDone)
2017-09-25 14:41:43 +02:00
return;
2017-10-12 11:47:49 +02:00
2017-09-25 14:41:43 +02:00
/* Config dump */
m_ConfigDumpInProgress = true;
2019-01-28 15:19:10 +01:00
PublishStats();
2017-09-25 14:41:43 +02:00
UpdateAllConfigObjects();
m_ConfigDumpDone = true;
2017-09-25 14:41:43 +02:00
m_ConfigDumpInProgress = false;
}
void IcingaDB::PublishStatsTimerHandler(void)
2017-09-25 14:41:43 +02:00
{
PublishStats();
2017-09-25 14:41:43 +02:00
}
void IcingaDB::PublishStats()
2017-09-25 14:41:43 +02:00
{
if (!m_Rcon || !m_Rcon->IsConnected())
2018-10-26 14:07:07 +02:00
return;
2018-06-08 11:38:36 +02:00
Dictionary::Ptr status = GetStats();
2019-01-28 15:19:10 +01:00
status->Set("config_dump_in_progress", m_ConfigDumpInProgress);
status->Set("timestamp", TimestampToMilliseconds(Utility::GetTime()));
status->Set("icingadb_environment", m_EnvironmentId);
2017-09-25 14:41:43 +02:00
std::vector<String> query {"XADD", "icinga:stats", "MAXLEN", "1", "*"};
{
ObjectLock statusLock (status);
for (auto& kv : status) {
query.emplace_back(kv.first);
query.emplace_back(JsonEncode(kv.second));
}
}
m_Rcon->FireAndForgetQuery(std::move(query), Prio::Heartbeat);
2017-09-25 14:41:43 +02:00
}
void IcingaDB::Stop(bool runtimeRemoved)
2017-09-25 14:41:43 +02:00
{
Log(LogInformation, "IcingaDB")
<< "Flushing history data buffer to Redis.";
if (m_HistoryThread.wait_for(std::chrono::minutes(1)) == std::future_status::timeout) {
Log(LogCritical, "IcingaDB")
<< "Flushing takes more than one minute (while we're about to shut down). Giving up and discarding "
<< m_HistoryBulker.Size() << " queued history queries.";
}
Log(LogInformation, "IcingaDB")
2018-05-15 14:43:01 +02:00
<< "'" << GetName() << "' stopped.";
2017-09-25 14:41:43 +02:00
ObjectImpl<IcingaDB>::Stop(runtimeRemoved);
2017-09-25 14:41:43 +02:00
}
2021-07-22 14:34:07 +02:00
void IcingaDB::ValidateTlsProtocolmin(const Lazy<String>& lvalue, const ValidationUtils& utils)
{
ObjectImpl<IcingaDB>::ValidateTlsProtocolmin(lvalue, utils);
try {
ResolveTlsProtocolVersion(lvalue());
} catch (const std::exception& ex) {
BOOST_THROW_EXCEPTION(ValidationError(this, { "tls_protocolmin" }, ex.what()));
}
}
2021-07-26 16:14:51 +02:00
void IcingaDB::ValidateConnectTimeout(const Lazy<double>& lvalue, const ValidationUtils& utils)
{
ObjectImpl<IcingaDB>::ValidateConnectTimeout(lvalue, utils);
if (lvalue() <= 0) {
BOOST_THROW_EXCEPTION(ValidationError(this, { "connect_timeout" }, "Value must be greater than 0."));
}
}
void IcingaDB::AssertOnWorkQueue()
2017-09-25 14:41:43 +02:00
{
ASSERT(m_WorkQueue.IsWorkerThread());
}
void IcingaDB::DumpedGlobals::Reset()
{
std::lock_guard<std::mutex> l (m_Mutex);
m_Ids.clear();
}
String IcingaDB::GetEnvironmentId() const {
return m_EnvironmentId;
}
bool IcingaDB::DumpedGlobals::IsNew(const String& id)
{
std::lock_guard<std::mutex> l (m_Mutex);
return m_Ids.emplace(id).second;
}
/**
* Initializes the m_EnvironmentId attribute or throws an exception on failure to do so. Can be called concurrently.
*/
void IcingaDB::InitEnvironmentId()
{
// Initialize m_EnvironmentId once across all IcingaDB objects. In theory, this could be done using
// std::call_once, however, due to a bug in libstdc++ (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66146),
// this can result in a deadlock when an exception is thrown (which is explicitly allowed by the standard).
std::unique_lock<std::mutex> lock (m_EnvironmentIdInitMutex);
if (m_EnvironmentId.IsEmpty()) {
String path = Configuration::DataDir + "/icingadb.env";
String envId;
if (Utility::PathExists(path)) {
envId = Utility::LoadJsonFile(path);
if (envId.GetLength() != 2*SHA_DIGEST_LENGTH) {
throw std::runtime_error("environment ID stored at " + path + " is corrupt: wrong length.");
}
for (unsigned char c : envId) {
if (!std::isxdigit(c)) {
throw std::runtime_error("environment ID stored at " + path + " is corrupt: invalid hex string.");
}
}
} else {
String caPath = ApiListener::GetDefaultCaPath();
if (!Utility::PathExists(caPath)) {
throw std::runtime_error("Cannot find the CA certificate at '" + caPath + "'. "
"Please ensure the ApiListener is enabled first using 'icinga2 api setup'.");
}
std::shared_ptr<X509> cert = GetX509Certificate(caPath);
unsigned int n;
unsigned char digest[EVP_MAX_MD_SIZE];
if (X509_pubkey_digest(cert.get(), EVP_sha1(), digest, &n) != 1) {
BOOST_THROW_EXCEPTION(openssl_error()
<< boost::errinfo_api_function("X509_pubkey_digest")
<< errinfo_openssl_error(ERR_peek_error()));
}
envId = BinaryToHex(digest, n);
}
m_EnvironmentId = envId.ToLower();
}
}
/**
* Ensures that the environment ID is persisted on disk or throws an exception on failure to do so.
* Can be called concurrently.
*/
void IcingaDB::PersistEnvironmentId()
{
String path = Configuration::DataDir + "/icingadb.env";
std::unique_lock<std::mutex> lock (m_EnvironmentIdInitMutex);
if (!Utility::PathExists(path)) {
Utility::SaveJsonFile(path, 0600, m_EnvironmentId);
}
}