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"
2021-09-21 12:56:20 +02:00
# include "remote/apilistener.hpp"
2017-09-25 14:41:43 +02:00
# include "remote/eventqueue.hpp"
2021-09-21 12:56:20 +02:00
# include "base/configuration.hpp"
2017-09-25 14:41:43 +02:00
# include "base/json.hpp"
2022-01-26 14:02:48 +01:00
# include "base/perfdatavalue.hpp"
# include "base/statsfunction.hpp"
2021-09-21 12:56:20 +02:00
# include "base/tlsutility.hpp"
# include "base/utility.hpp"
2018-10-30 16:00:41 +01:00
# include "icinga/checkable.hpp"
# include "icinga/host.hpp"
2017-10-12 17:46:06 +02:00
# include <boost/algorithm/string.hpp>
2021-09-21 12:56:20 +02:00
# include <fstream>
2019-11-02 18:01:31 +01:00
# include <memory>
2019-06-28 11:56:15 +02:00
# 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
2017-10-11 14:19:42 +02:00
2019-11-27 11:08:22 +01:00
using Prio = RedisConnection : : QueryPriority ;
2021-03-19 15:31:19 +01:00
String IcingaDB : : m_EnvironmentId ;
2021-09-21 12:56:20 +02:00
std : : once_flag IcingaDB : : m_EnvironmentIdOnce ;
2021-03-19 15:31:19 +01:00
2019-10-29 17:32:29 +01:00
REGISTER_TYPE ( IcingaDB ) ;
2017-09-25 14:41:43 +02:00
2022-01-26 14:02:48 +01:00
REGISTER_STATSFUNCTION ( IcingaDB , & IcingaDB : : StatsFunc ) ;
2019-10-29 17:32:29 +01:00
IcingaDB : : IcingaDB ( )
2019-11-02 18:01:31 +01:00
: m_Rcon ( nullptr )
2017-10-12 11:47:49 +02:00
{
2018-10-18 14:39:59 +02:00
m_Rcon = nullptr ;
2019-10-29 17:32:29 +01:00
m_WorkQueue . SetName ( " IcingaDB " ) ;
2018-07-10 11:09:37 +02:00
2021-04-22 08:56:28 +02:00
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
2022-01-26 14:02:48 +01:00
/**
* Feature stats interface
*
* @ param status Key value pairs for feature stats
*/
void IcingaDB : : StatsFunc ( const Dictionary : : Ptr & status , const Array : : Ptr & perfdata )
{
DictionaryData nodes ;
for ( auto & icingadb : ConfigType : : GetObjectsByType < IcingaDB > ( ) ) {
auto historyBufferItems ( icingadb - > m_HistoryBulker . Size ( ) ) ;
nodes . emplace_back ( icingadb - > GetName ( ) , new Dictionary ( {
{ " history_buffer_items " , historyBufferItems }
} ) ) ;
perfdata - > Add ( new PerfdataValue ( " icingadb_ " + icingadb - > GetName ( ) + " _history_buffer_items " , historyBufferItems ) ) ;
}
status - > Set ( " icingadb " , new Dictionary ( std : : move ( nodes ) ) ) ;
}
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. " ) ) ;
}
}
2017-09-25 14:41:43 +02:00
/**
* Starts the component .
*/
2019-10-29 17:32:29 +01:00
void IcingaDB : : Start ( bool runtimeCreated )
2017-09-25 14:41:43 +02:00
{
2019-10-29 17:32:29 +01:00
ObjectImpl < IcingaDB > : : Start ( runtimeCreated ) ;
2017-09-25 14:41:43 +02:00
2021-09-21 12:56:20 +02:00
std : : call_once ( m_EnvironmentIdOnce , [ ] ( ) {
String path = Configuration : : DataDir + " /icingadb.env " ;
if ( Utility : : PathExists ( path ) ) {
m_EnvironmentId = Utility : : LoadJsonFile ( path ) ;
if ( m_EnvironmentId . GetLength ( ) ! = 2 * SHA_DIGEST_LENGTH ) {
throw std : : runtime_error ( " Wrong length of stored Icinga DB environment " ) ;
}
for ( unsigned char c : m_EnvironmentId ) {
if ( ! std : : isxdigit ( c ) ) {
throw std : : runtime_error ( " Stored Icinga DB environment is not a hex string " ) ;
}
}
} else {
std : : shared_ptr < X509 > cert = GetX509Certificate ( ApiListener : : GetDefaultCaPath ( ) ) ;
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 ( ) ) ) ;
}
m_EnvironmentId = BinaryToHex ( digest , n ) ;
Utility : : SaveJsonFile ( path , 0600 , m_EnvironmentId ) ;
}
m_EnvironmentId = m_EnvironmentId . ToLower ( ) ;
} ) ;
2021-03-19 15:31:19 +01:00
2019-10-29 17:32:29 +01:00
Log ( LogInformation , " IcingaDB " )
2018-05-15 14:43:01 +02:00
< < " ' " < < GetName ( ) < < " ' started. " ;
2017-09-25 14:41:43 +02:00
m_ConfigDumpInProgress = false ;
2018-10-18 14:39:59 +02:00
m_ConfigDumpDone = false ;
2019-06-28 11:56:15 +02:00
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 ( ) ) ;
2021-07-27 12:21:04 +02:00
2021-07-28 14:13:02 +02:00
for ( const Type : : Ptr & type : GetTypes ( ) ) {
auto ctype ( dynamic_cast < ConfigType * > ( type . get ( ) ) ) ;
if ( ! ctype )
continue ;
2021-07-28 14:28:21 +02:00
RedisConnection : : Ptr con = new RedisConnection ( GetHost ( ) , GetPort ( ) , GetPath ( ) , GetPassword ( ) , GetDbIndex ( ) ,
2021-07-28 14:13:02 +02:00
GetEnableTls ( ) , GetInsecureNoverify ( ) , GetCertPath ( ) , GetKeyPath ( ) , GetCaPath ( ) , GetCrlPath ( ) ,
GetTlsProtocolmin ( ) , GetCipherList ( ) , GetConnectTimeout ( ) , GetDebugInfo ( ) , m_Rcon ) ;
2021-07-28 14:28:21 +02:00
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 ) ;
2021-07-28 14:13:02 +02:00
}
2021-07-28 14:28:21 +02:00
m_PendingRcons = m_Rcons . size ( ) ;
m_Rcon - > SetConnectedCallback ( [ this ] ( boost : : asio : : yield_context & yc ) {
m_Rcon - > SetConnectedCallback ( nullptr ) ;
2021-07-27 12:21:04 +02:00
for ( auto & kv : m_Rcons ) {
kv . second - > Start ( ) ;
}
} ) ;
2021-01-19 10:56:54 +01:00
m_Rcon - > Start ( ) ;
2017-09-25 14:41:43 +02:00
m_StatsTimer = new Timer ( ) ;
2019-05-13 11:37:54 +02:00
m_StatsTimer - > SetInterval ( 1 ) ;
2019-06-28 11:56:15 +02:00
m_StatsTimer - > OnTimerExpired . connect ( [ this ] ( const Timer * const & ) { PublishStatsTimerHandler ( ) ; } ) ;
2017-09-25 14:41:43 +02:00
m_StatsTimer - > Start ( ) ;
2019-10-29 17:32:29 +01:00
m_WorkQueue . SetName ( " IcingaDB " ) ;
2017-10-02 09:59:11 +02:00
2019-12-17 16:41:34 +01:00
m_Rcon - > SuppressQueryKind ( Prio : : CheckResult ) ;
2021-07-26 16:39:18 +02:00
m_Rcon - > SuppressQueryKind ( Prio : : RuntimeStateSync ) ;
2022-01-31 12:38:02 +01:00
2022-03-01 15:28:03 +01:00
Ptr keepAlive ( this ) ;
m_HistoryThread = std : : async ( std : : launch : : async , [ this , keepAlive ] ( ) { ForwardHistoryEntries ( ) ; } ) ;
2017-09-25 14:41:43 +02:00
}
2019-10-29 17:32:29 +01:00
void IcingaDB : : ExceptionHandler ( boost : : exception_ptr exp )
2017-09-25 14:41:43 +02:00
{
2019-10-29 17:32:29 +01:00
Log ( LogCritical , " IcingaDB " , " Exception during redis query. Verify that Redis is operational. " ) ;
2017-09-25 14:41:43 +02:00
2019-10-29 17:32:29 +01: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
}
2021-01-19 10:56:54 +01:00
void IcingaDB : : OnConnectedHandler ( )
2017-09-25 14:41:43 +02:00
{
AssertOnWorkQueue ( ) ;
2018-10-18 14:39:59 +02:00
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 ( ) ;
2018-10-18 14:39:59 +02:00
m_ConfigDumpDone = true ;
2017-09-25 14:41:43 +02:00
m_ConfigDumpInProgress = false ;
}
2019-10-29 17:32:29 +01:00
void IcingaDB : : PublishStatsTimerHandler ( void )
2017-09-25 14:41:43 +02:00
{
2020-05-15 11:17:25 +02:00
PublishStats ( ) ;
2017-09-25 14:41:43 +02:00
}
2019-10-29 17:32:29 +01:00
void IcingaDB : : PublishStats ( )
2017-09-25 14:41:43 +02:00
{
2018-11-26 13:34:05 +01: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 ) ;
2019-12-05 10:32:15 +01:00
status - > Set ( " timestamp " , TimestampToMilliseconds ( Utility : : GetTime ( ) ) ) ;
2021-10-13 12:40:56 +02:00
status - > Set ( " icingadb_environment " , m_EnvironmentId ) ;
2017-09-25 14:41:43 +02:00
2021-05-03 17:13:06 +02:00
std : : vector < String > query { " XADD " , " icinga:stats " , " MAXLEN " , " 1 " , " * " } ;
2019-12-02 18:06:07 +01:00
{
ObjectLock statusLock ( status ) ;
for ( auto & kv : status ) {
2021-05-03 17:13:06 +02:00
query . emplace_back ( kv . first ) ;
query . emplace_back ( JsonEncode ( kv . second ) ) ;
2019-12-02 18:06:07 +01:00
}
}
2021-05-03 17:13:06 +02:00
m_Rcon - > FireAndForgetQuery ( std : : move ( query ) , Prio : : Heartbeat ) ;
2017-09-25 14:41:43 +02:00
}
2019-10-29 17:32:29 +01:00
void IcingaDB : : Stop ( bool runtimeRemoved )
2017-09-25 14:41:43 +02:00
{
2022-01-31 12:38:02 +01:00
Log ( LogInformation , " IcingaDB " )
< < " Flushing history data buffer to Redis. " ;
2022-03-01 15:28:03 +01:00
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. " ;
}
2022-01-31 12:38:02 +01:00
2019-10-29 17:32:29 +01:00
Log ( LogInformation , " IcingaDB " )
2018-05-15 14:43:01 +02:00
< < " ' " < < GetName ( ) < < " ' stopped. " ;
2017-09-25 14:41:43 +02:00
2019-10-29 17:32:29 +01: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. " ) ) ;
}
}
2019-10-29 17:32:29 +01:00
void IcingaDB : : AssertOnWorkQueue ( )
2017-09-25 14:41:43 +02:00
{
ASSERT ( m_WorkQueue . IsWorkerThread ( ) ) ;
}
2021-02-24 13:37:36 +01:00
void IcingaDB : : DumpedGlobals : : Reset ( )
{
std : : lock_guard < std : : mutex > l ( m_Mutex ) ;
m_Ids . clear ( ) ;
}
2021-10-14 12:00:59 +02:00
String IcingaDB : : GetEnvironmentId ( ) const {
return m_EnvironmentId ;
}
2021-02-24 13:37:36 +01:00
bool IcingaDB : : DumpedGlobals : : IsNew ( const String & id )
{
std : : lock_guard < std : : mutex > l ( m_Mutex ) ;
return m_Ids . emplace ( id ) . second ;
}