From: Gunnar Beutner Date: Sat, 3 May 2014 18:02:22 +0000 (+0200) Subject: Refactor the agent and cluster components. X-Git-Tag: v0.0.11~76 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=45270f1bb8df9dc188338e714299a7ac127c6e1b;p=icinga2 Refactor the agent and cluster components. Refs #6107 --- diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 527a938e9..737807c54 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -1,6 +1,4 @@ -add_subdirectory(agent) add_subdirectory(checker) -add_subdirectory(cluster) add_subdirectory(compat) add_subdirectory(db_ido_mysql) add_subdirectory(db_ido_pgsql) diff --git a/components/agent/CMakeLists.txt b/components/agent/CMakeLists.txt deleted file mode 100644 index 67396c1ec..000000000 --- a/components/agent/CMakeLists.txt +++ /dev/null @@ -1,39 +0,0 @@ -# Icinga 2 -# Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) -# -# 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 2 -# 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, write to the Free Software Foundation -# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -mkclass_target(agentlistener.ti agentlistener.th) - -mkembedconfig_target(agent-type.conf agent-type.cpp) - -add_library(agent SHARED - agentchecktask.cpp agentlistener.cpp agentlistener.th - agent-type.cpp -) - -target_link_libraries(agent ${Boost_LIBRARIES} base config icinga remote) - -set_target_properties ( - agent PROPERTIES - INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/icinga2 - FOLDER Components -) - -install(TARGETS agent RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/icinga2) - -#install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/icinga2/agent\")") -install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/icinga2/agent/inventory\")") - diff --git a/components/agent/agent-type.conf b/components/agent/agent-type.conf deleted file mode 100644 index 58a88dc8a..000000000 --- a/components/agent/agent-type.conf +++ /dev/null @@ -1,39 +0,0 @@ -/****************************************************************************** - * Icinga 2 * - * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * - * * - * 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 2 * - * 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, write to the Free Software Foundation * - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * - ******************************************************************************/ - -%type AgentListener { - %attribute %string "cert_path", - %require "cert_path", - - %attribute %string "key_path", - %require "key_path", - - %attribute %string "ca_path", - %require "ca_path", - - %attribute %string "crl_path", - - %attribute %string "bind_host", - %attribute %string "bind_port", - - %attribute %string "upstream_name", - %attribute %string "upstream_host", - %attribute %string "upstream_port", - %attribute %number "upstream_interval" -} diff --git a/components/agent/agentchecktask.cpp b/components/agent/agentchecktask.cpp deleted file mode 100644 index 674ea4237..000000000 --- a/components/agent/agentchecktask.cpp +++ /dev/null @@ -1,133 +0,0 @@ -/****************************************************************************** - * Icinga 2 * - * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * - * * - * 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 2 * - * 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, write to the Free Software Foundation * - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * - ******************************************************************************/ - -#include "agent/agentchecktask.h" -#include "agent/agentlistener.h" -#include "icinga/service.h" -#include "icinga/checkcommand.h" -#include "icinga/macroprocessor.h" -#include "icinga/icingaapplication.h" -#include "base/application.h" -#include "base/objectlock.h" -#include "base/convert.h" -#include "base/utility.h" -#include "base/initialize.h" -#include "base/scriptfunction.h" -#include "base/dynamictype.h" - -using namespace icinga; - -boost::mutex l_Mutex; -std::map l_PendingChecks; -Timer::Ptr l_AgentTimer; - -INITIALIZE_ONCE(&AgentCheckTask::StaticInitialize); -REGISTER_SCRIPTFUNCTION(AgentCheck, &AgentCheckTask::ScriptFunc); - -void AgentCheckTask::StaticInitialize(void) -{ - l_AgentTimer = make_shared(); - l_AgentTimer->OnTimerExpired.connect(boost::bind(&AgentCheckTask::AgentTimerHandler)); - l_AgentTimer->SetInterval(60); - l_AgentTimer->Start(); -} - -void AgentCheckTask::AgentTimerHandler(void) -{ - boost::mutex::scoped_lock lock(l_Mutex); - - std::map newmap; - std::pair kv; - - double now = Utility::GetTime(); - - BOOST_FOREACH(kv, l_PendingChecks) { - if (kv.second < now - 60 && kv.first->IsCheckPending() && !SendResult(kv.first, false)) { - CheckResult::Ptr cr = make_shared(); - cr->SetOutput("Agent isn't responding."); - cr->SetState(ServiceCritical); - kv.first->ProcessCheckResult(cr); - } else { - newmap.insert(kv); - } - } - - l_PendingChecks.swap(newmap); -} - -bool AgentCheckTask::SendResult(const Checkable::Ptr& checkable, bool enqueue) -{ - Host::Ptr host; - Service::Ptr service; - tie(host, service) = GetHostService(checkable); - - MacroProcessor::ResolverList resolvers; - if (service) - resolvers.push_back(std::make_pair("service", service)); - resolvers.push_back(std::make_pair("host", host)); - resolvers.push_back(std::make_pair("command", checkable->GetCheckCommand())); - resolvers.push_back(std::make_pair("icinga", IcingaApplication::GetInstance())); - - String agent_identity = MacroProcessor::ResolveMacros("$agent_identity$", resolvers, checkable->GetLastCheckResult()); - String agent_host = MacroProcessor::ResolveMacros("$agent_host$", resolvers, checkable->GetLastCheckResult()); - String agent_service = MacroProcessor::ResolveMacros("$agent_service$", resolvers, checkable->GetLastCheckResult()); - - if (agent_identity.IsEmpty() || agent_host.IsEmpty()) { - Log(LogWarning, "agent", "'agent_name' and 'agent_host' must be set for agent checks."); - return false; - } - - String agent_peer_host = MacroProcessor::ResolveMacros("$agent_peer_host$", resolvers, checkable->GetLastCheckResult()); - String agent_peer_port = MacroProcessor::ResolveMacros("$agent_peer_port$", resolvers, checkable->GetLastCheckResult()); - - double now = Utility::GetTime(); - - BOOST_FOREACH(const AgentListener::Ptr& al, DynamicType::GetObjects()) { - double seen = al->GetAgentSeen(agent_identity); - - if (seen < now - 300) - continue; - - CheckResult::Ptr cr = al->GetCheckResult(agent_identity, agent_host, agent_service); - - if (cr) { - checkable->ProcessCheckResult(cr); - return true; - } - } - - if (enqueue) { - { - boost::mutex::scoped_lock lock(l_Mutex); - l_PendingChecks[checkable] = now; - } - - BOOST_FOREACH(const AgentListener::Ptr& al, DynamicType::GetObjects()) { - if (!agent_peer_host.IsEmpty() && !agent_peer_port.IsEmpty()) - al->AddConnection(agent_peer_host, agent_peer_port); - } - } - - return false; -} - -void AgentCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) -{ - SendResult(checkable, true); -} diff --git a/components/agent/agentlistener.cpp b/components/agent/agentlistener.cpp deleted file mode 100644 index dfc99198c..000000000 --- a/components/agent/agentlistener.cpp +++ /dev/null @@ -1,323 +0,0 @@ -/****************************************************************************** - * Icinga 2 * - * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * - * * - * 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 2 * - * 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, write to the Free Software Foundation * - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * - ******************************************************************************/ - -#include "agent/agentlistener.h" -#include "remote/jsonrpc.h" -#include "icinga/icingaapplication.h" -#include "base/netstring.h" -#include "base/dynamictype.h" -#include "base/logger_fwd.h" -#include "base/objectlock.h" -#include "base/networkstream.h" -#include "base/application.h" -#include "base/context.h" -#include - -using namespace icinga; - -REGISTER_TYPE(AgentListener); - -/** - * Starts the component. - */ -void AgentListener::Start(void) -{ - DynamicObject::Start(); - - m_Results = make_shared(); - - /* set up SSL context */ - shared_ptr cert = GetX509Certificate(GetCertPath()); - SetIdentity(GetCertificateCN(cert)); - Log(LogInformation, "agent", "My identity: " + GetIdentity()); - - m_SSLContext = MakeSSLContext(GetCertPath(), GetKeyPath(), GetCaPath()); - - if (!GetCrlPath().IsEmpty()) - AddCRLToSSLContext(m_SSLContext, GetCrlPath()); - - /* create the primary JSON-RPC listener */ - if (!GetBindPort().IsEmpty()) - AddListener(GetBindPort()); - - m_AgentTimer = make_shared(); - m_AgentTimer->OnTimerExpired.connect(boost::bind(&AgentListener::AgentTimerHandler, this)); - m_AgentTimer->SetInterval(GetUpstreamInterval()); - m_AgentTimer->Start(); - m_AgentTimer->Reschedule(0); -} - -shared_ptr AgentListener::GetSSLContext(void) const -{ - return m_SSLContext; -} - -String AgentListener::GetInventoryDir(void) -{ - return Application::GetLocalStateDir() + "/lib/icinga2/agent/inventory/"; -} - -/** - * Creates a new JSON-RPC listener on the specified port. - * - * @param service The port to listen on. - */ -void AgentListener::AddListener(const String& service) -{ - ObjectLock olock(this); - - shared_ptr sslContext = m_SSLContext; - - if (!sslContext) - BOOST_THROW_EXCEPTION(std::logic_error("SSL context is required for AddListener()")); - - std::ostringstream s; - s << "Adding new listener: port " << service; - Log(LogInformation, "agent", s.str()); - - TcpSocket::Ptr server = make_shared(); - server->Bind(service, AF_INET6); - - boost::thread thread(boost::bind(&AgentListener::ListenerThreadProc, this, server)); - thread.detach(); - - m_Servers.insert(server); -} - -void AgentListener::ListenerThreadProc(const Socket::Ptr& server) -{ - Utility::SetThreadName("Cluster Listener"); - - server->Listen(); - - for (;;) { - Socket::Ptr client = server->Accept(); - - Utility::QueueAsyncCallback(boost::bind(&AgentListener::NewClientHandler, this, client, TlsRoleServer)); - } -} - -/** - * Creates a new JSON-RPC client and connects to the specified host and port. - * - * @param node The remote host. - * @param service The remote port. - */ -void AgentListener::AddConnection(const String& node, const String& service) { - { - ObjectLock olock(this); - - shared_ptr sslContext = m_SSLContext; - - if (!sslContext) - BOOST_THROW_EXCEPTION(std::logic_error("SSL context is required for AddConnection()")); - } - - TcpSocket::Ptr client = make_shared(); - - client->Connect(node, service); - Utility::QueueAsyncCallback(boost::bind(&AgentListener::NewClientHandler, this, client, TlsRoleClient)); -} - -/** - * Processes a new client connection. - * - * @param client The new client. - */ -void AgentListener::NewClientHandler(const Socket::Ptr& client, TlsRole role) -{ - CONTEXT("Handling new agent client connection"); - - TlsStream::Ptr tlsStream; - - { - ObjectLock olock(this); - tlsStream = make_shared(client, role, m_SSLContext); - } - - tlsStream->Handshake(); - - shared_ptr cert = tlsStream->GetPeerCertificate(); - String identity = GetCertificateCN(cert); - - Log(LogInformation, "agent", "New client connection for identity '" + identity + "'"); - - if (identity != GetUpstreamName()) { - Dictionary::Ptr request = make_shared(); - request->Set("method", "get_crs"); - JsonRpc::SendMessage(tlsStream, request); - } - - try { - Dictionary::Ptr message = JsonRpc::ReadMessage(tlsStream); - MessageHandler(tlsStream, identity, message); - } catch (const std::exception& ex) { - Log(LogWarning, "agent", "Error while reading JSON-RPC message for agent '" + identity + "': " + DiagnosticInformation(ex)); - } - - tlsStream->Close(); -} - -void AgentListener::MessageHandler(const TlsStream::Ptr& sender, const String& identity, const Dictionary::Ptr& message) -{ - CONTEXT("Processing agent message of type '" + message->Get("method") + "'"); - - String method = message->Get("method"); - - if (identity == GetUpstreamName()) { - if (method == "get_crs") { - Dictionary::Ptr hosts = make_shared(); - - BOOST_FOREACH(const Host::Ptr& host, DynamicType::GetObjects()) { - Dictionary::Ptr hostInfo = make_shared(); - - hostInfo->Set("cr", Serialize(host->GetLastCheckResult())); - - Dictionary::Ptr services = make_shared(); - - BOOST_FOREACH(const Service::Ptr& service, host->GetServices()) { - Dictionary::Ptr serviceInfo = make_shared(); - serviceInfo->Set("cr", Serialize(service->GetLastCheckResult())); - services->Set(service->GetShortName(), serviceInfo); - } - - hostInfo->Set("services", services); - - hosts->Set(host->GetName(), hostInfo); - } - - Dictionary::Ptr params = make_shared(); - params->Set("hosts", hosts); - - Dictionary::Ptr request = make_shared(); - request->Set("method", "push_crs"); - request->Set("params", params); - - JsonRpc::SendMessage(sender, request); - } - } - - if (method == "push_crs") { - Value paramsv = message->Get("params"); - - if (paramsv.IsEmpty() || !paramsv.IsObjectType()) - return; - - Dictionary::Ptr params = paramsv; - - params->Set("seen", Utility::GetTime()); - - Dictionary::Ptr inventoryDescr = make_shared(); - inventoryDescr->Set("identity", identity); - inventoryDescr->Set("params", params); - - String inventoryFile = GetInventoryDir() + SHA256(identity); - String inventoryTempFile = inventoryFile + ".tmp"; - - std::ofstream fp(inventoryTempFile.CStr(), std::ofstream::out | std::ostream::trunc); - fp << JsonSerialize(inventoryDescr); - fp.close(); - -#ifdef _WIN32 - _unlink(inventoryFile.CStr()); -#endif /* _WIN32 */ - - if (rename(inventoryTempFile.CStr(), inventoryFile.CStr()) < 0) { - BOOST_THROW_EXCEPTION(posix_error() - << boost::errinfo_api_function("rename") - << boost::errinfo_errno(errno) - << boost::errinfo_file_name(inventoryTempFile)); - } - - m_Results->Set(identity, params); - } -} - -double AgentListener::GetAgentSeen(const String& agentIdentity) -{ - Dictionary::Ptr agentparams = m_Results->Get(agentIdentity); - - if (!agentparams) - return 0; - - return agentparams->Get("seen"); -} - -CheckResult::Ptr AgentListener::GetCheckResult(const String& agentIdentity, const String& hostName, const String& serviceName) -{ - Dictionary::Ptr agentparams = m_Results->Get(agentIdentity); - - if (!agentparams) - return CheckResult::Ptr(); - - Value hostsv = agentparams->Get("hosts"); - - if (hostsv.IsEmpty() || !hostsv.IsObjectType()) - return CheckResult::Ptr(); - - Dictionary::Ptr hosts = hostsv; - - Value hostv = hosts->Get(hostName); - - if (hostv.IsEmpty() || !hostv.IsObjectType()) - return CheckResult::Ptr(); - - Dictionary::Ptr host = hostv; - - if (serviceName.IsEmpty()) { - Value hostcrv = Deserialize(host->Get("cr")); - - if (hostcrv.IsEmpty() || !hostcrv.IsObjectType()) - return CheckResult::Ptr(); - - return hostcrv; - } else { - Value servicesv = host->Get("services"); - - if (servicesv.IsEmpty() || !servicesv.IsObjectType()) - return CheckResult::Ptr(); - - Dictionary::Ptr services = servicesv; - - Value servicev = services->Get(serviceName); - - if (servicev.IsEmpty() || !servicev.IsObjectType()) - return CheckResult::Ptr(); - - Dictionary::Ptr service = servicev; - - Value servicecrv = Deserialize(service->Get("cr")); - - if (servicecrv.IsEmpty() || !servicecrv.IsObjectType()) - return CheckResult::Ptr(); - - return servicecrv; - } -} - -void AgentListener::AgentTimerHandler(void) -{ - String host = GetUpstreamHost(); - String port = GetUpstreamPort(); - - if (host.IsEmpty() || port.IsEmpty()) - return; - - AddConnection(host, port); -} diff --git a/components/agent/agentlistener.ti b/components/agent/agentlistener.ti deleted file mode 100644 index e42c94297..000000000 --- a/components/agent/agentlistener.ti +++ /dev/null @@ -1,24 +0,0 @@ -#include "base/dynamicobject.h" -#include "base/application.h" - -namespace icinga -{ - -class AgentListener : DynamicObject -{ - [config] String cert_path; - [config] String key_path; - [config] String ca_path; - [config] String crl_path; - [config] String bind_host; - [config] String bind_port; - [config] String upstream_host; - [config] String upstream_port; - [config] String upstream_name; - [config] int upstream_interval { - default {{{ return 60; }}} - }; - String identity; -}; - -} diff --git a/components/checker/CMakeLists.txt b/components/checker/CMakeLists.txt index 7fe18cf9b..33761dc98 100644 --- a/components/checker/CMakeLists.txt +++ b/components/checker/CMakeLists.txt @@ -21,7 +21,7 @@ mkembedconfig_target(checker-type.conf checker-type.cpp) add_library(checker SHARED checkercomponent.cpp checkercomponent.th checker-type.cpp) -target_link_libraries(checker ${Boost_LIBRARIES} base config icinga) +target_link_libraries(checker ${Boost_LIBRARIES} base config icinga remote) set_target_properties ( checker PROPERTIES diff --git a/components/checker/checkercomponent.cpp b/components/checker/checkercomponent.cpp index 808bf6d62..2f53142dc 100644 --- a/components/checker/checkercomponent.cpp +++ b/components/checker/checkercomponent.cpp @@ -20,6 +20,7 @@ #include "checker/checkercomponent.h" #include "icinga/icingaapplication.h" #include "icinga/cib.h" +#include "remote/apilistener.h" #include "base/dynamictype.h" #include "base/objectlock.h" #include "base/utility.h" @@ -63,7 +64,6 @@ void CheckerComponent::OnConfigLoaded(void) { DynamicObject::OnStarted.connect(bind(&CheckerComponent::ObjectHandler, this, _1)); DynamicObject::OnStopped.connect(bind(&CheckerComponent::ObjectHandler, this, _1)); - DynamicObject::OnAuthorityChanged.connect(bind(&CheckerComponent::ObjectHandler, this, _1)); Checkable::OnNextCheckChanged.connect(bind(&CheckerComponent::NextCheckChangedHandler, this, _1)); } @@ -117,12 +117,6 @@ void CheckerComponent::CheckThreadProc(void) CheckTimeView::iterator it = idx.begin(); Checkable::Ptr checkable = *it; - if (!checkable->HasAuthority("checker")) { - m_IdleCheckables.erase(checkable); - - continue; - } - double wait = checkable->GetNextCheck() - Utility::GetTime(); if (wait > 0) { @@ -227,7 +221,7 @@ void CheckerComponent::ExecuteCheckHelper(const Checkable::Ptr& checkable) if (it != m_PendingCheckables.end()) { m_PendingCheckables.erase(it); - if (checkable->IsActive() && checkable->HasAuthority("checker")) + if (checkable->IsActive()) m_IdleCheckables.insert(checkable); m_CV.notify_all(); @@ -257,10 +251,13 @@ void CheckerComponent::ObjectHandler(const DynamicObject::Ptr& object) Checkable::Ptr checkable = static_pointer_cast(object); + Zone::Ptr zone = Zone::GetByName(checkable->GetZone()); + bool same_zone = (!zone || Zone::GetLocalZone() == zone); + { boost::mutex::scoped_lock lock(m_Mutex); - if (object->IsActive() && object->HasAuthority("checker")) { + if (object->IsActive() && same_zone) { if (m_PendingCheckables.find(checkable) != m_PendingCheckables.end()) return; diff --git a/components/cluster/CMakeLists.txt b/components/cluster/CMakeLists.txt deleted file mode 100644 index e592754a0..000000000 --- a/components/cluster/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -# Icinga 2 -# Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) -# -# 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 2 -# 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, write to the Free Software Foundation -# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -mkclass_target(clusterlistener.ti clusterlistener.th) - -mkembedconfig_target(cluster-type.conf cluster-type.cpp) - -add_library(cluster SHARED - clusterchecktask.cpp clusterlink.cpp clusterlistener.cpp clusterlistener.th - cluster-type.cpp -) - -target_link_libraries(cluster ${Boost_LIBRARIES} base config icinga remote) - -set_target_properties ( - cluster PROPERTIES - INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/icinga2 - FOLDER Components -) - -install(TARGETS cluster RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/icinga2) - -#install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/icinga2/cluster\")") -install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/icinga2/cluster/config\")") -install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/icinga2/cluster/log\")") - diff --git a/components/cluster/cluster-type.conf b/components/cluster/cluster-type.conf deleted file mode 100644 index eea76f49c..000000000 --- a/components/cluster/cluster-type.conf +++ /dev/null @@ -1,38 +0,0 @@ -/****************************************************************************** - * Icinga 2 * - * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * - * * - * 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 2 * - * 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, write to the Free Software Foundation * - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * - ******************************************************************************/ - -%type ClusterListener { - %attribute %string "cert_path", - %require "cert_path", - - %attribute %string "key_path", - %require "key_path", - - %attribute %string "ca_path", - %require "ca_path", - - %attribute %string "crl_path", - - %attribute %string "bind_host", - %attribute %string "bind_port", - - %attribute %array "peers" { - %attribute %name(Endpoint) "*" - } -} diff --git a/components/cluster/clusterlistener.cpp b/components/cluster/clusterlistener.cpp deleted file mode 100644 index 16501ff9b..000000000 --- a/components/cluster/clusterlistener.cpp +++ /dev/null @@ -1,1872 +0,0 @@ -/****************************************************************************** - * Icinga 2 * - * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * - * * - * 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 2 * - * 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, write to the Free Software Foundation * - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * - ******************************************************************************/ - -#include "cluster/clusterlistener.h" -#include "remote/endpoint.h" -#include "icinga/cib.h" -#include "icinga/domain.h" -#include "icinga/icingaapplication.h" -#include "base/netstring.h" -#include "base/dynamictype.h" -#include "base/logger_fwd.h" -#include "base/objectlock.h" -#include "base/networkstream.h" -#include "base/zlibstream.h" -#include "base/application.h" -#include "base/convert.h" -#include "base/context.h" -#include "base/statsfunction.h" -#include - -using namespace icinga; - -REGISTER_TYPE(ClusterListener); - -REGISTER_STATSFUNCTION(ClusterListenerStats, &ClusterListener::StatsFunc); - -Value ClusterListener::StatsFunc(Dictionary::Ptr& status, Dictionary::Ptr& perfdata) -{ - Dictionary::Ptr nodes = make_shared(); - std::pair stats; - - BOOST_FOREACH(const ClusterListener::Ptr& cluster_listener, DynamicType::GetObjects()) { - stats = cluster_listener->GetClusterStatus(); - nodes->Set(cluster_listener->GetName(), stats.first); - - String perfdata_prefix = "clusterlistener_" + cluster_listener->GetName() + "_"; - BOOST_FOREACH(Dictionary::Pair const& kv, stats.second) { - perfdata->Set(perfdata_prefix + kv.first, kv.second); - } - } - - status->Set("clusterlistener", nodes); - - return 0; -} - -/** - * Starts the component. - */ -void ClusterListener::Start(void) -{ - DynamicObject::Start(); - - { - ObjectLock olock(this); - RotateLogFile(); - OpenLogFile(); - } - - /* set up SSL context */ - shared_ptr cert = GetX509Certificate(GetCertPath()); - SetIdentity(GetCertificateCN(cert)); - Log(LogInformation, "cluster", "My identity: " + GetIdentity()); - - Endpoint::Ptr self = Endpoint::GetByName(GetIdentity()); - - if (!self) - BOOST_THROW_EXCEPTION(std::invalid_argument("No configuration available for the local endpoint.")); - - m_SSLContext = MakeSSLContext(GetCertPath(), GetKeyPath(), GetCaPath()); - - if (!GetCrlPath().IsEmpty()) - AddCRLToSSLContext(m_SSLContext, GetCrlPath()); - - /* create the primary JSON-RPC listener */ - if (!GetBindPort().IsEmpty()) - AddListener(GetBindPort()); - - m_ClusterTimer = make_shared(); - m_ClusterTimer->OnTimerExpired.connect(boost::bind(&ClusterListener::ClusterTimerHandler, this)); - m_ClusterTimer->SetInterval(5); - m_ClusterTimer->Start(); - - m_MessageQueue.SetExceptionCallback(&ClusterListener::MessageExceptionHandler); - - Checkable::OnNewCheckResult.connect(boost::bind(&ClusterListener::CheckResultHandler, this, _1, _2, _3)); - Checkable::OnNextCheckChanged.connect(boost::bind(&ClusterListener::NextCheckChangedHandler, this, _1, _2, _3)); - Notification::OnNextNotificationChanged.connect(boost::bind(&ClusterListener::NextNotificationChangedHandler, this, _1, _2, _3)); - Checkable::OnForceNextCheckChanged.connect(boost::bind(&ClusterListener::ForceNextCheckChangedHandler, this, _1, _2, _3)); - Checkable::OnForceNextNotificationChanged.connect(boost::bind(&ClusterListener::ForceNextNotificationChangedHandler, this, _1, _2, _3)); - Checkable::OnEnableActiveChecksChanged.connect(boost::bind(&ClusterListener::EnableActiveChecksChangedHandler, this, _1, _2, _3)); - Checkable::OnEnablePassiveChecksChanged.connect(boost::bind(&ClusterListener::EnablePassiveChecksChangedHandler, this, _1, _2, _3)); - Checkable::OnEnableNotificationsChanged.connect(boost::bind(&ClusterListener::EnableNotificationsChangedHandler, this, _1, _2, _3)); - Checkable::OnEnableFlappingChanged.connect(boost::bind(&ClusterListener::EnableFlappingChangedHandler, this, _1, _2, _3)); - Checkable::OnCommentAdded.connect(boost::bind(&ClusterListener::CommentAddedHandler, this, _1, _2, _3)); - Checkable::OnCommentRemoved.connect(boost::bind(&ClusterListener::CommentRemovedHandler, this, _1, _2, _3)); - Checkable::OnDowntimeAdded.connect(boost::bind(&ClusterListener::DowntimeAddedHandler, this, _1, _2, _3)); - Checkable::OnDowntimeRemoved.connect(boost::bind(&ClusterListener::DowntimeRemovedHandler, this, _1, _2, _3)); - Checkable::OnAcknowledgementSet.connect(boost::bind(&ClusterListener::AcknowledgementSetHandler, this, _1, _2, _3, _4, _5, _6)); - Checkable::OnAcknowledgementCleared.connect(boost::bind(&ClusterListener::AcknowledgementClearedHandler, this, _1, _2)); - - Endpoint::OnMessageReceived.connect(boost::bind(&ClusterListener::AsyncMessageHandler, this, _1, _2)); - - BOOST_FOREACH(const DynamicType::Ptr& type, DynamicType::GetTypes()) { - BOOST_FOREACH(const DynamicObject::Ptr& object, type->GetObjects()) { - BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects()) { - int privs = 0; - - Array::Ptr domains = object->GetDomains(); - - if (domains) { - ObjectLock olock(domains); - BOOST_FOREACH(const String& domain, domains) { - Domain::Ptr domainObj = Domain::GetByName(domain); - - if (!domainObj) - BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid domain: " + domain)); - - privs |= domainObj->GetPrivileges(endpoint->GetName()); - } - } else { - privs = INT_MAX; - } - - Log(LogDebug, "cluster", "Privileges for object '" + object->GetName() + "' of type '" + object->GetType()->GetName() + "' for instance '" + endpoint->GetName() + "' are '" + Convert::ToString(privs) + "'"); - object->SetPrivileges(endpoint->GetName(), privs); - } - } - } -} - -/** - * Stops the component. - */ -void ClusterListener::Stop(void) -{ - ObjectLock olock(this); - CloseLogFile(); - RotateLogFile(); -} - -shared_ptr ClusterListener::GetSSLContext(void) const -{ - return m_SSLContext; -} - -/** - * Creates a new JSON-RPC listener on the specified port. - * - * @param service The port to listen on. - */ -void ClusterListener::AddListener(const String& service) -{ - ObjectLock olock(this); - - shared_ptr sslContext = m_SSLContext; - - if (!sslContext) - BOOST_THROW_EXCEPTION(std::logic_error("SSL context is required for AddListener()")); - - std::ostringstream s; - s << "Adding new listener: port " << service; - Log(LogInformation, "cluster", s.str()); - - TcpSocket::Ptr server = make_shared(); - server->Bind(service, AF_INET6); - - boost::thread thread(boost::bind(&ClusterListener::ListenerThreadProc, this, server)); - thread.detach(); - - m_Servers.insert(server); -} - -void ClusterListener::ListenerThreadProc(const Socket::Ptr& server) -{ - Utility::SetThreadName("Cluster Listener"); - - server->Listen(); - - for (;;) { - Socket::Ptr client = server->Accept(); - - Utility::QueueAsyncCallback(boost::bind(&ClusterListener::NewClientHandler, this, client, TlsRoleServer)); - } -} - -/** - * Creates a new JSON-RPC client and connects to the specified host and port. - * - * @param node The remote host. - * @param service The remote port. - */ -void ClusterListener::AddConnection(const String& node, const String& service) { - { - ObjectLock olock(this); - - shared_ptr sslContext = m_SSLContext; - - if (!sslContext) - BOOST_THROW_EXCEPTION(std::logic_error("SSL context is required for AddConnection()")); - } - - TcpSocket::Ptr client = make_shared(); - - client->Connect(node, service); - Utility::QueueAsyncCallback(boost::bind(&ClusterListener::NewClientHandler, this, client, TlsRoleClient)); -} - -void ClusterListener::AsyncRelayMessage(const Endpoint::Ptr& source, const Endpoint::Ptr& destination, const Dictionary::Ptr& message, bool persistent) -{ - m_RelayQueue.Enqueue(boost::bind(&ClusterListener::RelayMessage, this, source, destination, message, persistent)); -} - -void ClusterListener::PersistMessage(const Endpoint::Ptr& source, const Dictionary::Ptr& message) -{ - double ts = message->Get("ts"); - - ASSERT(ts != 0); - - Dictionary::Ptr pmessage = make_shared(); - pmessage->Set("timestamp", ts); - - if (source) - pmessage->Set("source", source->GetName()); - - pmessage->Set("message", JsonSerialize(message)); - pmessage->Set("security", message->Get("security")); - - ObjectLock olock(this); - if (m_LogFile) { - NetString::WriteStringToStream(m_LogFile, JsonSerialize(pmessage)); - m_LogMessageCount++; - SetLogMessageTimestamp(ts); - - if (m_LogMessageCount > 50000) { - CloseLogFile(); - RotateLogFile(); - OpenLogFile(); - } - } -} - -void ClusterListener::RelayMessage(const Endpoint::Ptr& source, const Endpoint::Ptr& destination, const Dictionary::Ptr& message, bool persistent) -{ - double ts = Utility::GetTime(); - message->Set("ts", ts); - - if (persistent) - m_LogQueue.Enqueue(boost::bind(&ClusterListener::PersistMessage, this, source, message)); - - Dictionary::Ptr security = message->Get("security"); - DynamicObject::Ptr secobj; - int privs = 0; - - if (security) { - String type = security->Get("type"); - DynamicType::Ptr dtype = DynamicType::GetByName(type); - - if (!dtype) { - Log(LogWarning, "cluster", "Invalid type in security attribute: " + type); - return; - } - - String name = security->Get("name"); - secobj = dtype->GetObject(name); - - if (!secobj) { - Log(LogWarning, "cluster", "Invalid object name in security attribute: " + name + " (of type '" + type + "')"); - return; - } - - privs = security->Get("privs"); - } - - double now = Utility::GetTime(); - - BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects()) { - if (!endpoint->IsConnected()) - continue; - - if (destination && endpoint != destination) - continue; - - if (!destination && endpoint->GetBlockedUntil() > now) - continue; - - if (endpoint == source) - continue; - - if (endpoint->GetName() == GetIdentity()) - continue; - - if (secobj && !secobj->HasPrivileges(endpoint->GetName(), privs)) { - Log(LogDebug, "cluster", "Not sending message to endpoint '" + endpoint->GetName() + "': Insufficient privileges."); - continue; - } - - { - ObjectLock olock(endpoint); - - if (!endpoint->GetSyncing()) - endpoint->SendMessage(message); - } - } -} - -String ClusterListener::GetClusterDir(void) const -{ - return Application::GetLocalStateDir() + "/lib/icinga2/cluster/"; -} - -void ClusterListener::OpenLogFile(void) -{ - ASSERT(OwnsLock()); - - String path = GetClusterDir() + "log/current"; - - std::fstream *fp = new std::fstream(path.CStr(), std::fstream::out | std::ofstream::app); - - if (!fp->good()) { - Log(LogWarning, "cluster", "Could not open spool file: " + path); - return; - } - - StdioStream::Ptr logStream = make_shared(fp, true); -#ifdef HAVE_BIOZLIB - m_LogFile = make_shared(logStream); -#else /* HAVE_BIOZLIB */ - m_LogFile = logStream; -#endif /* HAVE_BIOZLIB */ - m_LogMessageCount = 0; - SetLogMessageTimestamp(0); -} - -void ClusterListener::CloseLogFile(void) -{ - ASSERT(OwnsLock()); - - if (!m_LogFile) - return; - - m_LogFile->Close(); - m_LogFile.reset(); - -} - -void ClusterListener::RotateLogFile(void) -{ - ASSERT(OwnsLock()); - - double ts = GetLogMessageTimestamp(); - - if (ts == 0) - ts = Utility::GetTime(); - - String oldpath = GetClusterDir() + "log/current"; - String newpath = GetClusterDir() + "log/" + Convert::ToString(static_cast(ts) + 1); - (void) rename(oldpath.CStr(), newpath.CStr()); -} - -void ClusterListener::LogGlobHandler(std::vector& files, const String& file) -{ - String name = Utility::BaseName(file); - - int ts; - - try { - ts = Convert::ToLong(name); - } catch (const std::exception&) { - return; - } - - files.push_back(ts); -} - -void ClusterListener::ReplayLog(const Endpoint::Ptr& endpoint, const Stream::Ptr& stream) -{ - CONTEXT("Replaying log for Endpoint '" + endpoint->GetName() + "'"); - - int count = -1; - double peer_ts = endpoint->GetLocalLogPosition(); - bool last_sync = false; - - ASSERT(!OwnsLock()); - - for (;;) { - ObjectLock olock(this); - - CloseLogFile(); - RotateLogFile(); - - if (count == -1 || count > 50000) { - OpenLogFile(); - olock.Unlock(); - } else { - last_sync = true; - } - - count = 0; - - std::vector files; - Utility::Glob(GetClusterDir() + "log/*", boost::bind(&ClusterListener::LogGlobHandler, boost::ref(files), _1), GlobFile); - std::sort(files.begin(), files.end()); - - BOOST_FOREACH(int ts, files) { - String path = GetClusterDir() + "log/" + Convert::ToString(ts); - - if (ts < peer_ts) - continue; - - Log(LogInformation, "cluster", "Replaying log: " + path); - - std::fstream *fp = new std::fstream(path.CStr(), std::fstream::in); - StdioStream::Ptr logStream = make_shared(fp, true); -#ifdef HAVE_BIOZLIB - ZlibStream::Ptr lstream = make_shared(logStream); -#else /* HAVE_BIOZLIB */ - Stream::Ptr lstream = logStream; -#endif /* HAVE_BIOZLIB */ - - String message; - while (true) { - Dictionary::Ptr pmessage; - - try { - if (!NetString::ReadStringFromStream(lstream, &message)) - break; - - pmessage = JsonDeserialize(message); - } catch (std::exception&) { - Log(LogWarning, "cluster", "Unexpected end-of-file for cluster log: " + path); - - /* Log files may be incomplete or corrupted. This is perfectly OK. */ - break; - } - - if (pmessage->Get("timestamp") < peer_ts) - continue; - - if (pmessage->Get("source") == endpoint->GetName()) - continue; - - Dictionary::Ptr security = pmessage->Get("security"); - DynamicObject::Ptr secobj; - int privs; - - if (security) { - String type = security->Get("type"); - DynamicType::Ptr dtype = DynamicType::GetByName(type); - - if (!dtype) { - Log(LogDebug, "cluster", "Invalid type in security attribute: " + type); - continue; - } - - String name = security->Get("name"); - secobj = dtype->GetObject(name); - - if (!secobj) { - Log(LogDebug, "cluster", "Invalid object name in security attribute: " + name + " (of type '" + type + "')"); - continue; - } - - privs = security->Get("privs"); - } - - if (secobj && !secobj->HasPrivileges(endpoint->GetName(), privs)) { - Log(LogDebug, "cluster", "Not replaying message to endpoint '" + endpoint->GetName() + "': Insufficient privileges."); - continue; - } - - NetString::WriteStringToStream(stream, pmessage->Get("message")); - count++; - - peer_ts = pmessage->Get("timestamp"); - } - - lstream->Close(); - } - - Log(LogInformation, "cluster", "Replayed " + Convert::ToString(count) + " messages."); - - if (last_sync) { - { - ObjectLock olock2(endpoint); - endpoint->SetSyncing(false); - } - - OpenLogFile(); - - break; - } - } -} - -void ClusterListener::ConfigGlobHandler(const Dictionary::Ptr& config, const String& file, bool basename) -{ - CONTEXT("Creating config update for file '" + file + "'"); - - Dictionary::Ptr elem = make_shared(); - - std::ifstream fp(file.CStr()); - if (!fp) - return; - - String content((std::istreambuf_iterator(fp)), std::istreambuf_iterator()); - elem->Set("content", content); - - config->Set(basename ? Utility::BaseName(file) : file, elem); -} - -/** - * Processes a new client connection. - * - * @param client The new client. - */ -void ClusterListener::NewClientHandler(const Socket::Ptr& client, TlsRole role) -{ - CONTEXT("Handling new cluster client connection"); - - TlsStream::Ptr tlsStream = make_shared(client, role, m_SSLContext); - tlsStream->Handshake(); - - shared_ptr cert = tlsStream->GetPeerCertificate(); - String identity = GetCertificateCN(cert); - - Endpoint::Ptr endpoint = Endpoint::GetByName(identity); - - if (!endpoint) { - Log(LogInformation, "cluster", "Closing endpoint '" + identity + "': No configuration available."); - tlsStream->Close(); - return; - } - - if (endpoint->GetClient()) { - tlsStream->Close(); - return; - } - - Log(LogInformation, "cluster", "New client connection for identity '" + identity + "'"); - - { - ObjectLock olock(endpoint); - - endpoint->SetSyncing(true); - endpoint->SetSeen(Utility::GetTime()); - endpoint->SetClient(tlsStream); - } - - Dictionary::Ptr config = make_shared(); - Array::Ptr configFiles = endpoint->GetConfigFiles(); - - if (configFiles) { - ObjectLock olock(configFiles); - BOOST_FOREACH(const String& pattern, configFiles) { - Utility::Glob(pattern, boost::bind(&ClusterListener::ConfigGlobHandler, boost::cref(config), _1, false), GlobFile); - } - } - - Array::Ptr configFilesRecursive = endpoint->GetConfigFilesRecursive(); - - if (configFilesRecursive) { - ObjectLock olock(configFilesRecursive); - BOOST_FOREACH(const Value& configFile, configFilesRecursive) { - if (configFile.IsObjectType()) { - Dictionary::Ptr configFileDict = configFile; - String path = configFileDict->Get("path"); - String pattern = configFileDict->Get("pattern"); - Utility::GlobRecursive(path, pattern, boost::bind(&ClusterListener::ConfigGlobHandler, boost::cref(config), _1, false), GlobFile); - } else { - String configFilePath = configFile; - Utility::GlobRecursive(configFilePath, "*.conf", boost::bind(&ClusterListener::ConfigGlobHandler, boost::cref(config), _1, false), GlobFile); - } - } - } - - Log(LogInformation, "cluster", "Sending " + Convert::ToString(static_cast(config->GetLength())) + " config files to endpoint '" + endpoint->GetName() + "'."); - - Dictionary::Ptr params = make_shared(); - params->Set("identity", GetIdentity()); - params->Set("config_files", config); - - Dictionary::Ptr message = make_shared(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "cluster::Config"); - message->Set("params", params); - - NetString::WriteStringToStream(tlsStream, JsonSerialize(message)); - - ReplayLog(endpoint, tlsStream); -} - -void ClusterListener::UpdateLinks(void) -{ - ObjectLock olock(this); - /* build a set of potential routes */ - std::set links; - std::pair kv; - BOOST_FOREACH(kv, m_VisibleEndpoints) { - String endpoint = kv.first; - const EndpointPeerInfo& epi = kv.second; - - if (GetIdentity() == endpoint) - continue; - - if (epi.Seen > Utility::GetTime() - 30) - links.insert(ClusterLink(GetIdentity(), endpoint)); - - if (!epi.Peers) - continue; - - ObjectLock olock(epi.Peers); - BOOST_FOREACH(const String& peer, epi.Peers) - links.insert(ClusterLink(endpoint, peer)); - } - olock.Unlock(); - - /* sort the routes by metric */ - std::vector sortedLinks; - std::copy(links.begin(), links.end(), std::back_inserter(sortedLinks)); - std::sort(sortedLinks.begin(), sortedLinks.end(), ClusterLinkMetricLessComparer()); - - /* pick routes */ - std::set visitedEndpoints; - BOOST_FOREACH(const ClusterLink& link, sortedLinks) { - Endpoint::Ptr other; - - if (link.From == GetIdentity()) - other = Endpoint::GetByName(link.To); - else if (link.To == GetIdentity()) - other = Endpoint::GetByName(link.From); - - if (visitedEndpoints.find(link.From) != visitedEndpoints.end() && - visitedEndpoints.find(link.To) != visitedEndpoints.end()) { - if (other) { - Log(LogInformation, "cluster", "Blocking link to '" + other->GetName() + "'"); - - Dictionary::Ptr message = make_shared(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "cluster::BlockLink"); - message->Set("params", make_shared()); - - AsyncRelayMessage(Endpoint::Ptr(), other, message, false); - } - - continue; - } - - visitedEndpoints.insert(link.From); - visitedEndpoints.insert(link.To); - } -} - -void ClusterListener::ClusterTimerHandler(void) -{ - /* Update endpoint routes */ - UpdateLinks(); - - /* Eww. */ - Dictionary::Ptr features = make_shared(); - features->Set("checker", SupportsChecks()); - features->Set("notification", SupportsNotifications()); - - /* broadcast a heartbeat message */ - BOOST_FOREACH(const Endpoint::Ptr& destination, DynamicType::GetObjects()) { - std::set connected_endpoints; - - BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects()) { - if (endpoint->GetName() == GetIdentity()) - continue; - - if (!endpoint->IsConnected()) - continue; - - connected_endpoints.insert(endpoint->GetName()); - } - - Array::Ptr epnames = make_shared(); - BOOST_FOREACH(const String& name, connected_endpoints) - epnames->Add(name); - - Dictionary::Ptr params = make_shared(); - params->Set("identity", GetIdentity()); - params->Set("features", features); - params->Set("connected_endpoints", epnames); - - Dictionary::Ptr message = make_shared(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "cluster::HeartBeat"); - message->Set("params", params); - - Endpoint::GetByName(GetIdentity())->SetFeatures(features); - - AsyncRelayMessage(Endpoint::Ptr(), destination, message, false); - } - - { - ObjectLock olock(this); - /* check if we've recently seen heartbeat messages from our peers */ - BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects()) { - if (endpoint->GetSeen() > Utility::GetTime() - 60) - continue; - - m_VisibleEndpoints.erase(endpoint->GetName()); - - Stream::Ptr client = endpoint->GetClient(); - - if (client) { - Log(LogWarning, "cluster", "Closing connection for endpoint '" + endpoint->GetName() + "' due to inactivity."); - client->Close(); - endpoint->SetClient(Stream::Ptr()); - } - } - } - - std::vector files; - Utility::Glob(GetClusterDir() + "log/*", boost::bind(&ClusterListener::LogGlobHandler, boost::ref(files), _1), GlobFile); - std::sort(files.begin(), files.end()); - - BOOST_FOREACH(int ts, files) { - bool need = false; - - BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects()) { - if (endpoint->GetName() == GetIdentity()) - continue; - - double position = endpoint->GetLocalLogPosition(); - - if (position != 0 && ts > position) { - need = true; - break; - } - } - - if (!need) { - String path = GetClusterDir() + "log/" + Convert::ToString(ts); - Log(LogInformation, "cluster", "Removing old log file: " + path); - (void) unlink(path.CStr()); - } - } - - UpdateAuthority(); - - Array::Ptr peers = GetPeers(); - - if (peers) { - ObjectLock olock(peers); - BOOST_FOREACH(const String& peer, peers) { - Endpoint::Ptr endpoint = Endpoint::GetByName(peer); - - if (!endpoint) { - Log(LogWarning, "cluster", "Attempted to reconnect to endpoint '" + peer + "': No configuration found."); - continue; - } - - if (endpoint->IsConnected()) - continue; - - String host, port; - host = endpoint->GetHost(); - port = endpoint->GetPort(); - - if (host.IsEmpty() || port.IsEmpty()) { - Log(LogWarning, "cluster", "Can't reconnect " - "to endpoint '" + endpoint->GetName() + "': No " - "host/port information."); - continue; - } - - try { - Log(LogInformation, "cluster", "Attempting to reconnect to cluster endpoint '" + endpoint->GetName() + "' via '" + host + ":" + port + "'."); - AddConnection(host, port); - } catch (std::exception& ex) { - std::ostringstream msgbuf; - msgbuf << "Exception occured while reconnecting to endpoint '" - << endpoint->GetName() << "': " << DiagnosticInformation(ex); - Log(LogWarning, "cluster", msgbuf.str()); - } - } - } -} - -void ClusterListener::SetSecurityInfo(const Dictionary::Ptr& message, const DynamicObject::Ptr& object, int privs) -{ - ASSERT(object); - - Dictionary::Ptr security = make_shared(); - security->Set("type", object->GetType()->GetName()); - security->Set("name", object->GetName()); - security->Set("privs", privs); - - message->Set("security", security); -} - -void ClusterListener::CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, const String& authority) -{ - if (!authority.IsEmpty() && authority != GetIdentity()) - return; - - Dictionary::Ptr params = make_shared(); - params->Set("type", checkable->GetReflectionType()->GetName()); - params->Set("checkable", checkable->GetName()); - params->Set("check_result", Serialize(cr)); - - Dictionary::Ptr message = make_shared(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "cluster::CheckResult"); - message->Set("params", params); - - SetSecurityInfo(message, checkable, DomainPrivRead); - - AsyncRelayMessage(Endpoint::Ptr(), Endpoint::Ptr(), message, true); -} - -void ClusterListener::NextCheckChangedHandler(const Checkable::Ptr& checkable, double nextCheck, const String& authority) -{ - if (!authority.IsEmpty() && authority != GetIdentity()) - return; - - Dictionary::Ptr params = make_shared(); - params->Set("type", checkable->GetReflectionType()->GetName()); - params->Set("checkable", checkable->GetName()); - params->Set("next_check", nextCheck); - - Dictionary::Ptr message = make_shared(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "cluster::SetNextCheck"); - message->Set("params", params); - - SetSecurityInfo(message, checkable, DomainPrivRead); - - AsyncRelayMessage(Endpoint::Ptr(), Endpoint::Ptr(), message, true); -} - -void ClusterListener::NextNotificationChangedHandler(const Notification::Ptr& notification, double nextNotification, const String& authority) -{ - if (!authority.IsEmpty() && authority != GetIdentity()) - return; - - Dictionary::Ptr params = make_shared(); - params->Set("notification", notification->GetName()); - params->Set("next_notification", nextNotification); - - Dictionary::Ptr message = make_shared(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "cluster::SetNextNotification"); - message->Set("params", params); - - SetSecurityInfo(message, notification->GetCheckable(), DomainPrivRead); - - AsyncRelayMessage(Endpoint::Ptr(), Endpoint::Ptr(), message, true); -} - -void ClusterListener::ForceNextCheckChangedHandler(const Checkable::Ptr& checkable, bool forced, const String& authority) -{ - if (!authority.IsEmpty() && authority != GetIdentity()) - return; - - Dictionary::Ptr params = make_shared(); - params->Set("type", checkable->GetReflectionType()->GetName()); - params->Set("checkable", checkable->GetName()); - params->Set("forced", forced); - - Dictionary::Ptr message = make_shared(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "cluster::SetForceNextCheck"); - message->Set("params", params); - - SetSecurityInfo(message, checkable, DomainPrivRead); - - AsyncRelayMessage(Endpoint::Ptr(), Endpoint::Ptr(), message, true); -} - -void ClusterListener::ForceNextNotificationChangedHandler(const Checkable::Ptr& checkable, bool forced, const String& authority) -{ - if (!authority.IsEmpty() && authority != GetIdentity()) - return; - - Dictionary::Ptr params = make_shared(); - params->Set("type", checkable->GetReflectionType()->GetName()); - params->Set("checkable", checkable->GetName()); - params->Set("forced", forced); - - Dictionary::Ptr message = make_shared(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "cluster::SetForceNextNotification"); - message->Set("params", params); - - SetSecurityInfo(message, checkable, DomainPrivRead); - - AsyncRelayMessage(Endpoint::Ptr(), Endpoint::Ptr(), message, true); -} - -void ClusterListener::EnableActiveChecksChangedHandler(const Checkable::Ptr& checkable, bool enabled, const String& authority) -{ - if (!authority.IsEmpty() && authority != GetIdentity()) - return; - - Dictionary::Ptr params = make_shared(); - params->Set("type", checkable->GetReflectionType()->GetName()); - params->Set("checkable", checkable->GetName()); - params->Set("enabled", enabled); - - Dictionary::Ptr message = make_shared(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "cluster::SetEnableActiveChecks"); - message->Set("params", params); - - SetSecurityInfo(message, checkable, DomainPrivRead); - - AsyncRelayMessage(Endpoint::Ptr(), Endpoint::Ptr(), message, true); -} - -void ClusterListener::EnablePassiveChecksChangedHandler(const Checkable::Ptr& checkable, bool enabled, const String& authority) -{ - if (!authority.IsEmpty() && authority != GetIdentity()) - return; - - Dictionary::Ptr params = make_shared(); - params->Set("type", checkable->GetReflectionType()->GetName()); - params->Set("checkable", checkable->GetName()); - params->Set("enabled", enabled); - - Dictionary::Ptr message = make_shared(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "cluster::SetEnablePassiveChecks"); - message->Set("params", params); - - SetSecurityInfo(message, checkable, DomainPrivRead); - - AsyncRelayMessage(Endpoint::Ptr(), Endpoint::Ptr(), message, true); -} - -void ClusterListener::EnableNotificationsChangedHandler(const Checkable::Ptr& checkable, bool enabled, const String& authority) -{ - if (!authority.IsEmpty() && authority != GetIdentity()) - return; - - Dictionary::Ptr params = make_shared(); - params->Set("type", checkable->GetReflectionType()->GetName()); - params->Set("checkable", checkable->GetName()); - params->Set("enabled", enabled); - - Dictionary::Ptr message = make_shared(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "cluster::SetEnableNotifications"); - message->Set("params", params); - - SetSecurityInfo(message, checkable, DomainPrivRead); - - AsyncRelayMessage(Endpoint::Ptr(), Endpoint::Ptr(), message, true); -} - -void ClusterListener::EnableFlappingChangedHandler(const Checkable::Ptr& checkable, bool enabled, const String& authority) -{ - if (!authority.IsEmpty() && authority != GetIdentity()) - return; - - Dictionary::Ptr params = make_shared(); - params->Set("type", checkable->GetReflectionType()->GetName()); - params->Set("checkable", checkable->GetName()); - params->Set("enabled", enabled); - - Dictionary::Ptr message = make_shared(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "cluster::SetEnableFlapping"); - message->Set("params", params); - - SetSecurityInfo(message, checkable, DomainPrivRead); - - AsyncRelayMessage(Endpoint::Ptr(), Endpoint::Ptr(), message, true); -} - -void ClusterListener::CommentAddedHandler(const Checkable::Ptr& checkable, const Comment::Ptr& comment, const String& authority) -{ - if (!authority.IsEmpty() && authority != GetIdentity()) - return; - - Dictionary::Ptr params = make_shared(); - params->Set("type", checkable->GetReflectionType()->GetName()); - params->Set("checkable", checkable->GetName()); - params->Set("comment", Serialize(comment)); - - Dictionary::Ptr message = make_shared(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "cluster::AddComment"); - message->Set("params", params); - - SetSecurityInfo(message, checkable, DomainPrivRead); - - AsyncRelayMessage(Endpoint::Ptr(), Endpoint::Ptr(), message, true); -} - -void ClusterListener::CommentRemovedHandler(const Checkable::Ptr& checkable, const Comment::Ptr& comment, const String& authority) -{ - if (!authority.IsEmpty() && authority != GetIdentity()) - return; - - Dictionary::Ptr params = make_shared(); - params->Set("type", checkable->GetReflectionType()->GetName()); - params->Set("checkable", checkable->GetName()); - params->Set("id", comment->GetId()); - - Dictionary::Ptr message = make_shared(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "cluster::RemoveComment"); - message->Set("params", params); - - SetSecurityInfo(message, checkable, DomainPrivRead); - - AsyncRelayMessage(Endpoint::Ptr(), Endpoint::Ptr(), message, true); -} - -void ClusterListener::DowntimeAddedHandler(const Checkable::Ptr& checkable, const Downtime::Ptr& downtime, const String& authority) -{ - if (!authority.IsEmpty() && authority != GetIdentity()) - return; - - Dictionary::Ptr params = make_shared(); - params->Set("type", checkable->GetReflectionType()->GetName()); - params->Set("checkable", checkable->GetName()); - params->Set("downtime", Serialize(downtime)); - - Dictionary::Ptr message = make_shared(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "cluster::AddDowntime"); - message->Set("params", params); - - SetSecurityInfo(message, checkable, DomainPrivRead); - - AsyncRelayMessage(Endpoint::Ptr(), Endpoint::Ptr(), message, true); -} - -void ClusterListener::DowntimeRemovedHandler(const Checkable::Ptr& checkable, const Downtime::Ptr& downtime, const String& authority) -{ - if (!authority.IsEmpty() && authority != GetIdentity()) - return; - - Dictionary::Ptr params = make_shared(); - params->Set("type", checkable->GetReflectionType()->GetName()); - params->Set("checkable", checkable->GetName()); - params->Set("id", downtime->GetId()); - - Dictionary::Ptr message = make_shared(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "cluster::RemoveDowntime"); - message->Set("params", params); - - SetSecurityInfo(message, checkable, DomainPrivRead); - - AsyncRelayMessage(Endpoint::Ptr(), Endpoint::Ptr(), message, true); -} - -void ClusterListener::AcknowledgementSetHandler(const Checkable::Ptr& checkable, const String& author, const String& comment, AcknowledgementType type, double expiry, const String& authority) -{ - if (!authority.IsEmpty() && authority != GetIdentity()) - return; - - Dictionary::Ptr params = make_shared(); - params->Set("type", checkable->GetReflectionType()->GetName()); - params->Set("checkable", checkable->GetName()); - params->Set("author", author); - params->Set("comment", comment); - params->Set("acktype", type); - params->Set("expiry", expiry); - - Dictionary::Ptr message = make_shared(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "cluster::SetAcknowledgement"); - message->Set("params", params); - - SetSecurityInfo(message, checkable, DomainPrivRead); - - AsyncRelayMessage(Endpoint::Ptr(), Endpoint::Ptr(), message, true); -} - -void ClusterListener::AcknowledgementClearedHandler(const Checkable::Ptr& checkable, const String& authority) -{ - if (!authority.IsEmpty() && authority != GetIdentity()) - return; - - Dictionary::Ptr params = make_shared(); - params->Set("type", checkable->GetReflectionType()->GetName()); - params->Set("checkable", checkable->GetName()); - - Dictionary::Ptr message = make_shared(); - message->Set("jsonrpc", "2.0"); - message->Set("method", "cluster::ClearAcknowledgement"); - message->Set("params", params); - - SetSecurityInfo(message, checkable, DomainPrivRead); - - AsyncRelayMessage(Endpoint::Ptr(), Endpoint::Ptr(), message, true); -} - -void ClusterListener::AsyncMessageHandler(const Endpoint::Ptr& sender, const Dictionary::Ptr& message) -{ - m_MessageQueue.Enqueue(boost::bind(&ClusterListener::MessageHandler, this, sender, message)); -} - -void ClusterListener::MessageExceptionHandler(boost::exception_ptr exp) -{ - Log(LogCritical, "cluster", "Exception while processing cluster message: " + DiagnosticInformation(exp)); -} - -void ClusterListener::MessageHandler(const Endpoint::Ptr& sender, const Dictionary::Ptr& message) -{ - CONTEXT("Processing cluster message of type '" + message->Get("method") + "'"); - - sender->SetSeen(Utility::GetTime()); - - if (message->Contains("ts")) { - double ts = message->Get("ts"); - - /* ignore old messages */ - if (ts < sender->GetRemoteLogPosition()) - return; - - if (sender->GetRemoteLogPosition() + 10 < ts) { - Dictionary::Ptr lparams = make_shared(); - lparams->Set("log_position", message->Get("ts")); - - Dictionary::Ptr lmessage = make_shared(); - lmessage->Set("jsonrpc", "2.0"); - lmessage->Set("method", "cluster::SetLogPosition"); - lmessage->Set("params", lparams); - - sender->SendMessage(lmessage); - - Log(LogInformation, "cluster", "Acknowledging log position for identity '" + sender->GetName() + "': " + Utility::FormatDateTime("%Y/%m/%d %H:%M:%S", message->Get("ts"))); - sender->SetRemoteLogPosition(message->Get("ts")); - - ObjectLock olock(this); - const EndpointPeerInfo& epi = m_VisibleEndpoints[sender->GetName()]; - - if (epi.Peers) { - ObjectLock olock(epi.Peers); - BOOST_FOREACH(const String& epname, epi.Peers) { - if (epname == GetIdentity()) - continue; - - Endpoint::Ptr peer_endpoint = Endpoint::GetByName(epname); - - if (!peer_endpoint) - continue; - - Log(LogInformation, "cluster", "Acknowledging log position for identity '" + peer_endpoint->GetName() + "' (via '" + sender->GetName() + "'): " + Utility::FormatDateTime("%Y/%m/%d %H:%M:%S", message->Get("ts"))); - peer_endpoint->SetRemoteLogPosition(message->Get("ts")); - } - } - } - } - - Dictionary::Ptr params = message->Get("params"); - - if (message->Get("method") == "cluster::HeartBeat") { - if (!params) - return; - - String identity = params->Get("identity"); - - { - ObjectLock olock(this); - EndpointPeerInfo epi; - epi.Seen = Utility::GetTime(); - epi.Peers = params->Get("connected_endpoints"); - m_VisibleEndpoints[identity] = epi; - } - - Endpoint::Ptr endpoint = Endpoint::GetByName(identity); - - if (endpoint) { - endpoint->SetSeen(Utility::GetTime()); - endpoint->SetFeatures(params->Get("features")); - } - - AsyncRelayMessage(sender, Endpoint::Ptr(), message, false); - } else if (message->Get("method") == "cluster::BlockLink") { - Log(LogDebug, "cluster", "Got cluster::BlockLink message. Blocking direct link for '" + sender->GetName() + "'"); - sender->SetBlockedUntil(Utility::GetTime() + 30); - } else if (message->Get("method") == "cluster::CheckResult") { - if (!params) - return; - - String type = params->Get("type"); - String chk = params->Get("checkable"); - - Checkable::Ptr checkable; - - if (type == "Host") - checkable = Host::GetByName(chk); - else if (type == "Service") - checkable = Service::GetByName(chk); - else - return; - - if (!checkable) - return; - - if (!checkable->HasPrivileges(sender->GetName(), DomainPrivCheckResult)) { - Log(LogDebug, "cluster", "Not accepting cluster::CheckResult message from endpoint '" + sender->GetName() + "' for checkable '" + checkable->GetName() + "': Insufficient privileges."); - return; - } - - CheckResult::Ptr cr = Deserialize(params->Get("check_result"), true); - - if (!cr) - return; - - checkable->ProcessCheckResult(cr, sender->GetName()); - - AsyncRelayMessage(sender, Endpoint::Ptr(), message, true); - } else if (message->Get("method") == "cluster::SetNextCheck") { - if (!params) - return; - - String type = params->Get("type"); - String chk = params->Get("checkable"); - - Checkable::Ptr checkable; - - if (type == "Host") - checkable = DynamicObject::GetObject(chk); - else if (type == "Service") - checkable = DynamicObject::GetObject(chk); - else - return; - - if (!checkable) - return; - - if (!checkable->HasPrivileges(sender->GetName(), DomainPrivCommand)) { - Log(LogDebug, "cluster", "Not accepting cluster::SetNextCheck message from endpoint '" + sender->GetName() + "' for checkable '" + checkable->GetName() + "': Insufficient privileges."); - return; - } - - double nextCheck = params->Get("next_check"); - - checkable->SetNextCheck(nextCheck, sender->GetName()); - - AsyncRelayMessage(sender, Endpoint::Ptr(), message, true); - } else if (message->Get("method") == "cluster::SetForceNextCheck") { - if (!params) - return; - - String type = params->Get("type"); - String chk = params->Get("checkable"); - - Checkable::Ptr checkable; - - if (type == "Host") - checkable = DynamicObject::GetObject(chk); - else if (type == "Service") - checkable = DynamicObject::GetObject(chk); - else - return; - - if (!checkable) - return; - - if (!checkable->HasPrivileges(sender->GetName(), DomainPrivCommand)) { - Log(LogDebug, "cluster", "Not accepting cluster::SetForceNextCheck message from endpoint '" + sender->GetName() + "' for checkable '" + checkable->GetName() + "': Insufficient privileges."); - return; - } - - bool forced = params->Get("forced"); - - checkable->SetForceNextCheck(forced, sender->GetName()); - - AsyncRelayMessage(sender, Endpoint::Ptr(), message, true); - } else if (message->Get("method") == "cluster::SetForceNextNotification") { - if (!params) - return; - - String type = params->Get("type"); - String chk = params->Get("checkable"); - - Checkable::Ptr checkable; - - if (type == "Host") - checkable = DynamicObject::GetObject(chk); - else if (type == "Service") - checkable = DynamicObject::GetObject(chk); - else - return; - - if (!checkable) - return; - - if (!checkable->HasPrivileges(sender->GetName(), DomainPrivCommand)) { - Log(LogDebug, "cluster", "Not accepting cluster::SetForceNextNotification message from endpoint '" + sender->GetName() + "' for checkable '" + checkable->GetName() + "': Insufficient privileges."); - return; - } - - bool forced = params->Get("forced"); - - checkable->SetForceNextNotification(forced, sender->GetName()); - - AsyncRelayMessage(sender, Endpoint::Ptr(), message, true); - } else if (message->Get("method") == "cluster::SetEnableActiveChecks") { - if (!params) - return; - - String type = params->Get("type"); - String chk = params->Get("checkable"); - - Checkable::Ptr checkable; - - if (type == "Host") - checkable = DynamicObject::GetObject(chk); - else if (type == "Service") - checkable = DynamicObject::GetObject(chk); - else - return; - - if (!checkable) - return; - - if (!checkable->HasPrivileges(sender->GetName(), DomainPrivCommand)) { - Log(LogDebug, "cluster", "Not accepting cluster::SetEnableActiveChecks message from endpoint '" + sender->GetName() + "' for checkable '" + checkable->GetName() + "': Insufficient privileges."); - return; - } - - bool enabled = params->Get("enabled"); - - checkable->SetEnableActiveChecks(enabled, sender->GetName()); - - AsyncRelayMessage(sender, Endpoint::Ptr(), message, true); - } else if (message->Get("method") == "cluster::SetEnablePassiveChecks") { - if (!params) - return; - - String type = params->Get("type"); - String chk = params->Get("checkable"); - - Checkable::Ptr checkable; - - if (type == "Host") - checkable = DynamicObject::GetObject(chk); - else if (type == "Service") - checkable = DynamicObject::GetObject(chk); - else - return; - - if (!checkable) - return; - - if (!checkable->HasPrivileges(sender->GetName(), DomainPrivCommand)) { - Log(LogDebug, "cluster", "Not accepting cluster::SetEnablePassiveChecks message from endpoint '" + sender->GetName() + "' for checkable '" + checkable->GetName() + "': Insufficient privileges."); - return; - } - - bool enabled = params->Get("enabled"); - - checkable->SetEnablePassiveChecks(enabled, sender->GetName()); - - AsyncRelayMessage(sender, Endpoint::Ptr(), message, true); - } else if (message->Get("method") == "cluster::SetEnableNotifications") { - if (!params) - return; - - String type = params->Get("type"); - String chk = params->Get("checkable"); - - Checkable::Ptr checkable; - - if (type == "Host") - checkable = DynamicObject::GetObject(chk); - else if (type == "Service") - checkable = DynamicObject::GetObject(chk); - else - return; - - if (!checkable) - return; - - if (!checkable->HasPrivileges(sender->GetName(), DomainPrivCommand)) { - Log(LogDebug, "cluster", "Not accepting cluster::SetEnableNotifications message from endpoint '" + sender->GetName() + "' for checkable '" + checkable->GetName() + "': Insufficient privileges."); - return; - } - - bool enabled = params->Get("enabled"); - - checkable->SetEnableNotifications(enabled, sender->GetName()); - - AsyncRelayMessage(sender, Endpoint::Ptr(), message, true); - } else if (message->Get("method") == "cluster::SetEnableFlapping") { - if (!params) - return; - - String type = params->Get("type"); - String chk = params->Get("checkable"); - - Checkable::Ptr checkable; - - if (type == "Host") - checkable = DynamicObject::GetObject(chk); - else if (type == "Service") - checkable = DynamicObject::GetObject(chk); - else - return; - - if (!checkable) - return; - - if (!checkable->HasPrivileges(sender->GetName(), DomainPrivCommand)) { - Log(LogDebug, "cluster", "Not accepting cluster::SetEnableFlapping message from endpoint '" + sender->GetName() + "' for checkable '" + checkable->GetName() + "': Insufficient privileges."); - return; - } - - bool enabled = params->Get("enabled"); - - checkable->SetEnableFlapping(enabled, sender->GetName()); - - AsyncRelayMessage(sender, Endpoint::Ptr(), message, true); - } else if (message->Get("method") == "cluster::SetNextNotification") { - if (!params) - return; - - String nfc = params->Get("notification"); - - Notification::Ptr notification = Notification::GetByName(nfc); - - if (!notification) - return; - - Checkable::Ptr service = notification->GetCheckable(); - - if (!service->HasPrivileges(sender->GetName(), DomainPrivCommand)) { - Log(LogDebug, "cluster", "Not accepting cluster::SetNextNotification message from endpoint '" + sender->GetName() + "' for service '" + service->GetName() + "': Insufficient privileges."); - return; - } - - double nextNotification = params->Get("next_notification"); - - notification->SetNextNotification(nextNotification, sender->GetName()); - - AsyncRelayMessage(sender, Endpoint::Ptr(), message, true); - } else if (message->Get("method") == "cluster::AddComment") { - if (!params) - return; - - String type = params->Get("type"); - String chk = params->Get("checkable"); - - Checkable::Ptr checkable; - - if (type == "Host") - checkable = DynamicObject::GetObject(chk); - else if (type == "Service") - checkable = DynamicObject::GetObject(chk); - else - return; - - if (!checkable) - return; - - if (!checkable->HasPrivileges(sender->GetName(), DomainPrivCommand)) { - Log(LogDebug, "cluster", "Not accepting cluster::AddComment message from endpoint '" + sender->GetName() + "' for checkable '" + checkable->GetName() + "': Insufficient privileges."); - return; - } - - Comment::Ptr comment = Deserialize(params->Get("comment"), true); - - checkable->AddComment(comment->GetEntryType(), comment->GetAuthor(), - comment->GetText(), comment->GetExpireTime(), comment->GetId(), sender->GetName()); - - AsyncRelayMessage(sender, Endpoint::Ptr(), message, true); - } else if (message->Get("method") == "cluster::RemoveComment") { - if (!params) - return; - - String type = params->Get("type"); - String chk = params->Get("checkable"); - - Checkable::Ptr checkable; - - if (type == "Host") - checkable = DynamicObject::GetObject(chk); - else if (type == "Service") - checkable = DynamicObject::GetObject(chk); - else - return; - - if (!checkable) - return; - - if (!checkable->HasPrivileges(sender->GetName(), DomainPrivCommand)) { - Log(LogDebug, "cluster", "Not accepting cluster::RemoveComment message from endpoint '" + sender->GetName() + "' for checkable '" + checkable->GetName() + "': Insufficient privileges."); - return; - } - - String id = params->Get("id"); - - checkable->RemoveComment(id, sender->GetName()); - - AsyncRelayMessage(sender, Endpoint::Ptr(), message, true); - } else if (message->Get("method") == "cluster::AddDowntime") { - if (!params) - return; - - String type = params->Get("type"); - String chk = params->Get("checkable"); - - Checkable::Ptr checkable; - - if (type == "Host") - checkable = DynamicObject::GetObject(chk); - else if (type == "Service") - checkable = DynamicObject::GetObject(chk); - else - return; - - if (!checkable) - return; - - if (!checkable->HasPrivileges(sender->GetName(), DomainPrivCommand)) { - Log(LogDebug, "cluster", "Not accepting cluster::AddDowntime message from endpoint '" + sender->GetName() + "' for checkable '" + checkable->GetName() + "': Insufficient privileges."); - return; - } - - Downtime::Ptr downtime = Deserialize(params->Get("downtime"), true); - - checkable->AddDowntime(downtime->GetAuthor(), downtime->GetComment(), - downtime->GetStartTime(), downtime->GetEndTime(), - downtime->GetFixed(), downtime->GetTriggeredBy(), - downtime->GetDuration(), downtime->GetScheduledBy(), - downtime->GetId(), sender->GetName()); - - AsyncRelayMessage(sender, Endpoint::Ptr(), message, true); - } else if (message->Get("method") == "cluster::RemoveDowntime") { - if (!params) - return; - - String type = params->Get("type"); - String chk = params->Get("checkable"); - - Checkable::Ptr checkable; - - if (type == "Host") - checkable = DynamicObject::GetObject(chk); - else if (type == "Service") - checkable = DynamicObject::GetObject(chk); - else - return; - - if (!checkable) - return; - - if (!checkable->HasPrivileges(sender->GetName(), DomainPrivCommand)) { - Log(LogDebug, "cluster", "Not accepting cluster::RemoveDowntime message from endpoint '" + sender->GetName() + "' for checkable '" + checkable->GetName() + "': Insufficient privileges."); - return; - } - - String id = params->Get("id"); - - checkable->RemoveDowntime(id, false, sender->GetName()); - - AsyncRelayMessage(sender, Endpoint::Ptr(), message, true); - } else if (message->Get("method") == "cluster::SetAcknowledgement") { - if (!params) - return; - - String type = params->Get("type"); - String chk = params->Get("checkable"); - - Checkable::Ptr checkable; - - if (type == "Host") - checkable = DynamicObject::GetObject(chk); - else if (type == "Service") - checkable = DynamicObject::GetObject(chk); - else - return; - - if (!checkable) - return; - - if (!checkable->HasPrivileges(sender->GetName(), DomainPrivCommand)) { - Log(LogDebug, "cluster", "Not accepting cluster::SetAcknowledgement message from endpoint '" + sender->GetName() + "' for checkable '" + checkable->GetName() + "': Insufficient privileges."); - return; - } - - String author = params->Get("author"); - String comment = params->Get("comment"); - int acktype = params->Get("acktype"); - double expiry = params->Get("expiry"); - - checkable->AcknowledgeProblem(author, comment, static_cast(acktype), expiry, sender->GetName()); - - AsyncRelayMessage(sender, Endpoint::Ptr(), message, true); - } else if (message->Get("method") == "cluster::ClearAcknowledgement") { - if (!params) - return; - - String type = params->Get("type"); - String chk = params->Get("checkable"); - - Checkable::Ptr checkable; - - if (type == "Host") - checkable = DynamicObject::GetObject(chk); - else if (type == "Service") - checkable = DynamicObject::GetObject(chk); - else - return; - - if (!checkable) - return; - - if (!checkable->HasPrivileges(sender->GetName(), DomainPrivCommand)) { - Log(LogDebug, "cluster", "Not accepting cluster::ClearAcknowledgement message from endpoint '" + sender->GetName() + "' for checkable '" + checkable->GetName() + "': Insufficient privileges."); - return; - } - - { - ObjectLock olock(checkable); - checkable->ClearAcknowledgement(sender->GetName()); - } - - AsyncRelayMessage(sender, Endpoint::Ptr(), message, true); - } else if (message->Get("method") == "cluster::SetLogPosition") { - if (!params) - return; - - sender->SetLocalLogPosition(params->Get("log_position")); - } else if (message->Get("method") == "cluster::Config") { - if (!params) - return; - - Dictionary::Ptr remoteConfig = params->Get("config_files"); - - if (!remoteConfig) - return; - - Endpoint::Ptr self = Endpoint::GetByName(GetIdentity()); - - Array::Ptr acceptConfig = self->GetAcceptConfig(); - - bool accept = false; - - if (acceptConfig) { - ObjectLock olock(acceptConfig); - BOOST_FOREACH(const String& pattern, acceptConfig) { - if (pattern == sender->GetName()) { - accept = true; - break; - } - } - } - - String identity = params->Get("identity"); - - if (!accept) { - Log(LogWarning, "cluster", "Ignoring config update from endpoint '" + sender->GetName() + "' for identity '" + identity + "'."); - return; - } - - Log(LogInformation, "cluster", "Processing config update for identity '" + identity + "'."); - - String dir = GetClusterDir() + "config/" + SHA256(identity); -#ifndef _WIN32 - if (mkdir(dir.CStr(), 0700) < 0 && errno != EEXIST) { -#else /*_ WIN32 */ - if (mkdir(dir.CStr()) < 0 && errno != EEXIST) { -#endif /* _WIN32 */ - BOOST_THROW_EXCEPTION(posix_error() - << boost::errinfo_api_function("localtime") - << boost::errinfo_errno(errno)); - } - - Dictionary::Ptr localConfig = make_shared(); - Utility::Glob(dir + "/*", boost::bind(&ClusterListener::ConfigGlobHandler, boost::cref(localConfig), _1, true), GlobFile); - - bool configChange = false; - - /* figure out whether config files were removed */ - if (localConfig->GetLength() != remoteConfig->GetLength()) - configChange = true; - - ObjectLock olock(remoteConfig); - BOOST_FOREACH(const Dictionary::Pair& kv, remoteConfig) { - Dictionary::Ptr remoteFile = kv.second; - bool writeFile = false; - String hash = SHA256(kv.first); - String path = dir + "/" + hash + ".conf"; - - if (!localConfig->Contains(hash)) - writeFile = true; - else { - Dictionary::Ptr localFile = localConfig->Get(hash); - - String localContent = localFile->Get("content"); - String remoteContent = remoteFile->Get("content"); - - if (localContent != remoteContent) - writeFile = true; - } - - if (writeFile) { - configChange = true; - - Log(LogInformation, "cluster", "Updating configuration file: " + path); - - std::ofstream fp(path.CStr(), std::ofstream::out | std::ostream::trunc); - fp << remoteFile->Get("content"); - fp.close(); - } - - localConfig->Remove(hash); - } - olock.Unlock(); - - ObjectLock olock2(localConfig); - BOOST_FOREACH(const Dictionary::Pair& kv, localConfig) { - String path = dir + "/" + kv.first; - Log(LogInformation, "cluster", "Removing obsolete config file: " + path); - (void) unlink(path.CStr()); - configChange = true; - } - olock2.Unlock(); - - if (configChange) { - Log(LogInformation, "cluster", "Restarting after configuration change."); - Application::RequestRestart(); - } - - AsyncRelayMessage(sender, Endpoint::Ptr(), message, true); - } -} - -bool ClusterListener::IsAuthority(const DynamicObject::Ptr& object, const String& type) -{ - double now = Utility::GetTime(); - - Array::Ptr authorities = object->GetAuthorities(); - std::vector endpoints; - - BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects()) { - bool match = false; - - if ((endpoint->GetSeen() < now - 30 && endpoint->GetName() != GetIdentity()) || !endpoint->HasFeature(type)) - continue; - - if (authorities) { - ObjectLock olock(authorities); - BOOST_FOREACH(const String& authority, authorities) { - if (authority == endpoint->GetName()) { - match = true; - - break; - } - } - } else { - match = true; - } - - if (match) - endpoints.push_back(endpoint->GetName()); - } - - if (endpoints.empty()) - return false; - - std::sort(endpoints.begin(), endpoints.end()); - - String key = object->GetType()->GetName() + "\t" + object->GetName(); - unsigned long hash = Utility::SDBM(key); - unsigned long index = hash % endpoints.size(); - -// Log(LogDebug, "cluster", "Authority for object '" + object->GetName() + "' of type '" + object->GetType()->GetName() + "' is '" + endpoints[index] + "'."); - - return (endpoints[index] == GetIdentity()); -} - -void ClusterListener::UpdateAuthority(void) -{ - Log(LogDebug, "cluster", "Updating authority for objects."); - - int checker_count = 0, notifications_count = 0; - - BOOST_FOREACH(const DynamicType::Ptr& type, DynamicType::GetTypes()) { - BOOST_FOREACH(const DynamicObject::Ptr& object, type->GetObjects()) { - bool checkerAuthority = IsAuthority(object, "checker"); - - if (checkerAuthority) - checker_count++; - - object->SetAuthority("checker", checkerAuthority); - - bool notificationAuthority = IsAuthority(object, "notifications"); - - if (notificationAuthority) - notifications_count++; - - object->SetAuthority("notifications", notificationAuthority); - } - } - - Log(LogDebug, "cluster", "Cluster authority: " + Convert::ToString(checker_count) + "x checker, " + Convert::ToString(notifications_count) + "x notifications"); -} - -bool ClusterListener::SupportsChecks(void) -{ - return SupportsFeature("CheckerComponent") && (IcingaApplication::GetInstance()->GetEnableHostChecks() || IcingaApplication::GetInstance()->GetEnableServiceChecks()); -} - -bool ClusterListener::SupportsNotifications(void) -{ - return SupportsFeature("NotificationComponent") && IcingaApplication::GetInstance()->GetEnableNotifications(); -} - -bool ClusterListener::SupportsFeature(const String& name) -{ - DynamicType::Ptr type = DynamicType::GetByName(name); - - if (!type) - return false; - - return std::distance(type->GetObjects().first, type->GetObjects().second) > 0; -} - -std::pair ClusterListener::GetClusterStatus(void) -{ - Dictionary::Ptr status = make_shared(); - Dictionary::Ptr perfdata = make_shared(); - - /* cluster stats */ - status->Set("node", IcingaApplication::GetInstance()->GetNodeName()); - status->Set("identity", GetIdentity()); - - double count_endpoints = 0; - Array::Ptr not_connected_endpoints = make_shared(); - Array::Ptr connected_endpoints = make_shared(); - - BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects()) { - count_endpoints++; - - if(!endpoint->IsAvailable() && endpoint->GetName() != GetIdentity()) - not_connected_endpoints->Add(endpoint->GetName()); - else if(endpoint->IsAvailable() && endpoint->GetName() != GetIdentity()) - connected_endpoints->Add(endpoint->GetName()); - } - - status->Set("num_endpoints", count_endpoints); - status->Set("num_conn_endpoints", connected_endpoints->GetLength()); - status->Set("num_not_conn_endpoints", not_connected_endpoints->GetLength()); - status->Set("conn_endpoints", connected_endpoints); - status->Set("not_conn_endpoints", not_connected_endpoints); - - perfdata->Set("num_endpoints", count_endpoints); - perfdata->Set("num_conn_endpoints", Convert::ToDouble(connected_endpoints->GetLength())); - perfdata->Set("num_not_conn_endpoints", Convert::ToDouble(not_connected_endpoints->GetLength())); - - return std::make_pair(status, perfdata); -} diff --git a/components/cluster/clusterlistener.h b/components/cluster/clusterlistener.h deleted file mode 100644 index 51d3eaa3b..000000000 --- a/components/cluster/clusterlistener.h +++ /dev/null @@ -1,139 +0,0 @@ -/****************************************************************************** - * Icinga 2 * - * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * - * * - * 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 2 * - * 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, write to the Free Software Foundation * - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * - ******************************************************************************/ - -#ifndef CLUSTERLISTENER_H -#define CLUSTERLISTENER_H - -#include "cluster/clusterlistener.th" -#include "cluster/clusterlink.h" -#include "base/dynamicobject.h" -#include "base/timer.h" -#include "base/array.h" -#include "base/tcpsocket.h" -#include "base/tlsstream.h" -#include "base/utility.h" -#include "base/tlsutility.h" -#include "base/stdiostream.h" -#include "base/workqueue.h" -#include "icinga/service.h" -#include "remote/endpoint.h" - -namespace icinga -{ - -/** - * @ingroup cluster - */ -struct EndpointPeerInfo -{ - double Seen; - Array::Ptr Peers; -}; - -/** - * @ingroup cluster - */ -class ClusterListener : public ObjectImpl -{ -public: - DECLARE_PTR_TYPEDEFS(ClusterListener); - DECLARE_TYPENAME(ClusterListener); - - static Value StatsFunc(Dictionary::Ptr& status, Dictionary::Ptr& perfdata); - - virtual void Start(void); - virtual void Stop(void); - - shared_ptr GetSSLContext(void) const; - String GetClusterDir(void) const; - - std::pair GetClusterStatus(void); - -private: - shared_ptr m_SSLContext; - - WorkQueue m_RelayQueue; - WorkQueue m_MessageQueue; - WorkQueue m_LogQueue; - - Timer::Ptr m_ClusterTimer; - void ClusterTimerHandler(void); - - std::set m_Servers; - - void AddListener(const String& service); - void AddConnection(const String& node, const String& service); - - static void ConfigGlobHandler(const Dictionary::Ptr& config, const String& file, bool basename); - - void NewClientHandler(const Socket::Ptr& client, TlsRole role); - void ListenerThreadProc(const Socket::Ptr& server); - - std::map m_VisibleEndpoints; - - void UpdateLinks(void); - - void AsyncRelayMessage(const Endpoint::Ptr& source, const Endpoint::Ptr& destination, const Dictionary::Ptr& message, bool persistent); - void RelayMessage(const Endpoint::Ptr& source, const Endpoint::Ptr& destination, const Dictionary::Ptr& message, bool persistent); - - void OpenLogFile(void); - void RotateLogFile(void); - void CloseLogFile(void); - static void LogGlobHandler(std::vector& files, const String& file); - void ReplayLog(const Endpoint::Ptr& endpoint, const Stream::Ptr& stream); - - Stream::Ptr m_LogFile; - size_t m_LogMessageCount; - - void CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, const String& authority); - void NextCheckChangedHandler(const Checkable::Ptr& checkable, double nextCheck, const String& authority); - void NextNotificationChangedHandler(const Notification::Ptr& notification, double nextCheck, const String& authority); - void ForceNextCheckChangedHandler(const Checkable::Ptr& checkable, bool forced, const String& authority); - void ForceNextNotificationChangedHandler(const Checkable::Ptr& checkable, bool forced, const String& authority); - void EnableActiveChecksChangedHandler(const Checkable::Ptr& checkable, bool enabled, const String& authority); - void EnablePassiveChecksChangedHandler(const Checkable::Ptr& checkable, bool enabled, const String& authority); - void EnableNotificationsChangedHandler(const Checkable::Ptr& checkable, bool enabled, const String& authority); - void EnableFlappingChangedHandler(const Checkable::Ptr& checkable, bool enabled, const String& authority); - void CommentAddedHandler(const Checkable::Ptr& checkable, const Comment::Ptr& comment, const String& authority); - void CommentRemovedHandler(const Checkable::Ptr& checkable, const Comment::Ptr& comment, const String& authority); - void DowntimeAddedHandler(const Checkable::Ptr& checkable, const Downtime::Ptr& downtime, const String& authority); - void DowntimeRemovedHandler(const Checkable::Ptr& checkable, const Downtime::Ptr& downtime, const String& authority); - void AcknowledgementSetHandler(const Checkable::Ptr& checkable, const String& author, const String& comment, AcknowledgementType type, double expiry, const String& authority); - void AcknowledgementClearedHandler(const Checkable::Ptr& checkable, const String& authority); - - void AsyncMessageHandler(const Endpoint::Ptr& sender, const Dictionary::Ptr& message); - void MessageHandler(const Endpoint::Ptr& sender, const Dictionary::Ptr& message); - - bool IsAuthority(const DynamicObject::Ptr& object, const String& type); - void UpdateAuthority(void); - - static bool SupportsChecks(void); - static bool SupportsNotifications(void); - static bool SupportsFeature(const String& name); - - void SetSecurityInfo(const Dictionary::Ptr& message, const DynamicObject::Ptr& object, int privs); - - void PersistMessage(const Endpoint::Ptr& source, const Dictionary::Ptr& message); - - static void MessageExceptionHandler(boost::exception_ptr exp); -}; - -} - -#endif /* CLUSTERLISTENER_H */ diff --git a/components/demo/CMakeLists.txt b/components/demo/CMakeLists.txt index 2b1d0f3a0..d31377efa 100644 --- a/components/demo/CMakeLists.txt +++ b/components/demo/CMakeLists.txt @@ -21,7 +21,7 @@ mkembedconfig_target(demo-type.conf demo-type.cpp) add_library(demo SHARED demo.cpp demo.th demo-type.cpp) -target_link_libraries(demo ${Boost_LIBRARIES} base config icinga) +target_link_libraries(demo ${Boost_LIBRARIES} base config icinga remote) set_target_properties ( demo PROPERTIES diff --git a/components/demo/demo.cpp b/components/demo/demo.cpp index abb91c0da..c8dde9966 100644 --- a/components/demo/demo.cpp +++ b/components/demo/demo.cpp @@ -18,6 +18,8 @@ ******************************************************************************/ #include "demo/demo.h" +#include "remote/apilistener.h" +#include "remote/apifunction.h" #include "base/dynamictype.h" #include "base/logger_fwd.h" @@ -25,6 +27,8 @@ using namespace icinga; REGISTER_TYPE(Demo); +REGISTER_APIFUNCTION(HelloWorld, demo, &Demo::DemoMessageHandler); + /** * Starts the component. */ @@ -39,19 +43,23 @@ void Demo::Start(void) } /** - * Stops the component. + * Periodically broadcasts an API message. */ -void Demo::Stop(void) +void Demo::DemoTimerHandler(void) { - /* Nothing to do here. */ + Dictionary::Ptr message = make_shared(); + message->Set("method", "demo::HelloWorld"); + + ApiListener::Ptr listener = ApiListener::GetInstance(); + if (listener) { + listener->RelayMessage(MessageOrigin(), DynamicObject::Ptr(), message, true); + Log(LogInformation, "demo", "Sent demo::HelloWorld message"); + } } -/** - * Periodically sends a demo::HelloWorld message. - * - * @param - Event arguments for the timer. - */ -void Demo::DemoTimerHandler(void) +Value Demo::DemoMessageHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) { - Log(LogInformation, "demo", "Hello World!"); + Log(LogInformation, "demo", "Got demo message from '" + origin.FromClient->GetEndpoint()->GetName() + "'"); + + return Empty; } diff --git a/components/demo/demo.h b/components/demo/demo.h index 299b59faf..f70e3f23e 100644 --- a/components/demo/demo.h +++ b/components/demo/demo.h @@ -21,6 +21,7 @@ #define DEMO_H #include "demo/demo.th" +#include "remote/messageorigin.h" #include "base/timer.h" namespace icinga @@ -36,7 +37,9 @@ public: DECLARE_TYPENAME(Demo); virtual void Start(void); - virtual void Stop(void); + + static Value DemoMessageHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); + private: Timer::Ptr m_DemoTimer; diff --git a/etc/CMakeLists.txt b/etc/CMakeLists.txt index 93f642d3d..e2d869bc7 100644 --- a/etc/CMakeLists.txt +++ b/etc/CMakeLists.txt @@ -40,7 +40,7 @@ install_if_not_exists(icinga2/conf.d/hosts/localhost/users.conf ${CMAKE_INSTALL_ install_if_not_exists(icinga2/conf.d/notifications.conf ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/conf.d) install_if_not_exists(icinga2/conf.d/timeperiods.conf ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/conf.d) install_if_not_exists(icinga2/conf.d/users.conf ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/conf.d) -install_if_not_exists(icinga2/features-available/agent.conf ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/features-available) +install_if_not_exists(icinga2/features-available/api.conf ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/features-available) install_if_not_exists(icinga2/features-available/checker.conf ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/features-available) install_if_not_exists(icinga2/features-available/command.conf ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/features-available) install_if_not_exists(icinga2/features-available/compatlog.conf ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/features-available) @@ -59,6 +59,8 @@ install_if_not_exists(icinga2/scripts/mail-host-notification.sh ${CMAKE_INSTALL_ install_if_not_exists(icinga2/scripts/mail-service-notification.sh ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/scripts) install_if_not_exists(logrotate.d/icinga2 ${CMAKE_INSTALL_SYSCONFDIR}/logrotate.d) +install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_SYSCONFDIR}/icinga2/pki\")") + if(NOT WIN32) install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_SYSCONFDIR}/icinga2/features-enabled\")") install(CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" -E create_symlink ../features-available/checker.conf \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_SYSCONFDIR}/icinga2/features-enabled/checker.conf\")") diff --git a/etc/icinga2/constants.conf b/etc/icinga2/constants.conf index 74539d267..7f9c9146f 100644 --- a/etc/icinga2/constants.conf +++ b/etc/icinga2/constants.conf @@ -1,7 +1,13 @@ /** * This file defines global constants which can be used in - * the other configuration files. At a minimum the - * PluginDir constant should be defined. + * the other configuration files. */ +/* The directory which contains the plugins from the Monitoring Plugins project. */ const PluginDir = "/usr/lib/nagios/plugins" + +/* Our local instance name. This should be the common name from the API certificate */ +const NodeName = "localhost" + +/* Our local zone name. */ +const ZoneName = "master" diff --git a/etc/icinga2/features-available/agent.conf b/etc/icinga2/features-available/agent.conf deleted file mode 100644 index 152e9fd5d..000000000 --- a/etc/icinga2/features-available/agent.conf +++ /dev/null @@ -1,11 +0,0 @@ -/** - * The agent listener accepts checks from agents. - */ - -library "agent" - -object AgentListener "agent" { - cert_path = SysconfDir + "/icinga2/pki/your-master.crt" - key_path = SysconfDir + "/icinga2/pki/your-master.key" - ca_path = SysconfDir + "/icinga2/pki/ca.crt" -} diff --git a/etc/icinga2/features-available/api.conf b/etc/icinga2/features-available/api.conf new file mode 100644 index 000000000..802100a0f --- /dev/null +++ b/etc/icinga2/features-available/api.conf @@ -0,0 +1,27 @@ +/** + * The API listener is used for distributed monitoring setups. + */ + +object ApiListener "api" { + cert_path = SysconfDir + "/icinga2/pki/" + NodeName + ".crt" + key_path = SysconfDir + "/icinga2/pki/" + NodeName + ".key" + ca_path = SysconfDir + "/icinga2/pki/ca.crt" +} + +object Endpoint NodeName { + host = NodeName +} + +object Zone ZoneName { + endpoints = [ NodeName ] +} + +/*object Endpoint "satellite.example.org" { + host = "satellite.example.org" +} + +object Zone "satellite" { + parent = "master" + endpoints = [ "satellite.example.org" ] +}*/ + diff --git a/icinga-app/icinga.cpp b/icinga-app/icinga.cpp index b4bd319da..3058376b5 100644 --- a/icinga-app/icinga.cpp +++ b/icinga-app/icinga.cpp @@ -652,6 +652,9 @@ VOID WINAPI ServiceMain(DWORD argc, LPSTR *argv) */ int main(int argc, char **argv) { + /* must be called before using any other libbase functions */ + Application::InitializeBase(); + /* Set command-line arguments. */ Application::SetArgC(argc); Application::SetArgV(argv); diff --git a/lib/base/application.cpp b/lib/base/application.cpp index 03a3cf64b..bf9f8d342 100644 --- a/lib/base/application.cpp +++ b/lib/base/application.cpp @@ -103,6 +103,11 @@ Application::~Application(void) m_Instance = NULL; } +void Application::InitializeBase(void) +{ + Utility::ExecuteDeferredInitializers(); +} + /** * Retrieves a pointer to the application singleton object. * diff --git a/lib/base/application.h b/lib/base/application.h index 9f5e5ebe1..3be23547e 100644 --- a/lib/base/application.h +++ b/lib/base/application.h @@ -26,9 +26,8 @@ #include "base/dynamicobject.h" #include "base/process.h" -namespace icinga { - -class Component; +namespace icinga +{ /** * Abstract base class for applications. @@ -41,6 +40,8 @@ public: ~Application(void); + static void InitializeBase(void); + static Application::Ptr GetInstance(void); int Run(void); diff --git a/lib/base/dynamicobject.cpp b/lib/base/dynamicobject.cpp index 334065dff..286c80b6c 100644 --- a/lib/base/dynamicobject.cpp +++ b/lib/base/dynamicobject.cpp @@ -44,8 +44,7 @@ INITIALIZE_ONCE(&DynamicObject::StaticInitialize); boost::signals2::signal DynamicObject::OnStarted; boost::signals2::signal DynamicObject::OnStopped; -boost::signals2::signal DynamicObject::OnStateChanged; -boost::signals2::signal DynamicObject::OnAuthorityChanged; +boost::signals2::signal DynamicObject::OnStateChanged; boost::signals2::signal DynamicObject::OnVarsChanged; void DynamicObject::StaticInitialize(void) @@ -71,56 +70,6 @@ bool DynamicObject::IsActive(void) const return GetActive(); } -void DynamicObject::SetAuthority(const String& type, bool value) -{ - ASSERT(!OwnsLock()); - - { - ObjectLock olock(this); - - bool old_value = HasAuthority(type); - - if (old_value == value) - return; - - if (GetAuthorityInfo() == NULL) - SetAuthorityInfo(make_shared()); - - GetAuthorityInfo()->Set(type, value); - } - - OnAuthorityChanged(GetSelf(), type, value); -} - -bool DynamicObject::HasAuthority(const String& type) const -{ - Dictionary::Ptr authorityInfo = GetAuthorityInfo(); - - if (!authorityInfo || !authorityInfo->Contains(type)) - return true; - - return authorityInfo->Get(type); -} - -void DynamicObject::SetPrivileges(const String& instance, int privs) -{ - m_Privileges[instance] = privs; -} - -bool DynamicObject::HasPrivileges(const String& instance, int privs) const -{ - if (privs == 0) - return true; - - std::map::const_iterator it; - it = m_Privileges.find(instance); - - if (it == m_Privileges.end()) - return false; - - return (it->second & privs) == privs; -} - void DynamicObject::SetExtension(const String& key, const Object::Ptr& object) { Dictionary::Ptr extensions = GetExtensions(); diff --git a/lib/base/dynamicobject.h b/lib/base/dynamicobject.h index f729891d3..0dbf09f6b 100644 --- a/lib/base/dynamicobject.h +++ b/lib/base/dynamicobject.h @@ -77,8 +77,7 @@ public: static boost::signals2::signal OnStarted; static boost::signals2::signal OnStopped; - static boost::signals2::signal OnStateChanged; - static boost::signals2::signal OnAuthorityChanged; + static boost::signals2::signal OnStateChanged; static boost::signals2::signal OnVarsChanged; Value InvokeMethod(const String& method, const std::vector& arguments); @@ -87,12 +86,6 @@ public: bool IsActive(void) const; - void SetAuthority(const String& type, bool value); - bool HasAuthority(const String& type) const; - - void SetPrivileges(const String& instance, int privs); - bool HasPrivileges(const String& instance, int privs) const; - void SetExtension(const String& key, const Object::Ptr& object); Object::Ptr GetExtension(const String& key); void ClearExtension(const String& key); diff --git a/lib/base/dynamicobject.ti b/lib/base/dynamicobject.ti index 6adbcc5ef..4f9fce254 100644 --- a/lib/base/dynamicobject.ti +++ b/lib/base/dynamicobject.ti @@ -20,12 +20,10 @@ abstract class DynamicObject }}} }; [config, get_protected] String type (TypeName); - [config] String package; + [config] String zone; [config, get_protected] Array::Ptr templates; [config] Dictionary::Ptr methods; [config] Dictionary::Ptr vars (VarsRaw); - [config] Array::Ptr domains; - [config] Array::Ptr authorities; [get_protected] bool active; [get_protected] bool start_called; [get_protected] bool stop_called; diff --git a/lib/base/initialize.h b/lib/base/initialize.h index 43120c068..b0dfff905 100644 --- a/lib/base/initialize.h +++ b/lib/base/initialize.h @@ -30,11 +30,7 @@ typedef void (*InitializeFunc)(void); inline bool InitializeOnceHelper(InitializeFunc func) { - if (Utility::GetLoadingLibrary()) - Utility::AddDeferredInitializer(func); - else - func(); - + Utility::AddDeferredInitializer(func); return true; } diff --git a/lib/base/serializer.cpp b/lib/base/serializer.cpp index 88c2ad7ac..cfea4069f 100644 --- a/lib/base/serializer.cpp +++ b/lib/base/serializer.cpp @@ -1,21 +1,21 @@ /****************************************************************************** -* Icinga 2 * -* Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * -* * -* 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 2 * -* 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, write to the Free Software Foundation * -* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * -******************************************************************************/ + * Icinga 2 * + * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * + * * + * 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 2 * + * 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, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ #include "base/serializer.h" #include "base/type.h" diff --git a/lib/base/socket.h b/lib/base/socket.h index e8c008ac0..f055ce521 100644 --- a/lib/base/socket.h +++ b/lib/base/socket.h @@ -27,7 +27,8 @@ #include #include -namespace icinga { +namespace icinga +{ /** * Base class for connection-oriented sockets. diff --git a/lib/base/stream.h b/lib/base/stream.h index 78ea8acad..cf96750ed 100644 --- a/lib/base/stream.h +++ b/lib/base/stream.h @@ -29,6 +29,12 @@ namespace icinga { +enum ConnectionRole +{ + RoleClient, + RoleServer +}; + struct ReadLineContext { ReadLineContext(void) : Buffer(NULL), Size(0), Eof(false), MustRead(true) diff --git a/lib/base/timer.cpp b/lib/base/timer.cpp index 85b5cfa4f..8afe259d7 100644 --- a/lib/base/timer.cpp +++ b/lib/base/timer.cpp @@ -21,7 +21,6 @@ #include "base/application.h" #include "base/debug.h" #include "base/utility.h" -#include "base/logger_fwd.h" #include #include #include @@ -48,7 +47,7 @@ struct icinga::TimerNextExtractor * @param wtimer Weak pointer to the timer. * @returns The next timestamp */ - double operator()(const weak_ptr& wtimer) + double operator()(const weak_ptr& wtimer) const { Timer::Ptr timer = wtimer.lock(); @@ -87,7 +86,7 @@ void Timer::Initialize(void) { boost::mutex::scoped_lock lock(l_Mutex); l_StopThread = false; - l_Thread = boost::thread(boost::bind(&Timer::TimerThreadProc)); + l_Thread = boost::thread(&Timer::TimerThreadProc); } /** @@ -296,7 +295,7 @@ void Timer::TimerThreadProc(void) double wait = timer->m_Next - Utility::GetTime(); - if (wait > 0) { + if (wait > 0.01) { /* Make sure the timer we just examined can be destroyed while we're waiting. */ timer.reset(); diff --git a/lib/base/tlsstream.cpp b/lib/base/tlsstream.cpp index b875a16cf..7a9ab3901 100644 --- a/lib/base/tlsstream.cpp +++ b/lib/base/tlsstream.cpp @@ -36,7 +36,7 @@ bool I2_EXPORT TlsStream::m_SSLIndexInitialized = false; * @param role The role of the client. * @param sslContext The SSL context for the client. */ -TlsStream::TlsStream(const Socket::Ptr& socket, TlsRole role, shared_ptr sslContext) +TlsStream::TlsStream(const Socket::Ptr& socket, ConnectionRole role, shared_ptr sslContext) : m_Socket(socket), m_Role(role) { m_SSL = shared_ptr(SSL_new(sslContext.get()), SSL_free); @@ -62,7 +62,7 @@ TlsStream::TlsStream(const Socket::Ptr& socket, TlsRole role, shared_ptr TlsStream::GetPeerCertificate(void) const void TlsStream::Handshake(void) { - ASSERT(!OwnsLock()); - for (;;) { int rc, err; { - ObjectLock olock(this); + boost::mutex::scoped_lock lock(m_SSLLock); rc = SSL_do_handshake(m_SSL.get()); if (rc > 0) @@ -128,15 +126,13 @@ void TlsStream::Handshake(void) */ size_t TlsStream::Read(void *buffer, size_t count) { - ASSERT(!OwnsLock()); - size_t left = count; while (left > 0) { int rc, err; { - ObjectLock olock(this); + boost::mutex::scoped_lock lock(m_SSLLock); rc = SSL_read(m_SSL.get(), ((char *)buffer) + (count - left), left); if (rc <= 0) @@ -169,15 +165,13 @@ size_t TlsStream::Read(void *buffer, size_t count) void TlsStream::Write(const void *buffer, size_t count) { - ASSERT(!OwnsLock()); - size_t left = count; while (left > 0) { int rc, err; { - ObjectLock olock(this); + boost::mutex::scoped_lock lock(m_SSLLock); rc = SSL_write(m_SSL.get(), ((const char *)buffer) + (count - left), left); if (rc <= 0) @@ -211,13 +205,11 @@ void TlsStream::Write(const void *buffer, size_t count) */ void TlsStream::Close(void) { - ASSERT(!OwnsLock()); - for (;;) { int rc, err; { - ObjectLock olock(this); + boost::mutex::scoped_lock lock(m_SSLLock); do { rc = SSL_shutdown(m_SSL.get()); diff --git a/lib/base/tlsstream.h b/lib/base/tlsstream.h index 55685c9b9..22e313fa2 100644 --- a/lib/base/tlsstream.h +++ b/lib/base/tlsstream.h @@ -28,12 +28,6 @@ namespace icinga { -enum TlsRole -{ - TlsRoleClient, - TlsRoleServer -}; - /** * A TLS stream. * @@ -44,7 +38,7 @@ class I2_BASE_API TlsStream : public Stream public: DECLARE_PTR_TYPEDEFS(TlsStream); - TlsStream(const Socket::Ptr& socket, TlsRole role, shared_ptr sslContext); + TlsStream(const Socket::Ptr& socket, ConnectionRole role, shared_ptr sslContext); shared_ptr GetClientCertificate(void) const; shared_ptr GetPeerCertificate(void) const; @@ -59,11 +53,12 @@ public: virtual bool IsEof(void) const; private: + boost::mutex m_SSLLock; shared_ptr m_SSL; BIO *m_BIO; Socket::Ptr m_Socket; - TlsRole m_Role; + ConnectionRole m_Role; static int m_SSLIndex; static bool m_SSLIndexInitialized; diff --git a/lib/base/utility.cpp b/lib/base/utility.cpp index 9901b61f5..d5d5de2ce 100644 --- a/lib/base/utility.cpp +++ b/lib/base/utility.cpp @@ -370,21 +370,9 @@ Utility::LoadExtensionLibrary(const String& library) Log(LogInformation, "base", "Loading library '" + path + "'"); - m_DeferredInitializers.reset(new std::vector >()); - #ifdef _WIN32 - HMODULE hModule; + HMODULE hModule = LoadLibrary(path.CStr()); - try { - SetLoadingLibrary(true); - hModule = LoadLibrary(path.CStr()); - } catch (...) { - SetLoadingLibrary(false); - throw; - } - - SetLoadingLibrary(false); - if (hModule == NULL) { BOOST_THROW_EXCEPTION(win32_error() << boost::errinfo_api_function("LoadLibrary") @@ -392,44 +380,35 @@ Utility::LoadExtensionLibrary(const String& library) << boost::errinfo_file_name(path)); } #else /* _WIN32 */ - void *hModule; + void *hModule = dlopen(path.CStr(), RTLD_NOW); - try { - hModule = dlopen(path.CStr(), RTLD_NOW); - } catch (...) { - SetLoadingLibrary(false); - throw; - } - - SetLoadingLibrary(false); - if (hModule == NULL) { BOOST_THROW_EXCEPTION(std::runtime_error("Could not load library '" + path + "': " + dlerror())); } #endif /* _WIN32 */ - BOOST_FOREACH(const boost::function& callback, *m_DeferredInitializers.get()) - callback(); + ExecuteDeferredInitializers(); - m_DeferredInitializers.reset(); return hModule; } -bool Utility::GetLoadingLibrary(void) +void Utility::ExecuteDeferredInitializers(void) { - bool *loading = m_LoadingLibrary.get(); - return loading && *loading; -} + if (!m_DeferredInitializers.get()) + return; -void Utility::SetLoadingLibrary(bool loading) -{ - bool *ploading = new bool(loading); - m_LoadingLibrary.reset(ploading); + BOOST_FOREACH(const boost::function& callback, *m_DeferredInitializers.get()) + callback(); + + m_DeferredInitializers.reset(); } void Utility::AddDeferredInitializer(const boost::function& callback) { + if (!m_DeferredInitializers.get()) + m_DeferredInitializers.reset(new std::vector >()); + m_DeferredInitializers.get()->push_back(callback); } @@ -536,9 +515,7 @@ bool Utility::Glob(const String& pathSpec, const boost::function& callback); + static void ExecuteDeferredInitializers(void); #ifndef _WIN32 static void SetNonBlocking(int fd); diff --git a/lib/config/aexpression.cpp b/lib/config/aexpression.cpp index 8d152188f..523c039c5 100644 --- a/lib/config/aexpression.cpp +++ b/lib/config/aexpression.cpp @@ -523,7 +523,7 @@ Value AExpression::OpObject(const AExpression* expr, const Dictionary::Ptr& loca String type = left->Get(1); AExpression::Ptr aname = left->Get(2); AExpression::Ptr filter = left->Get(3); - String package = left->Get(4); + String zone = left->Get(4); String name = aname->Evaluate(locals); @@ -561,7 +561,7 @@ Value AExpression::OpObject(const AExpression* expr, const Dictionary::Ptr& loca item->AddExpression(exprl); item->SetAbstract(abstract); item->SetScope(locals); - item->SetPackage(package); + item->SetZone(zone); item->Compile()->Register(); ObjectRule::AddRule(type, name, exprl, filter, expr->m_DebugInfo, locals); diff --git a/lib/config/base-type.conf b/lib/config/base-type.conf index 46ee8b606..29a91176b 100644 --- a/lib/config/base-type.conf +++ b/lib/config/base-type.conf @@ -24,7 +24,7 @@ %require "type", %attribute %string "type", - %attribute %string "package", + %attribute %name(Zone) "zone", %attribute %array "templates" { %attribute %string "*" @@ -35,10 +35,6 @@ %attribute %dictionary "vars" { %attribute %string "*" }, - - %attribute %array "domains" { - %attribute %string "*" - } } %type Logger { diff --git a/lib/config/config_lexer.ll b/lib/config/config_lexer.ll index c7564e46a..829cd9c2e 100644 --- a/lib/config/config_lexer.ll +++ b/lib/config/config_lexer.ll @@ -231,7 +231,7 @@ ignore return T_IGNORE; function return T_FUNCTION; lambda return T_LAMBDA; return return T_RETURN; -package return T_PACKAGE; +zone return T_ZONE; \<\< { yylval->op = &AExpression::OpShiftLeft; return T_SHIFT_LEFT; } \>\> { yylval->op = &AExpression::OpShiftRight; return T_SHIFT_RIGHT; } \<= { yylval->op = &AExpression::OpLessThanOrEqual; return T_LESS_THAN_OR_EQUAL; } diff --git a/lib/config/config_parser.yy b/lib/config/config_parser.yy index 4dd436dbb..d7e439988 100644 --- a/lib/config/config_parser.yy +++ b/lib/config/config_parser.yy @@ -160,7 +160,7 @@ static void MakeRBinaryOp(Value** result, AExpression::OpCallback& op, Value *le %token T_FUNCTION "function (T_FUNCTION)" %token T_LAMBDA "lambda (T_LAMBDA)" %token T_RETURN "return (T_RETURN)" -%token T_PACKAGE "package (T_PACKAGE)" +%token T_ZONE "zone (T_ZONE)" %type identifier %type rterm_items @@ -214,7 +214,7 @@ static std::stack m_RuleLists; static ConfigType::Ptr m_Type; static Dictionary::Ptr m_ModuleScope; -static String m_Package; +static String m_Zone; static int m_StatementNum; static bool m_Apply; @@ -227,7 +227,7 @@ void ConfigCompiler::Compile(void) { m_ModuleScope = make_shared(); - String parentPackage = m_Package; + String parentZone = m_Zone; int parentStatementNum = m_StatementNum; m_StatementNum = 0; @@ -240,7 +240,7 @@ void ConfigCompiler::Compile(void) ConfigCompilerContext::GetInstance()->AddMessage(true, DiagnosticInformation(ex)); } - m_Package = parentPackage; + m_Zone = parentZone; m_StatementNum = parentStatementNum; } @@ -253,7 +253,7 @@ statements: /* empty */ | statements statement ; -statement: type | package | include | include_recursive | library | constant +statement: type | zone | include | include_recursive | library | constant { m_StatementNum++; } @@ -269,37 +269,39 @@ statement: type | package | include | include_recursive | library | constant } ; -package: T_PACKAGE rterm sep +zone: T_ZONE rterm sep { AExpression::Ptr aexpr = *$2; delete $2; - if (!m_Package.IsEmpty()) - BOOST_THROW_EXCEPTION(std::invalid_argument("Package name cannot be changed once it's been set.")); + if (!m_Zone.IsEmpty()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Zone name cannot be changed once it's been set.")); if (m_StatementNum != 0) - BOOST_THROW_EXCEPTION(std::invalid_argument("'package' directive must be the first statement in a file.")); + BOOST_THROW_EXCEPTION(std::invalid_argument("'zone' directive must be the first statement in a file.")); - m_Package = aexpr->Evaluate(m_ModuleScope); + m_Zone = aexpr->Evaluate(m_ModuleScope); } - | T_PACKAGE rterm rterm_scope sep + | T_ZONE rterm { AExpression::Ptr aexpr = *$2; delete $2; - AExpression::Ptr ascope = *$3; - delete $3; - - if (!m_Package.IsEmpty()) - BOOST_THROW_EXCEPTION(std::invalid_argument("Package name cannot be changed once it's been set.")); + if (!m_Zone.IsEmpty()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Zone name cannot be changed once it's been set.")); - m_Package = aexpr->Evaluate(m_ModuleScope); + m_Zone = aexpr->Evaluate(m_ModuleScope); + } + rterm_scope sep + { + AExpression::Ptr ascope = *$4; + delete $4; try { ascope->Evaluate(m_ModuleScope); - m_Package = String(); + m_Zone = String(); } catch (...) { - m_Package = String(); + m_Zone = String(); } } ; @@ -511,7 +513,7 @@ object: args->Add(filter); - args->Add(m_Package); + args->Add(m_Zone); $$ = new Value(make_shared(&AExpression::OpObject, args, exprl, DebugInfoRange(@2, @5))); diff --git a/lib/config/configitem.cpp b/lib/config/configitem.cpp index e405d8dff..d5964d1b5 100644 --- a/lib/config/configitem.cpp +++ b/lib/config/configitem.cpp @@ -50,10 +50,10 @@ ConfigItem::ItemMap ConfigItem::m_Items; ConfigItem::ConfigItem(const String& type, const String& name, bool abstract, const AExpression::Ptr& exprl, const DebugInfo& debuginfo, const Dictionary::Ptr& scope, - const String& package) + const String& zone) : m_Type(type), m_Name(name), m_Abstract(abstract), m_Validated(false), m_ExpressionList(exprl), m_DebugInfo(debuginfo), - m_Scope(scope), m_Package(package) + m_Scope(scope), m_Zone(zone) { } @@ -119,6 +119,8 @@ Dictionary::Ptr ConfigItem::GetProperties(void) if (!m_Properties) { m_Properties = make_shared(); m_Properties->Set("type", m_Type); + if (!m_Zone.IsEmpty()) + m_Properties->Set("zone", m_Zone); m_Properties->Set("__parent", m_Scope); GetExpressionList()->Evaluate(m_Properties); m_Properties->Remove("__parent"); diff --git a/lib/config/configitem.h b/lib/config/configitem.h index 743d111c2..8ce6fa1ff 100644 --- a/lib/config/configitem.h +++ b/lib/config/configitem.h @@ -39,7 +39,7 @@ public: ConfigItem(const String& type, const String& name, bool abstract, const AExpression::Ptr& exprl, const DebugInfo& debuginfo, - const Dictionary::Ptr& scope, const String& package); + const Dictionary::Ptr& scope, const String& zone); String GetType(void) const; String GetName(void) const; @@ -57,7 +57,7 @@ public: Dictionary::Ptr GetScope(void) const; - String GetPackage(void) const; + String GetZone(void) const; static ConfigItem::Ptr GetObject(const String& type, const String& name); @@ -81,7 +81,7 @@ private: items. */ DebugInfo m_DebugInfo; /**< Debug information. */ Dictionary::Ptr m_Scope; /**< variable scope. */ - String m_Package; /**< The package. */ + String m_Zone; /**< The zone. */ DynamicObject::Ptr m_Object; diff --git a/lib/config/configitembuilder.cpp b/lib/config/configitembuilder.cpp index 3d2901654..e70b36dc2 100644 --- a/lib/config/configitembuilder.cpp +++ b/lib/config/configitembuilder.cpp @@ -60,9 +60,9 @@ void ConfigItemBuilder::SetScope(const Dictionary::Ptr& scope) m_Scope = scope; } -void ConfigItemBuilder::SetPackage(const String& package) +void ConfigItemBuilder::SetZone(const String& zone) { - m_Package = package; + m_Zone = zone; } void ConfigItemBuilder::AddExpression(const AExpression::Ptr& expr) @@ -104,5 +104,5 @@ ConfigItem::Ptr ConfigItemBuilder::Compile(void) AExpression::Ptr exprl = make_shared(&AExpression::OpDict, exprs, true, m_DebugInfo); return make_shared(m_Type, m_Name, m_Abstract, exprl, - m_DebugInfo, m_Scope, m_Package); + m_DebugInfo, m_Scope, m_Zone); } diff --git a/lib/config/configitembuilder.h b/lib/config/configitembuilder.h index c9609a9cd..afad84759 100644 --- a/lib/config/configitembuilder.h +++ b/lib/config/configitembuilder.h @@ -46,7 +46,7 @@ public: void SetName(const String& name); void SetAbstract(bool abstract); void SetScope(const Dictionary::Ptr& scope); - void SetPackage(const String& name); + void SetZone(const String& zone); void AddExpression(const AExpression::Ptr& expr); @@ -59,7 +59,7 @@ private: Array::Ptr m_Expressions; /**< Expressions for this item. */ DebugInfo m_DebugInfo; /**< Debug information. */ Dictionary::Ptr m_Scope; /**< variable scope. */ - String m_Package; /**< The package. */ + String m_Zone; /**< The zone. */ }; } diff --git a/lib/db_ido/dbevents.cpp b/lib/db_ido/dbevents.cpp index 92b4ef3ab..ad7da223e 100644 --- a/lib/db_ido/dbevents.cpp +++ b/lib/db_ido/dbevents.cpp @@ -50,7 +50,7 @@ void DbEvents::StaticInitialize(void) Checkable::OnAcknowledgementSet.connect(boost::bind(&DbEvents::AddAcknowledgement, _1, _4)); Checkable::OnAcknowledgementCleared.connect(boost::bind(&DbEvents::RemoveAcknowledgement, _1)); - Checkable::OnNextCheckChanged.connect(bind(&DbEvents::NextCheckChangedHandler, _1, _2, _3)); + Checkable::OnNextCheckChanged.connect(bind(&DbEvents::NextCheckChangedHandler, _1, _2)); Checkable::OnFlappingChanged.connect(bind(&DbEvents::FlappingChangedHandler, _1, _2)); Checkable::OnNotificationSentToAllUsers.connect(bind(&DbEvents::LastNotificationChangedHandler, _1, _2)); @@ -78,7 +78,7 @@ void DbEvents::StaticInitialize(void) } /* check events */ -void DbEvents::NextCheckChangedHandler(const Checkable::Ptr& checkable, double nextCheck, const String& authority) +void DbEvents::NextCheckChangedHandler(const Checkable::Ptr& checkable, double nextCheck) { Host::Ptr host; Service::Ptr service; diff --git a/lib/db_ido/dbevents.h b/lib/db_ido/dbevents.h index 05966f594..aec44f4ef 100644 --- a/lib/db_ido/dbevents.h +++ b/lib/db_ido/dbevents.h @@ -72,7 +72,7 @@ public: static void AddLogHistory(const Checkable::Ptr& checkable, String buffer, LogEntryType type); /* Status */ - static void NextCheckChangedHandler(const Checkable::Ptr& checkable, double nextCheck, const String& authority); + static void NextCheckChangedHandler(const Checkable::Ptr& checkable, double nextCheck); static void FlappingChangedHandler(const Checkable::Ptr& checkable, FlappingState state); static void LastNotificationChangedHandler(const Notification::Ptr& notification, const Checkable::Ptr& checkable); diff --git a/lib/db_ido/endpointdbobject.cpp b/lib/db_ido/endpointdbobject.cpp index b95e1d177..13615fbbd 100644 --- a/lib/db_ido/endpointdbobject.cpp +++ b/lib/db_ido/endpointdbobject.cpp @@ -39,7 +39,7 @@ INITIALIZE_ONCE(&EndpointDbObject::StaticInitialize); void EndpointDbObject::StaticInitialize(void) { Endpoint::OnConnected.connect(boost::bind(&EndpointDbObject::UpdateConnectedStatus, _1)); - Endpoint::OnDisconnected.connect(boost::bind(&EndpointDbObject::UpdateDisconnectedStatus, _1)); + Endpoint::OnDisconnected.connect(boost::bind(&EndpointDbObject::UpdateConnectedStatus, _1)); } EndpointDbObject::EndpointDbObject(const DbType::Ptr& type, const String& name1, const String& name2) @@ -73,16 +73,8 @@ Dictionary::Ptr EndpointDbObject::GetStatusFields(void) const void EndpointDbObject::UpdateConnectedStatus(const Endpoint::Ptr& endpoint) { - UpdateConnectedStatusInternal(endpoint, true); -} + bool connected = EndpointIsConnected(endpoint); -void EndpointDbObject::UpdateDisconnectedStatus(const Endpoint::Ptr& endpoint) -{ - UpdateConnectedStatusInternal(endpoint, false); -} - -void EndpointDbObject::UpdateConnectedStatusInternal(const Endpoint::Ptr& endpoint, bool connected) -{ Log(LogDebug, "db_ido", "update is_connected=" + Convert::ToString(connected ? 1 : 0) + " for endpoint '" + endpoint->GetName() + "'"); DbQuery query1; diff --git a/lib/db_ido/endpointdbobject.h b/lib/db_ido/endpointdbobject.h index 7a69c548b..e2cf45e48 100644 --- a/lib/db_ido/endpointdbobject.h +++ b/lib/db_ido/endpointdbobject.h @@ -49,8 +49,6 @@ protected: private: static void UpdateConnectedStatus(const Endpoint::Ptr& endpoint); - static void UpdateDisconnectedStatus(const Endpoint::Ptr& endpoint); - static void UpdateConnectedStatusInternal(const Endpoint::Ptr& endpoint, bool connected); static int EndpointIsConnected(const Endpoint::Ptr& endpoint); }; diff --git a/lib/icinga/CMakeLists.txt b/lib/icinga/CMakeLists.txt index f18e91257..ba61fa49b 100644 --- a/lib/icinga/CMakeLists.txt +++ b/lib/icinga/CMakeLists.txt @@ -21,7 +21,6 @@ mkclass_target(checkresult.ti checkresult.th) mkclass_target(command.ti command.th) mkclass_target(comment.ti comment.th) mkclass_target(dependency.ti dependency.th) -mkclass_target(domain.ti domain.th) mkclass_target(downtime.ti downtime.th) mkclass_target(eventcommand.ti eventcommand.th) mkclass_target(hostgroup.ti hostgroup.th) @@ -41,10 +40,10 @@ mkclass_target(user.ti user.th) mkembedconfig_target(icinga-type.conf icinga-type.cpp) add_library(icinga SHARED - api.cpp checkable.cpp checkable.th checkable-dependency.cpp checkable-downtime.cpp checkable-event.cpp + api.cpp apievents.cpp checkable.cpp checkable.th checkable-dependency.cpp checkable-downtime.cpp checkable-event.cpp checkable-flapping.cpp checkcommand.cpp checkcommand.th checkresult.cpp checkresult.th cib.cpp command.cpp command.th comment.cpp comment.th compatutility.cpp dependency.cpp dependency.th - dependency-apply.cpp domain.cpp domain.th downtime.cpp downtime.th eventcommand.cpp eventcommand.th + dependency-apply.cpp downtime.cpp downtime.th eventcommand.cpp eventcommand.th externalcommandprocessor.cpp host.cpp host.th hostgroup.cpp hostgroup.th icingaapplication.cpp icingaapplication.th icingastatuswriter.cpp icingastatuswriter.th legacytimeperiod.cpp macroprocessor.cpp notificationcommand.cpp notificationcommand.th notification.cpp notification.th notification-apply.cpp @@ -54,7 +53,7 @@ add_library(icinga SHARED user.cpp user.th usergroup.cpp usergroup.th icinga-type.cpp ) -target_link_libraries(icinga ${Boost_LIBRARIES} base config) +target_link_libraries(icinga ${Boost_LIBRARIES} base config remote) set_target_properties ( icinga PROPERTIES diff --git a/lib/icinga/api.cpp b/lib/icinga/api.cpp index 723e33a5f..57eb8656e 100644 --- a/lib/icinga/api.cpp +++ b/lib/icinga/api.cpp @@ -18,15 +18,20 @@ ******************************************************************************/ #include "icinga/api.h" -#include "base/scriptfunction.h" +#include "remote/apifunction.h" #include "base/logger_fwd.h" using namespace icinga; -REGISTER_SCRIPTFUNCTION(GetAnswerToEverything, &API::GetAnswerToEverything); +REGISTER_APIFUNCTION(GetAnswerToEverything, uapi, boost::bind(&API::GetAnswerToEverything, _2)); -int API::GetAnswerToEverything(const String& text) +Value API::GetAnswerToEverything(const Dictionary::Ptr& params) { + String text; + + if (params) + text = params->Get("text"); + Log(LogInformation, "icinga", "Hello from the Icinga 2 API: " + text); return 42; diff --git a/lib/icinga/api.h b/lib/icinga/api.h index b163f5502..e5c79e28d 100644 --- a/lib/icinga/api.h +++ b/lib/icinga/api.h @@ -21,6 +21,7 @@ #define API_H #include "icinga/i2-icinga.h" +#include "remote/apiclient.h" #include "base/value.h" #include @@ -28,14 +29,12 @@ namespace icinga { /** - * A state change message for a service. - * * @ingroup icinga */ class I2_ICINGA_API API { public: - static int GetAnswerToEverything(const String& text); + static Value GetAnswerToEverything(const Dictionary::Ptr& params); private: API(void); diff --git a/lib/icinga/apievents.cpp b/lib/icinga/apievents.cpp new file mode 100644 index 000000000..3063193ab --- /dev/null +++ b/lib/icinga/apievents.cpp @@ -0,0 +1,967 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * + * * + * 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 2 * + * 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, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "icinga/apievents.h" +#include "icinga/service.h" +#include "remote/apilistener.h" +#include "remote/apiclient.h" +#include "remote/apifunction.h" +#include "base/application.h" +#include "base/dynamictype.h" +#include "base/objectlock.h" +#include "base/utility.h" +#include "base/logger_fwd.h" +#include "base/exception.h" +#include "base/initialize.h" +#include + +using namespace icinga; + +INITIALIZE_ONCE(&ApiEvents::StaticInitialize); + +REGISTER_APIFUNCTION(CheckResult, event, &ApiEvents::CheckResultAPIHandler); +REGISTER_APIFUNCTION(SetNextCheck, event, &ApiEvents::NextCheckChangedAPIHandler); +REGISTER_APIFUNCTION(SetNextNotification, event, &ApiEvents::NextNotificationChangedAPIHandler); +REGISTER_APIFUNCTION(SetForceNextCheck, event, &ApiEvents::ForceNextCheckChangedAPIHandler); +REGISTER_APIFUNCTION(SetForceNextNotification, event, &ApiEvents::ForceNextNotificationChangedAPIHandler); +REGISTER_APIFUNCTION(SetEnableActiveChecks, event, &ApiEvents::EnableActiveChecksChangedAPIHandler); +REGISTER_APIFUNCTION(SetEnablePassiveChecks, event, &ApiEvents::EnablePassiveChecksChangedAPIHandler); +REGISTER_APIFUNCTION(SetEnableNotifications, event, &ApiEvents::EnableNotificationsChangedAPIHandler); +REGISTER_APIFUNCTION(SetEnableFlapping, event, &ApiEvents::EnableFlappingChangedAPIHandler); +REGISTER_APIFUNCTION(AddComment, event, &ApiEvents::CommentAddedAPIHandler); +REGISTER_APIFUNCTION(RemoveComment, event, &ApiEvents::CommentRemovedAPIHandler); +REGISTER_APIFUNCTION(AddDowntime, event, &ApiEvents::DowntimeAddedAPIHandler); +REGISTER_APIFUNCTION(RemoveDowntime, event, &ApiEvents::DowntimeRemovedAPIHandler); +REGISTER_APIFUNCTION(SetAcknowledgement, event, &ApiEvents::AcknowledgementSetAPIHandler); +REGISTER_APIFUNCTION(ClearAcknowledgement, event, &ApiEvents::AcknowledgementClearedAPIHandler); +REGISTER_APIFUNCTION(UpdateRepository, event, &ApiEvents::UpdateRepositoryAPIHandler); + +static Timer::Ptr l_RepositoryTimer; + +void ApiEvents::StaticInitialize(void) +{ + Checkable::OnNewCheckResult.connect(&ApiEvents::CheckResultHandler); + Checkable::OnNextCheckChanged.connect(&ApiEvents::NextCheckChangedHandler); + Notification::OnNextNotificationChanged.connect(&ApiEvents::NextNotificationChangedHandler); + Checkable::OnForceNextCheckChanged.connect(&ApiEvents::ForceNextCheckChangedHandler); + Checkable::OnForceNextNotificationChanged.connect(&ApiEvents::ForceNextNotificationChangedHandler); + Checkable::OnEnableActiveChecksChanged.connect(&ApiEvents::EnableActiveChecksChangedHandler); + Checkable::OnEnablePassiveChecksChanged.connect(&ApiEvents::EnablePassiveChecksChangedHandler); + Checkable::OnEnableNotificationsChanged.connect(&ApiEvents::EnableNotificationsChangedHandler); + Checkable::OnEnableFlappingChanged.connect(&ApiEvents::EnableFlappingChangedHandler); + Checkable::OnCommentAdded.connect(&ApiEvents::CommentAddedHandler); + Checkable::OnCommentRemoved.connect(&ApiEvents::CommentRemovedHandler); + Checkable::OnDowntimeAdded.connect(&ApiEvents::DowntimeAddedHandler); + Checkable::OnDowntimeRemoved.connect(&ApiEvents::DowntimeRemovedHandler); + Checkable::OnAcknowledgementSet.connect(&ApiEvents::AcknowledgementSetHandler); + Checkable::OnAcknowledgementCleared.connect(&ApiEvents::AcknowledgementClearedHandler); + + l_RepositoryTimer = make_shared(); + l_RepositoryTimer->SetInterval(60); + l_RepositoryTimer->OnTimerExpired.connect(boost::bind(&ApiEvents::RepositoryTimerHandler)); + l_RepositoryTimer->Start(); + l_RepositoryTimer->Reschedule(0); +} + +void ApiEvents::CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, const MessageOrigin& origin) +{ + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return; + + Dictionary::Ptr message = make_shared(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::CheckResult"); + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr params = make_shared(); + params->Set("host", host->GetName()); + if (service) + params->Set("service", service->GetShortName()); + params->Set("cr", Serialize(cr)); + + message->Set("params", params); + + listener->RelayMessage(origin, checkable, message, true); +} + +Value ApiEvents::CheckResultAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) +{ + if (!params) + return Empty; + + Value crv = params->Get("cr"); + + CheckResult::Ptr cr = Deserialize(crv, true); + + Host::Ptr host = Host::GetByName(params->Get("host")); + + if (!host) + return Empty; + + Checkable::Ptr checkable; + + if (params->Contains("service")) + checkable = host->GetServiceByShortName(params->Get("service")); + else + checkable = host; + + if (!checkable) + return Empty; + + if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + return Empty; + + checkable->ProcessCheckResult(cr, origin); + + return Empty; +} + +void ApiEvents::NextCheckChangedHandler(const Checkable::Ptr& checkable, double nextCheck, const MessageOrigin& origin) +{ + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return; + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr params = make_shared(); + params->Set("host", host->GetName()); + if (service) + params->Set("service", service->GetShortName()); + params->Set("next_check", nextCheck); + + Dictionary::Ptr message = make_shared(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::SetNextCheck"); + message->Set("params", params); + + listener->RelayMessage(origin, checkable, message, true); +} + +Value ApiEvents::NextCheckChangedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) +{ + if (!params) + return Empty; + + Host::Ptr host = Host::GetByName(params->Get("host")); + + if (!host) + return Empty; + + Checkable::Ptr checkable; + + if (params->Contains("service")) + checkable = host->GetServiceByShortName(params->Get("service")); + else + checkable = host; + + if (!checkable) + return Empty; + + if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + return Empty; + + checkable->SetNextCheck(params->Get("next_check"), origin); + + return Empty; +} + +void ApiEvents::NextNotificationChangedHandler(const Notification::Ptr& notification, double nextNotification, const MessageOrigin& origin) +{ + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return; + + Dictionary::Ptr params = make_shared(); + params->Set("notification", notification->GetName()); + params->Set("next_notification", nextNotification); + + Dictionary::Ptr message = make_shared(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::SetNextNotification"); + message->Set("params", params); + + listener->RelayMessage(origin, notification, message, true); +} + +Value ApiEvents::NextNotificationChangedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) +{ + if (!params) + return Empty; + + Notification::Ptr notification = Notification::GetByName(params->Get("notification")); + + if (!notification) + return Empty; + + if (origin.FromZone && !origin.FromZone->CanAccessObject(notification)) + return Empty; + + notification->SetNextNotification(params->Get("next_notification"), origin); + + return Empty; +} + +void ApiEvents::ForceNextCheckChangedHandler(const Checkable::Ptr& checkable, bool forced, const MessageOrigin& origin) +{ + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return; + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr params = make_shared(); + params->Set("host", host->GetName()); + if (service) + params->Set("service", service->GetShortName()); + params->Set("forced", forced); + + Dictionary::Ptr message = make_shared(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::SetForceNextCheck"); + message->Set("params", params); + + listener->RelayMessage(origin, checkable, message, true); +} + +Value ApiEvents::ForceNextCheckChangedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) +{ + if (!params) + return Empty; + + Host::Ptr host = Host::GetByName(params->Get("host")); + + if (!host) + return Empty; + + Checkable::Ptr checkable; + + if (params->Contains("service")) + checkable = host->GetServiceByShortName(params->Get("service")); + else + checkable = host; + + if (!checkable) + return Empty; + + if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + return Empty; + + checkable->SetForceNextCheck(params->Get("forced"), origin); + + return Empty; +} + +void ApiEvents::ForceNextNotificationChangedHandler(const Checkable::Ptr& checkable, bool forced, const MessageOrigin& origin) +{ + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return; + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr params = make_shared(); + params->Set("host", host->GetName()); + if (service) + params->Set("service", service->GetShortName()); + params->Set("forced", forced); + + Dictionary::Ptr message = make_shared(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::SetForceNextNotification"); + message->Set("params", params); + + listener->RelayMessage(origin, checkable, message, true); +} + +Value ApiEvents::ForceNextNotificationChangedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) +{ + if (!params) + return Empty; + + Host::Ptr host = Host::GetByName(params->Get("host")); + + if (!host) + return Empty; + + Checkable::Ptr checkable; + + if (params->Contains("service")) + checkable = host->GetServiceByShortName(params->Get("service")); + else + checkable = host; + + if (!checkable) + return Empty; + + if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + return Empty; + + checkable->SetForceNextNotification(params->Get("forced"), origin); + + return Empty; +} + +void ApiEvents::EnableActiveChecksChangedHandler(const Checkable::Ptr& checkable, bool enabled, const MessageOrigin& origin) +{ + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return; + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr params = make_shared(); + params->Set("host", host->GetName()); + if (service) + params->Set("service", service->GetShortName()); + params->Set("enabled", enabled); + + Dictionary::Ptr message = make_shared(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::SetEnableActiveChecks"); + message->Set("params", params); + + listener->RelayMessage(origin, checkable, message, true); +} + +Value ApiEvents::EnableActiveChecksChangedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) +{ + if (!params) + return Empty; + + Host::Ptr host = Host::GetByName(params->Get("host")); + + if (!host) + return Empty; + + Checkable::Ptr checkable; + + if (params->Contains("service")) + checkable = host->GetServiceByShortName(params->Get("service")); + else + checkable = host; + + if (!checkable) + return Empty; + + if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + return Empty; + + checkable->SetEnableActiveChecks(params->Get("enabled"), origin); + + return Empty; +} + +void ApiEvents::EnablePassiveChecksChangedHandler(const Checkable::Ptr& checkable, bool enabled, const MessageOrigin& origin) +{ + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return; + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr params = make_shared(); + params->Set("host", host->GetName()); + if (service) + params->Set("service", service->GetShortName()); + params->Set("enabled", enabled); + + Dictionary::Ptr message = make_shared(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::SetEnablePassiveChecks"); + message->Set("params", params); + + listener->RelayMessage(origin, checkable, message, true); +} + +Value ApiEvents::EnablePassiveChecksChangedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) +{ + if (!params) + return Empty; + + Host::Ptr host = Host::GetByName(params->Get("host")); + + if (!host) + return Empty; + + Checkable::Ptr checkable; + + if (params->Contains("service")) + checkable = host->GetServiceByShortName(params->Get("service")); + else + checkable = host; + + if (!checkable) + return Empty; + + if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + return Empty; + + checkable->SetEnablePassiveChecks(params->Get("enabled"), origin); + + return Empty; +} + +void ApiEvents::EnableNotificationsChangedHandler(const Checkable::Ptr& checkable, bool enabled, const MessageOrigin& origin) +{ + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return; + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr params = make_shared(); + params->Set("host", host->GetName()); + if (service) + params->Set("service", service->GetShortName()); + params->Set("enabled", enabled); + + Dictionary::Ptr message = make_shared(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::SetEnableNotifications"); + message->Set("params", params); + + listener->RelayMessage(origin, checkable, message, true); +} + +Value ApiEvents::EnableNotificationsChangedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) +{ + if (!params) + return Empty; + + Host::Ptr host = Host::GetByName(params->Get("host")); + + if (!host) + return Empty; + + Checkable::Ptr checkable; + + if (params->Contains("service")) + checkable = host->GetServiceByShortName(params->Get("service")); + else + checkable = host; + + if (!checkable) + return Empty; + + if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + return Empty; + + checkable->SetEnableNotifications(params->Get("enabled"), origin); + + return Empty; +} + +void ApiEvents::EnableFlappingChangedHandler(const Checkable::Ptr& checkable, bool enabled, const MessageOrigin& origin) +{ + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return; + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr params = make_shared(); + params->Set("host", host->GetName()); + if (service) + params->Set("service", service->GetShortName()); + params->Set("enabled", enabled); + + Dictionary::Ptr message = make_shared(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::SetEnableFlapping"); + message->Set("params", params); + + listener->RelayMessage(origin, checkable, message, true); +} + +Value ApiEvents::EnableFlappingChangedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) +{ + if (!params) + return Empty; + + Host::Ptr host = Host::GetByName(params->Get("host")); + + if (!host) + return Empty; + + Checkable::Ptr checkable; + + if (params->Contains("service")) + checkable = host->GetServiceByShortName(params->Get("service")); + else + checkable = host; + + if (!checkable) + return Empty; + + if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + return Empty; + + checkable->SetEnableFlapping(params->Get("enabled"), origin); + + return Empty; +} + +void ApiEvents::CommentAddedHandler(const Checkable::Ptr& checkable, const Comment::Ptr& comment, const MessageOrigin& origin) +{ + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return; + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr params = make_shared(); + params->Set("host", host->GetName()); + if (service) + params->Set("service", service->GetShortName()); + params->Set("comment", Serialize(comment)); + + Dictionary::Ptr message = make_shared(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::AddComment"); + message->Set("params", params); + + listener->RelayMessage(origin, checkable, message, true); +} + +Value ApiEvents::CommentAddedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) +{ + if (!params) + return Empty; + + Host::Ptr host = Host::GetByName(params->Get("host")); + + if (!host) + return Empty; + + Checkable::Ptr checkable; + + if (params->Contains("service")) + checkable = host->GetServiceByShortName(params->Get("service")); + else + checkable = host; + + if (!checkable) + return Empty; + + if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + return Empty; + + Comment::Ptr comment = Deserialize(params->Get("comment"), true); + + checkable->AddComment(comment->GetEntryType(), comment->GetAuthor(), + comment->GetText(), comment->GetExpireTime(), comment->GetId(), origin); + + return Empty; +} + +void ApiEvents::CommentRemovedHandler(const Checkable::Ptr& checkable, const Comment::Ptr& comment, const MessageOrigin& origin) +{ + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return; + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr params = make_shared(); + params->Set("host", host->GetName()); + if (service) + params->Set("service", service->GetShortName()); + params->Set("id", comment->GetId()); + + Dictionary::Ptr message = make_shared(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::RemoveComment"); + message->Set("params", params); + + listener->RelayMessage(origin, checkable, message, true); +} + +Value ApiEvents::CommentRemovedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) +{ + if (!params) + return Empty; + + Host::Ptr host = Host::GetByName(params->Get("host")); + + if (!host) + return Empty; + + Checkable::Ptr checkable; + + if (params->Contains("service")) + checkable = host->GetServiceByShortName(params->Get("service")); + else + checkable = host; + + if (!checkable) + return Empty; + + if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + return Empty; + + checkable->RemoveComment(params->Get("id"), origin); + + return Empty; +} + +void ApiEvents::DowntimeAddedHandler(const Checkable::Ptr& checkable, const Downtime::Ptr& downtime, const MessageOrigin& origin) +{ + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return; + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr params = make_shared(); + params->Set("host", host->GetName()); + if (service) + params->Set("service", service->GetShortName()); + params->Set("downtime", Serialize(downtime)); + + Dictionary::Ptr message = make_shared(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::AddDowntime"); + message->Set("params", params); + + listener->RelayMessage(origin, checkable, message, true); +} + +Value ApiEvents::DowntimeAddedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) +{ + if (!params) + return Empty; + + Host::Ptr host = Host::GetByName(params->Get("host")); + + if (!host) + return Empty; + + Checkable::Ptr checkable; + + if (params->Contains("service")) + checkable = host->GetServiceByShortName(params->Get("service")); + else + checkable = host; + + if (!checkable) + return Empty; + + if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + return Empty; + + Downtime::Ptr downtime = Deserialize(params->Get("downtime"), true); + + checkable->AddDowntime(downtime->GetAuthor(), downtime->GetComment(), + downtime->GetStartTime(), downtime->GetEndTime(), + downtime->GetFixed(), downtime->GetTriggeredBy(), + downtime->GetDuration(), downtime->GetScheduledBy(), + downtime->GetId(), origin); + + return Empty; +} + +void ApiEvents::DowntimeRemovedHandler(const Checkable::Ptr& checkable, const Downtime::Ptr& downtime, const MessageOrigin& origin) +{ + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return; + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr params = make_shared(); + params->Set("host", host->GetName()); + if (service) + params->Set("service", service->GetShortName()); + params->Set("id", downtime->GetId()); + + Dictionary::Ptr message = make_shared(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::RemoveDowntime"); + message->Set("params", params); + + listener->RelayMessage(origin, checkable, message, true); +} + +Value ApiEvents::DowntimeRemovedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) +{ + if (!params) + return Empty; + + Host::Ptr host = Host::GetByName(params->Get("host")); + + if (!host) + return Empty; + + Checkable::Ptr checkable; + + if (params->Contains("service")) + checkable = host->GetServiceByShortName(params->Get("service")); + else + checkable = host; + + if (!checkable) + return Empty; + + if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + return Empty; + + checkable->RemoveDowntime(params->Get("id"), false, origin); + + return Empty; +} + +void ApiEvents::AcknowledgementSetHandler(const Checkable::Ptr& checkable, + const String& author, const String& comment, AcknowledgementType type, + double expiry, const MessageOrigin& origin) +{ + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return; + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr params = make_shared(); + params->Set("host", host->GetName()); + if (service) + params->Set("service", service->GetShortName()); + params->Set("author", author); + params->Set("comment", comment); + params->Set("acktype", type); + params->Set("expiry", expiry); + + Dictionary::Ptr message = make_shared(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::SetAcknowledgement"); + message->Set("params", params); + + listener->RelayMessage(origin, checkable, message, true); +} + +Value ApiEvents::AcknowledgementSetAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) +{ + if (!params) + return Empty; + + Host::Ptr host = Host::GetByName(params->Get("host")); + + if (!host) + return Empty; + + Checkable::Ptr checkable; + + if (params->Contains("service")) + checkable = host->GetServiceByShortName(params->Get("service")); + else + checkable = host; + + if (!checkable) + return Empty; + + if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + return Empty; + + checkable->AcknowledgeProblem(params->Get("author"), params->Get("comment"), + static_cast(static_cast(params->Get("acktype"))), + params->Get("expiry"), origin); + + return Empty; +} + +void ApiEvents::AcknowledgementClearedHandler(const Checkable::Ptr& checkable, const MessageOrigin& origin) +{ + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return; + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr params = make_shared(); + params->Set("host", host->GetName()); + if (service) + params->Set("service", service->GetShortName()); + + Dictionary::Ptr message = make_shared(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::ClearAcknowledgement"); + message->Set("params", params); + + listener->RelayMessage(origin, checkable, message, true); +} + +Value ApiEvents::AcknowledgementClearedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) +{ + if (!params) + return Empty; + + Host::Ptr host = Host::GetByName(params->Get("host")); + + if (!host) + return Empty; + + Checkable::Ptr checkable; + + if (params->Contains("service")) + checkable = host->GetServiceByShortName(params->Get("service")); + else + checkable = host; + + if (!checkable) + return Empty; + + if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + return Empty; + + checkable->ClearAcknowledgement(origin); + + return Empty; +} + +void ApiEvents::RepositoryTimerHandler(void) +{ + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return; + + Dictionary::Ptr repository = make_shared(); + + BOOST_FOREACH(const Host::Ptr& host, DynamicType::GetObjects()) { + Array::Ptr services = make_shared(); + + BOOST_FOREACH(const Service::Ptr& service, host->GetServices()) { + services->Add(service->GetName()); + } + + repository->Set(host->GetName(), services); + } + + Endpoint::Ptr my_endpoint = Endpoint::GetLocalEndpoint(); + Zone::Ptr my_zone = my_endpoint->GetZone(); + + Dictionary::Ptr params = make_shared(); + params->Set("endpoint", my_endpoint->GetName()); + + Zone::Ptr parent_zone = my_zone->GetParent(); + if (parent_zone) + params->Set("parent_zone", parent_zone->GetName()); + + params->Set("zone", my_zone->GetName()); + params->Set("repository", repository); + + Dictionary::Ptr message = make_shared(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::UpdateRepository"); + message->Set("params", params); + + listener->RelayMessage(MessageOrigin(), my_zone, message, true); +} + +String ApiEvents::GetRepositoryDir(void) +{ + return Application::GetLocalStateDir() + "/lib/icinga2/api/repository/"; +} + +Value ApiEvents::UpdateRepositoryAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) +{ + if (!params) + return Empty; + + Zone::Ptr zone = Zone::GetByName(params->Get("zone")); + + if (!zone || (origin.FromZone && !origin.FromZone->CanAccessObject(zone))) + return Empty; + + String repositoryFile = GetRepositoryDir() + SHA256(params->Get("endpoint")); + String repositoryTempFile = repositoryFile + ".tmp"; + + std::ofstream fp(repositoryTempFile.CStr(), std::ofstream::out | std::ostream::trunc); + fp << JsonSerialize(params); + fp.close(); + +#ifdef _WIN32 + _unlink(inventoryFile.CStr()); +#endif /* _WIN32 */ + + if (rename(repositoryTempFile.CStr(), repositoryFile.CStr()) < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("rename") + << boost::errinfo_errno(errno) + << boost::errinfo_file_name(repositoryTempFile)); + } + + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return Empty; + + Dictionary::Ptr message = make_shared(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::UpdateRepository"); + message->Set("params", params); + + listener->RelayMessage(origin, Zone::GetLocalZone(), message, true); + + return Empty; +} diff --git a/lib/icinga/apievents.h b/lib/icinga/apievents.h new file mode 100644 index 000000000..584cc30e5 --- /dev/null +++ b/lib/icinga/apievents.h @@ -0,0 +1,93 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * + * * + * 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 2 * + * 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, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#ifndef APIEVENTS_H +#define APIEVENTS_H + +#include "icinga/checkable.h" +#include "remote/apiclient.h" +#include "base/stream.h" +#include "base/timer.h" +#include "base/array.h" +#include + +namespace icinga +{ + +/** + * @ingroup icinga + */ +class I2_ICINGA_API ApiEvents +{ +public: + static void StaticInitialize(void); + + static void CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, const MessageOrigin& origin); + static Value CheckResultAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); + + static void NextCheckChangedHandler(const Checkable::Ptr& checkable, double nextCheck, const MessageOrigin& origin); + static Value NextCheckChangedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); + + static void NextNotificationChangedHandler(const Notification::Ptr& notification, double nextCheck, const MessageOrigin& origin); + static Value NextNotificationChangedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); + + static void ForceNextCheckChangedHandler(const Checkable::Ptr& checkable, bool forced, const MessageOrigin& origin); + static Value ForceNextCheckChangedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); + + static void ForceNextNotificationChangedHandler(const Checkable::Ptr& checkable, bool forced, const MessageOrigin& origin); + static Value ForceNextNotificationChangedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); + + static void EnableActiveChecksChangedHandler(const Checkable::Ptr& checkable, bool enabled, const MessageOrigin& origin); + static Value EnableActiveChecksChangedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); + + static void EnablePassiveChecksChangedHandler(const Checkable::Ptr& checkable, bool enabled, const MessageOrigin& origin); + static Value EnablePassiveChecksChangedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); + + static void EnableNotificationsChangedHandler(const Checkable::Ptr& checkable, bool enabled, const MessageOrigin& origin); + static Value EnableNotificationsChangedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); + + static void EnableFlappingChangedHandler(const Checkable::Ptr& checkable, bool enabled, const MessageOrigin& origin); + static Value EnableFlappingChangedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); + + static void CommentAddedHandler(const Checkable::Ptr& checkable, const Comment::Ptr& comment, const MessageOrigin& origin); + static Value CommentAddedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); + + static void CommentRemovedHandler(const Checkable::Ptr& checkable, const Comment::Ptr& comment, const MessageOrigin& origin); + static Value CommentRemovedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); + + static void DowntimeAddedHandler(const Checkable::Ptr& checkable, const Downtime::Ptr& downtime, const MessageOrigin& origin); + static Value DowntimeAddedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); + + static void DowntimeRemovedHandler(const Checkable::Ptr& checkable, const Downtime::Ptr& downtime, const MessageOrigin& origin); + static Value DowntimeRemovedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); + + static void AcknowledgementSetHandler(const Checkable::Ptr& checkable, const String& author, const String& comment, AcknowledgementType type, double expiry, const MessageOrigin& origin); + static Value AcknowledgementSetAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); + + static void AcknowledgementClearedHandler(const Checkable::Ptr& checkable, const MessageOrigin& origin); + static Value AcknowledgementClearedAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); + + static String GetRepositoryDir(void); + static void RepositoryTimerHandler(void); + static Value UpdateRepositoryAPIHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); +}; + +} + +#endif /* APIEVENTS_H */ diff --git a/lib/icinga/checkable-check.cpp b/lib/icinga/checkable-check.cpp index 3194b3e56..31e2ca177 100644 --- a/lib/icinga/checkable-check.cpp +++ b/lib/icinga/checkable-check.cpp @@ -21,6 +21,7 @@ #include "icinga/checkcommand.h" #include "icinga/icingaapplication.h" #include "icinga/cib.h" +#include "remote/apilistener.h" #include "base/dynamictype.h" #include "base/objectlock.h" #include "base/logger_fwd.h" @@ -33,16 +34,16 @@ using namespace icinga; -boost::signals2::signal Checkable::OnNewCheckResult; -boost::signals2::signal Checkable::OnStateChange; +boost::signals2::signal Checkable::OnNewCheckResult; +boost::signals2::signal Checkable::OnStateChange; boost::signals2::signal Checkable::OnNotificationsRequested; -boost::signals2::signal Checkable::OnNextCheckChanged; -boost::signals2::signal Checkable::OnForceNextCheckChanged; -boost::signals2::signal Checkable::OnForceNextNotificationChanged; -boost::signals2::signal Checkable::OnEnableActiveChecksChanged; -boost::signals2::signal Checkable::OnEnablePassiveChecksChanged; -boost::signals2::signal Checkable::OnEnableNotificationsChanged; -boost::signals2::signal Checkable::OnEnableFlappingChanged; +boost::signals2::signal Checkable::OnNextCheckChanged; +boost::signals2::signal Checkable::OnForceNextCheckChanged; +boost::signals2::signal Checkable::OnForceNextNotificationChanged; +boost::signals2::signal Checkable::OnEnableActiveChecksChanged; +boost::signals2::signal Checkable::OnEnablePassiveChecksChanged; +boost::signals2::signal Checkable::OnEnableNotificationsChanged; +boost::signals2::signal Checkable::OnEnableFlappingChanged; boost::signals2::signal Checkable::OnFlappingChanged; CheckCommand::Ptr Checkable::GetCheckCommand(void) const @@ -115,11 +116,11 @@ long Checkable::GetSchedulingOffset(void) return m_SchedulingOffset; } -void Checkable::SetNextCheck(double nextCheck, const String& authority) +void Checkable::SetNextCheck(double nextCheck, const MessageOrigin& origin) { SetNextCheckRaw(nextCheck); - OnNextCheckChanged(GetSelf(), nextCheck, authority); + OnNextCheckChanged(GetSelf(), nextCheck, origin); } double Checkable::GetNextCheck(void) @@ -171,11 +172,11 @@ bool Checkable::GetEnableActiveChecks(void) const return GetEnableActiveChecksRaw(); } -void Checkable::SetEnableActiveChecks(bool enabled, const String& authority) +void Checkable::SetEnableActiveChecks(bool enabled, const MessageOrigin& origin) { SetOverrideEnableActiveChecks(enabled); - OnEnableActiveChecksChanged(GetSelf(), enabled, authority); + OnEnableActiveChecksChanged(GetSelf(), enabled, origin); } bool Checkable::GetEnablePassiveChecks(void) const @@ -186,11 +187,11 @@ bool Checkable::GetEnablePassiveChecks(void) const return GetEnablePassiveChecksRaw(); } -void Checkable::SetEnablePassiveChecks(bool enabled, const String& authority) +void Checkable::SetEnablePassiveChecks(bool enabled, const MessageOrigin& origin) { SetOverrideEnablePassiveChecks(enabled); - OnEnablePassiveChecksChanged(GetSelf(), enabled, authority); + OnEnablePassiveChecksChanged(GetSelf(), enabled, origin); } bool Checkable::GetForceNextCheck(void) const @@ -198,11 +199,11 @@ bool Checkable::GetForceNextCheck(void) const return GetForceNextCheckRaw(); } -void Checkable::SetForceNextCheck(bool forced, const String& authority) +void Checkable::SetForceNextCheck(bool forced, const MessageOrigin& origin) { SetForceNextCheckRaw(forced); - OnForceNextCheckChanged(GetSelf(), forced, authority); + OnForceNextCheckChanged(GetSelf(), forced, origin); } int Checkable::GetMaxCheckAttempts(void) const @@ -218,7 +219,7 @@ void Checkable::SetMaxCheckAttempts(int attempts) SetOverrideMaxCheckAttempts(attempts); } -void Checkable::ProcessCheckResult(const CheckResult::Ptr& cr, const String& authority) +void Checkable::ProcessCheckResult(const CheckResult::Ptr& cr, const MessageOrigin& origin) { { ObjectLock olock(this); @@ -239,7 +240,7 @@ void Checkable::ProcessCheckResult(const CheckResult::Ptr& cr, const String& aut if (cr->GetExecutionEnd() == 0) cr->SetExecutionEnd(now); - if (authority.IsEmpty()) + if (origin.IsLocal()) cr->SetCheckSource(IcingaApplication::GetInstance()->GetNodeName()); bool reachable = IsReachable(); @@ -399,15 +400,15 @@ void Checkable::ProcessCheckResult(const CheckResult::Ptr& cr, const String& aut // " threshold: " + Convert::ToString(GetFlappingThreshold()) + // "% current: " + Convert::ToString(GetFlappingCurrent()) + "%."); - OnNewCheckResult(GetSelf(), cr, authority); + OnNewCheckResult(GetSelf(), cr, origin); /* signal status updates to for example db_ido */ - OnStateChanged(GetSelf(), authority); + OnStateChanged(GetSelf()); if (hardChange) - OnStateChange(GetSelf(), cr, StateTypeHard, authority); + OnStateChange(GetSelf(), cr, StateTypeHard, origin); else if (stateChange) - OnStateChange(GetSelf(), cr, StateTypeSoft, authority); + OnStateChange(GetSelf(), cr, StateTypeSoft, origin); if (GetStateType() == StateTypeSoft || hardChange || recovery) ExecuteEventHandler(); diff --git a/lib/icinga/checkable-comment.cpp b/lib/icinga/checkable-comment.cpp index e6e28b81e..7d4bb4fe3 100644 --- a/lib/icinga/checkable-comment.cpp +++ b/lib/icinga/checkable-comment.cpp @@ -33,8 +33,8 @@ static std::map l_LegacyCommentsCache; static std::map l_CommentsCache; static Timer::Ptr l_CommentsExpireTimer; -boost::signals2::signal Checkable::OnCommentAdded; -boost::signals2::signal Checkable::OnCommentRemoved; +boost::signals2::signal Checkable::OnCommentAdded; +boost::signals2::signal Checkable::OnCommentRemoved; int Checkable::GetNextCommentID(void) { @@ -44,7 +44,7 @@ int Checkable::GetNextCommentID(void) } String Checkable::AddComment(CommentType entryType, const String& author, - const String& text, double expireTime, const String& id, const String& authority) + const String& text, double expireTime, const String& id, const MessageOrigin& origin) { String uid; @@ -78,7 +78,7 @@ String Checkable::AddComment(CommentType entryType, const String& author, l_CommentsCache[uid] = GetSelf(); } - OnCommentAdded(GetSelf(), comment, authority); + OnCommentAdded(GetSelf(), comment, origin); return uid; } @@ -100,7 +100,7 @@ void Checkable::RemoveAllComments(void) } } -void Checkable::RemoveComment(const String& id, const String& authority) +void Checkable::RemoveComment(const String& id, const MessageOrigin& origin) { Checkable::Ptr owner = GetOwnerByCommentID(id); @@ -126,7 +126,7 @@ void Checkable::RemoveComment(const String& id, const String& authority) l_CommentsCache.erase(id); } - OnCommentRemoved(owner, comment, authority); + OnCommentRemoved(owner, comment, origin); } String Checkable::GetCommentIDFromLegacyID(int id) diff --git a/lib/icinga/checkable-downtime.cpp b/lib/icinga/checkable-downtime.cpp index 2fea94069..62fc77d47 100644 --- a/lib/icinga/checkable-downtime.cpp +++ b/lib/icinga/checkable-downtime.cpp @@ -35,8 +35,8 @@ static std::map l_LegacyDowntimesCache; static std::map l_DowntimesCache; static Timer::Ptr l_DowntimesExpireTimer; -boost::signals2::signal Checkable::OnDowntimeAdded; -boost::signals2::signal Checkable::OnDowntimeRemoved; +boost::signals2::signal Checkable::OnDowntimeAdded; +boost::signals2::signal Checkable::OnDowntimeRemoved; boost::signals2::signal Checkable::OnDowntimeTriggered; int Checkable::GetNextDowntimeID(void) @@ -49,7 +49,7 @@ int Checkable::GetNextDowntimeID(void) String Checkable::AddDowntime(const String& author, const String& comment, double startTime, double endTime, bool fixed, const String& triggeredBy, double duration, const String& scheduledBy, - const String& id, const String& authority) + const String& id, const MessageOrigin& origin) { String uid; @@ -106,12 +106,12 @@ String Checkable::AddDowntime(const String& author, const String& comment, Log(LogDebug, "icinga", "Added downtime with ID '" + Convert::ToString(downtime->GetLegacyId()) + "' between '" + Utility::FormatDateTime("%Y-%m-%d %H:%M:%S", startTime) + "' and '" + Utility::FormatDateTime("%Y-%m-%d %H:%M:%S", endTime) + "'."); - OnDowntimeAdded(GetSelf(), downtime, authority); + OnDowntimeAdded(GetSelf(), downtime, origin); return uid; } -void Checkable::RemoveDowntime(const String& id, bool cancelled, const String& authority) +void Checkable::RemoveDowntime(const String& id, bool cancelled, const MessageOrigin& origin) { Checkable::Ptr owner = GetOwnerByDowntimeID(id); @@ -146,7 +146,7 @@ void Checkable::RemoveDowntime(const String& id, bool cancelled, const String& a Log(LogDebug, "icinga", "Removed downtime with ID '" + Convert::ToString(downtime->GetLegacyId()) + "' from service '" + owner->GetName() + "'."); - OnDowntimeRemoved(owner, downtime, authority); + OnDowntimeRemoved(owner, downtime, origin); } void Checkable::TriggerDowntimes(void) diff --git a/lib/icinga/checkable-flapping.cpp b/lib/icinga/checkable-flapping.cpp index bcc8fe6cf..5bc71fb82 100644 --- a/lib/icinga/checkable-flapping.cpp +++ b/lib/icinga/checkable-flapping.cpp @@ -47,12 +47,12 @@ bool Checkable::GetEnableFlapping(void) const return GetEnableFlappingRaw(); } -void Checkable::SetEnableFlapping(bool enabled, const String& authority) +void Checkable::SetEnableFlapping(bool enabled, const MessageOrigin& origin) { SetOverrideEnableFlapping(enabled); OnFlappingChanged(GetSelf(), enabled ? FlappingEnabled : FlappingDisabled); - OnEnableFlappingChanged(GetSelf(), enabled, authority); + OnEnableFlappingChanged(GetSelf(), enabled, origin); } void Checkable::UpdateFlappingStatus(bool stateChange) diff --git a/lib/icinga/checkable-notification.cpp b/lib/icinga/checkable-notification.cpp index 5b44e6d3b..ef3667bc3 100644 --- a/lib/icinga/checkable-notification.cpp +++ b/lib/icinga/checkable-notification.cpp @@ -108,11 +108,11 @@ bool Checkable::GetEnableNotifications(void) const return GetEnableNotificationsRaw(); } -void Checkable::SetEnableNotifications(bool enabled, const String& authority) +void Checkable::SetEnableNotifications(bool enabled, const MessageOrigin& origin) { SetOverrideEnableNotifications(enabled); - OnEnableNotificationsChanged(GetSelf(), enabled, authority); + OnEnableNotificationsChanged(GetSelf(), enabled, origin); } bool Checkable::GetForceNextNotification(void) const @@ -120,9 +120,9 @@ bool Checkable::GetForceNextNotification(void) const return GetForceNextNotificationRaw(); } -void Checkable::SetForceNextNotification(bool forced, const String& authority) +void Checkable::SetForceNextNotification(bool forced, const MessageOrigin& origin) { SetForceNextNotificationRaw(forced); - OnForceNextNotificationChanged(GetSelf(), forced, authority); + OnForceNextNotificationChanged(GetSelf(), forced, origin); } diff --git a/lib/icinga/checkable.cpp b/lib/icinga/checkable.cpp index c1d187735..9daf9c88f 100644 --- a/lib/icinga/checkable.cpp +++ b/lib/icinga/checkable.cpp @@ -39,8 +39,8 @@ REGISTER_TYPE(Checkable); INITIALIZE_ONCE(&Checkable::StartDowntimesExpiredTimer); -boost::signals2::signal Checkable::OnAcknowledgementSet; -boost::signals2::signal Checkable::OnAcknowledgementCleared; +boost::signals2::signal Checkable::OnAcknowledgementSet; +boost::signals2::signal Checkable::OnAcknowledgementCleared; Checkable::Checkable(void) : m_CheckRunning(false) @@ -129,7 +129,7 @@ bool Checkable::IsAcknowledged(void) return GetAcknowledgement() != AcknowledgementNone; } -void Checkable::AcknowledgeProblem(const String& author, const String& comment, AcknowledgementType type, double expiry, const String& authority) +void Checkable::AcknowledgeProblem(const String& author, const String& comment, AcknowledgementType type, double expiry, const MessageOrigin& origin) { { ObjectLock olock(this); @@ -140,17 +140,17 @@ void Checkable::AcknowledgeProblem(const String& author, const String& comment, OnNotificationsRequested(GetSelf(), NotificationAcknowledgement, GetLastCheckResult(), author, comment); - OnAcknowledgementSet(GetSelf(), author, comment, type, expiry, authority); + OnAcknowledgementSet(GetSelf(), author, comment, type, expiry, origin); } -void Checkable::ClearAcknowledgement(const String& authority) +void Checkable::ClearAcknowledgement(const MessageOrigin& origin) { ASSERT(OwnsLock()); SetAcknowledgementRaw(AcknowledgementNone); SetAcknowledgementExpiry(0); - OnAcknowledgementCleared(GetSelf(), authority); + OnAcknowledgementCleared(GetSelf(), origin); } bool Checkable::GetEnablePerfdata(void) const @@ -161,7 +161,7 @@ bool Checkable::GetEnablePerfdata(void) const return GetEnablePerfdataRaw(); } -void Checkable::SetEnablePerfdata(bool enabled, const String& authority) +void Checkable::SetEnablePerfdata(bool enabled, const MessageOrigin& origin) { SetOverrideEnablePerfdata(enabled); } diff --git a/lib/icinga/checkable.h b/lib/icinga/checkable.h index e068d2477..f999a71b1 100644 --- a/lib/icinga/checkable.h +++ b/lib/icinga/checkable.h @@ -27,6 +27,7 @@ #include "icinga/notification.h" #include "icinga/comment.h" #include "icinga/downtime.h" +#include "remote/messageorigin.h" #include "base/i2-base.h" #include "base/array.h" #include @@ -97,8 +98,8 @@ public: AcknowledgementType GetAcknowledgement(void); - void AcknowledgeProblem(const String& author, const String& comment, AcknowledgementType type, double expiry = 0, const String& authority = String()); - void ClearAcknowledgement(const String& authority = String()); + void AcknowledgeProblem(const String& author, const String& comment, AcknowledgementType type, double expiry = 0, const MessageOrigin& origin = MessageOrigin()); + void ClearAcknowledgement(const MessageOrigin& origin = MessageOrigin()); /* Checks */ shared_ptr GetCheckCommand(void) const; @@ -119,7 +120,7 @@ public: long GetSchedulingOffset(void); void SetSchedulingOffset(long offset); - void SetNextCheck(double nextCheck, const String& authority = String()); + void SetNextCheck(double nextCheck, const MessageOrigin& origin = MessageOrigin()); double GetNextCheck(void); void UpdateNextCheck(void); @@ -128,18 +129,18 @@ public: double GetLastCheck(void) const; bool GetEnableActiveChecks(void) const; - void SetEnableActiveChecks(bool enabled, const String& authority = String()); + void SetEnableActiveChecks(bool enabled, const MessageOrigin& origin = MessageOrigin()); bool GetEnablePassiveChecks(void) const; - void SetEnablePassiveChecks(bool enabled, const String& authority = String()); + void SetEnablePassiveChecks(bool enabled, const MessageOrigin& origin = MessageOrigin()); bool GetForceNextCheck(void) const; - void SetForceNextCheck(bool forced, const String& authority = String()); + void SetForceNextCheck(bool forced, const MessageOrigin& origin = MessageOrigin()); static void UpdateStatistics(const CheckResult::Ptr& cr); void ExecuteCheck(void); - void ProcessCheckResult(const CheckResult::Ptr& cr, const String& authority = String()); + void ProcessCheckResult(const CheckResult::Ptr& cr, const MessageOrigin& origin = MessageOrigin()); int GetModifiedAttributes(void) const; void SetModifiedAttributes(int flags); @@ -149,15 +150,15 @@ public: static double CalculateExecutionTime(const CheckResult::Ptr& cr); static double CalculateLatency(const CheckResult::Ptr& cr); - static boost::signals2::signal OnNextCheckChanged; - static boost::signals2::signal OnForceNextCheckChanged; - static boost::signals2::signal OnForceNextNotificationChanged; - static boost::signals2::signal OnEnableActiveChecksChanged; - static boost::signals2::signal OnEnablePassiveChecksChanged; - static boost::signals2::signal OnEnableNotificationsChanged; - static boost::signals2::signal OnEnableFlappingChanged; - static boost::signals2::signal OnNewCheckResult; - static boost::signals2::signal OnStateChange; + static boost::signals2::signal OnNextCheckChanged; + static boost::signals2::signal OnForceNextCheckChanged; + static boost::signals2::signal OnForceNextNotificationChanged; + static boost::signals2::signal OnEnableActiveChecksChanged; + static boost::signals2::signal OnEnablePassiveChecksChanged; + static boost::signals2::signal OnEnableNotificationsChanged; + static boost::signals2::signal OnEnableFlappingChanged; + static boost::signals2::signal OnNewCheckResult; + static boost::signals2::signal OnStateChange; static boost::signals2::signal OnNotificationsRequested; static boost::signals2::signal&, @@ -169,15 +170,15 @@ public: static boost::signals2::signal&, const NotificationType&, const CheckResult::Ptr&, const String&, const String&)> OnNotificationSentToAllUsers; - static boost::signals2::signal OnCommentAdded; - static boost::signals2::signal OnCommentRemoved; - static boost::signals2::signal OnDowntimeAdded; - static boost::signals2::signal OnDowntimeRemoved; + static boost::signals2::signal OnCommentAdded; + static boost::signals2::signal OnCommentRemoved; + static boost::signals2::signal OnDowntimeAdded; + static boost::signals2::signal OnDowntimeRemoved; static boost::signals2::signal OnFlappingChanged; static boost::signals2::signal OnDowntimeTriggered; static boost::signals2::signal OnAcknowledgementSet; - static boost::signals2::signal OnAcknowledgementCleared; + double, const MessageOrigin&)> OnAcknowledgementSet; + static boost::signals2::signal OnAcknowledgementCleared; static boost::signals2::signal OnEventCommandExecuted; /* Downtimes */ @@ -189,9 +190,9 @@ public: double startTime, double endTime, bool fixed, const String& triggeredBy, double duration, const String& scheduledBy = String(), const String& id = String(), - const String& authority = String()); + const MessageOrigin& origin = MessageOrigin()); - static void RemoveDowntime(const String& id, bool cancelled, const String& = String()); + static void RemoveDowntime(const String& id, bool cancelled, const MessageOrigin& origin = MessageOrigin()); void TriggerDowntimes(void); static void TriggerDowntime(const String& id); @@ -209,11 +210,11 @@ public: static int GetNextCommentID(void); String AddComment(CommentType entryType, const String& author, - const String& text, double expireTime, const String& id = String(), const String& authority = String()); + const String& text, double expireTime, const String& id = String(), const MessageOrigin& origin = MessageOrigin()); void RemoveAllComments(void); void RemoveCommentsByType(int type); - static void RemoveComment(const String& id, const String& authority = String()); + static void RemoveComment(const String& id, const MessageOrigin& origin = MessageOrigin()); static String GetCommentIDFromLegacyID(int id); static Checkable::Ptr GetOwnerByCommentID(const String& id); @@ -221,7 +222,7 @@ public: /* Notifications */ bool GetEnableNotifications(void) const; - void SetEnableNotifications(bool enabled, const String& authority = String()); + void SetEnableNotifications(bool enabled, const MessageOrigin& origin = MessageOrigin()); void SendNotifications(NotificationType type, const CheckResult::Ptr& cr, const String& author = "", const String& text = ""); @@ -229,7 +230,7 @@ public: void AddNotification(const Notification::Ptr& notification); void RemoveNotification(const Notification::Ptr& notification); - void SetForceNextNotification(bool force, const String& authority = String()); + void SetForceNextNotification(bool force, const MessageOrigin& origin = MessageOrigin()); bool GetForceNextNotification(void) const; void ResetNotificationNumbers(void); @@ -247,14 +248,14 @@ public: double GetFlappingCurrent(void) const; bool GetEnableFlapping(void) const; - void SetEnableFlapping(bool enabled, const String& authority = String()); + void SetEnableFlapping(bool enabled, const MessageOrigin& origin = MessageOrigin()); bool IsFlapping(void) const; void UpdateFlappingStatus(bool stateChange); /* Performance data */ bool GetEnablePerfdata(void) const; - void SetEnablePerfdata(bool enabled, const String& authority = String()); + void SetEnablePerfdata(bool enabled, const MessageOrigin& origin = MessageOrigin()); /* Dependencies */ void AddDependency(const shared_ptr& dep); diff --git a/lib/icinga/domain.ti b/lib/icinga/domain.ti deleted file mode 100644 index 886f985ac..000000000 --- a/lib/icinga/domain.ti +++ /dev/null @@ -1,11 +0,0 @@ -#include "base/dynamicobject.h" - -namespace icinga -{ - -class Domain : DynamicObject -{ - [config] Dictionary::Ptr acl; -}; - -} diff --git a/lib/icinga/externalcommandprocessor.cpp b/lib/icinga/externalcommandprocessor.cpp index 4b86dacf6..09761437c 100644 --- a/lib/icinga/externalcommandprocessor.cpp +++ b/lib/icinga/externalcommandprocessor.cpp @@ -28,6 +28,7 @@ #include "icinga/checkcommand.h" #include "icinga/eventcommand.h" #include "icinga/notificationcommand.h" +#include "remote/apifunction.h" #include "base/convert.h" #include "base/logger_fwd.h" #include "base/objectlock.h" @@ -41,8 +42,7 @@ using namespace icinga; -static boost::once_flag l_InitializeOnce = BOOST_ONCE_INIT; -static boost::mutex l_Mutex; +INITIALIZE_ONCE(&ExternalCommandProcessor::StaticInitialize); typedef boost::function& arguments)> ExternalCommandCallback; @@ -53,18 +53,46 @@ struct ExternalCommandInfo size_t MaxArgs; }; -static std::map l_Commands; +static boost::mutex& GetMutex(void) +{ + static boost::mutex mtx; + return mtx; +} +static std::map& GetCommands(void) +{ + static std::map commands; + return commands; +} boost::signals2::signal&)> ExternalCommandProcessor::OnNewExternalCommand; +static Value ExternalCommandAPIWrapper(const String& command, const Dictionary::Ptr& params) +{ + std::vector arguments; + + if (params) { + int i = 0; + while (params->Contains("arg" + Convert::ToString(i))) { + arguments.push_back(params->Get("arg" + Convert::ToString(i))); + i++; + } + } + + ExternalCommandProcessor::Execute(Utility::GetTime(), command, arguments); + return true; +} + static void RegisterCommand(const String& command, const ExternalCommandCallback& callback, size_t minArgs = 0, size_t maxArgs = -1) { - boost::mutex::scoped_lock lock(l_Mutex); + boost::mutex::scoped_lock lock(GetMutex()); ExternalCommandInfo eci; eci.Callback = callback; eci.MinArgs = minArgs; eci.MaxArgs = (maxArgs == -1) ? minArgs : maxArgs; - l_Commands[command] = eci; + GetCommands()[command] = eci; + + ApiFunction::Ptr afunc = make_shared(boost::bind(&ExternalCommandAPIWrapper, command, _2)); + ApiFunction::Register("extcmd::" + command, afunc); } void ExternalCommandProcessor::Execute(const String& line) @@ -100,17 +128,15 @@ void ExternalCommandProcessor::Execute(const String& line) void ExternalCommandProcessor::Execute(double time, const String& command, const std::vector& arguments) { - boost::call_once(l_InitializeOnce, &ExternalCommandProcessor::Initialize); - ExternalCommandInfo eci; { - boost::mutex::scoped_lock lock(l_Mutex); + boost::mutex::scoped_lock lock(GetMutex()); std::map::iterator it; - it = l_Commands.find(command); + it = GetCommands().find(command); - if (it == l_Commands.end()) + if (it == GetCommands().end()) BOOST_THROW_EXCEPTION(std::invalid_argument("The external command '" + command + "' does not exist.")); eci = it->second; @@ -143,7 +169,7 @@ void ExternalCommandProcessor::Execute(double time, const String& command, const eci.Callback(time, realArguments); } -void ExternalCommandProcessor::Initialize(void) +void ExternalCommandProcessor::StaticInitialize(void) { RegisterCommand("PROCESS_HOST_CHECK_RESULT", &ExternalCommandProcessor::ProcessHostCheckResult, 3); RegisterCommand("PROCESS_SERVICE_CHECK_RESULT", &ExternalCommandProcessor::ProcessServiceCheckResult, 4); diff --git a/lib/icinga/externalcommandprocessor.h b/lib/icinga/externalcommandprocessor.h index 70f317cab..256cf1be6 100644 --- a/lib/icinga/externalcommandprocessor.h +++ b/lib/icinga/externalcommandprocessor.h @@ -37,13 +37,13 @@ public: static void Execute(const String& line); static void Execute(double time, const String& command, const std::vector& arguments); - static boost::signals2::signal&)> OnNewExternalCommand; + static void StaticInitialize(void); + + static boost::signals2::signal&)> OnNewExternalCommand; private: ExternalCommandProcessor(void); - static void Initialize(void); - static void ProcessHostCheckResult(double time, const std::vector& arguments); static void ProcessServiceCheckResult(double time, const std::vector& arguments); static void ScheduleHostCheck(double time, const std::vector& arguments); diff --git a/lib/icinga/icinga-type.conf b/lib/icinga/icinga-type.conf index 0f00ac1cc..9ed16537a 100644 --- a/lib/icinga/icinga-type.conf +++ b/lib/icinga/icinga-type.conf @@ -232,12 +232,6 @@ } -%type Domain { - %attribute %dictionary "acl" { - %attribute %number "*" - } -} - %type ScheduledDowntime { %require "host_name", %attribute %name(Host) "host_name", diff --git a/lib/icinga/notification.cpp b/lib/icinga/notification.cpp index a85f4a4c8..1e5d38b08 100644 --- a/lib/icinga/notification.cpp +++ b/lib/icinga/notification.cpp @@ -39,7 +39,7 @@ REGISTER_TYPE(Notification); REGISTER_SCRIPTFUNCTION(ValidateNotificationFilters, &Notification::ValidateFilters); INITIALIZE_ONCE(&Notification::StaticInitialize); -boost::signals2::signal Notification::OnNextNotificationChanged; +boost::signals2::signal Notification::OnNextNotificationChanged; String NotificationNameComposer::MakeName(const String& shortName, const Dictionary::Ptr props) const { @@ -180,11 +180,11 @@ double Notification::GetNextNotification(void) const * Sets the timestamp when the next periodical notification should be sent. * This does not affect notifications that are sent for state changes. */ -void Notification::SetNextNotification(double time, const String& authority) +void Notification::SetNextNotification(double time, const MessageOrigin& origin) { SetNextNotificationRaw(time); - OnNextNotificationChanged(GetSelf(), time, authority); + OnNextNotificationChanged(GetSelf(), time, origin); } void Notification::UpdateNotificationNumber(void) diff --git a/lib/icinga/notification.h b/lib/icinga/notification.h index 3cd252175..b72e9954e 100644 --- a/lib/icinga/notification.h +++ b/lib/icinga/notification.h @@ -25,6 +25,7 @@ #include "icinga/user.h" #include "icinga/usergroup.h" #include "icinga/timeperiod.h" +#include "remote/messageorigin.h" #include "config/applyrule.h" #include "base/array.h" @@ -86,7 +87,7 @@ public: std::set GetUserGroups(void) const; double GetNextNotification(void) const; - void SetNextNotification(double time, const String& authority = String()); + void SetNextNotification(double time, const MessageOrigin& origin = MessageOrigin()); void UpdateNotificationNumber(void); void ResetNotificationNumber(void); @@ -97,7 +98,7 @@ public: static String NotificationTypeToString(NotificationType type); - static boost::signals2::signal OnNextNotificationChanged; + static boost::signals2::signal OnNextNotificationChanged; static void RegisterApplyRuleHandler(void); diff --git a/lib/methods/CMakeLists.txt b/lib/methods/CMakeLists.txt index 59bc28d3d..1ceacf793 100644 --- a/lib/methods/CMakeLists.txt +++ b/lib/methods/CMakeLists.txt @@ -22,7 +22,7 @@ else() endif() add_library(methods SHARED - castfuncs.cpp icingachecktask.cpp nullchecktask.cpp nulleventtask.cpp + castfuncs.cpp clusterchecktask.cpp icingachecktask.cpp nullchecktask.cpp nulleventtask.cpp pluginchecktask.cpp plugineventtask.cpp pluginnotificationtask.cpp randomchecktask.cpp timeperiodtask.cpp ${WindowsSources} ) diff --git a/components/cluster/clusterchecktask.cpp b/lib/methods/clusterchecktask.cpp similarity index 73% rename from components/cluster/clusterchecktask.cpp rename to lib/methods/clusterchecktask.cpp index adb6f953a..e5dd39128 100644 --- a/components/cluster/clusterchecktask.cpp +++ b/lib/methods/clusterchecktask.cpp @@ -17,8 +17,8 @@ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * ******************************************************************************/ -#include "cluster/clusterchecktask.h" -#include "cluster/clusterlistener.h" +#include "methods/clusterchecktask.h" +#include "remote/apilistener.h" #include "remote/endpoint.h" #include "icinga/cib.h" #include "icinga/service.h" @@ -35,38 +35,39 @@ using namespace icinga; REGISTER_SCRIPTFUNCTION(ClusterCheck, &ClusterCheckTask::ScriptFunc); -void ClusterCheckTask::ScriptFunc(const Checkable::Ptr& service, const CheckResult::Ptr& cr) +void ClusterCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) { - /* fetch specific cluster status */ - std::pair stats; - BOOST_FOREACH(const ClusterListener::Ptr& cluster_listener, DynamicType::GetObjects()) { - /* XXX there's only one cluster listener */ - stats = cluster_listener->GetClusterStatus(); + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) { + cr->SetOutput("No API listener is configured for this instance."); + cr->SetState(ServiceUnknown); + checkable->ProcessCheckResult(cr); + return; } + std::pair stats = listener->GetStatus(); + Dictionary::Ptr status = stats.first; /* use feature stats perfdata */ std::pair feature_stats = CIB::GetFeatureStats(); - Dictionary::Ptr perfdata = feature_stats.second; + cr->SetPerformanceData(feature_stats.second); String connected_endpoints = FormatArray(status->Get("conn_endpoints")); String not_connected_endpoints = FormatArray(status->Get("not_conn_endpoints")); - ServiceState state = ServiceOK; - String output = "Icinga 2 Cluster is running: Connected Endpoints: "+ Convert::ToString(status->Get("num_conn_endpoints")) + " (" + - connected_endpoints + ")."; - if (status->Get("num_not_conn_endpoints") > 0) { - state = ServiceCritical; - output = "Icinga 2 Cluster Problem: " + Convert::ToString(status->Get("num_not_conn_endpoints")) + - " Endpoints (" + not_connected_endpoints + ") not connected."; + cr->SetState(ServiceCritical); + cr->SetOutput("Icinga 2 Cluster is running: Connected Endpoints: "+ Convert::ToString(status->Get("num_conn_endpoints")) + " (" + + connected_endpoints + ")."); + } else { + cr->SetState(ServiceOK); + cr->SetOutput("Icinga 2 Cluster Problem: " + Convert::ToString(status->Get("num_not_conn_endpoints")) + + " Endpoints (" + not_connected_endpoints + ") not connected."); } - cr->SetOutput(output); - cr->SetPerformanceData(perfdata); - cr->SetState(state); - service->ProcessCheckResult(cr); + checkable->ProcessCheckResult(cr); } String ClusterCheckTask::FormatArray(const Array::Ptr& arr) @@ -88,4 +89,3 @@ String ClusterCheckTask::FormatArray(const Array::Ptr& arr) return str; } - diff --git a/components/cluster/clusterchecktask.h b/lib/methods/clusterchecktask.h similarity index 100% rename from components/cluster/clusterchecktask.h rename to lib/methods/clusterchecktask.h diff --git a/lib/remote/CMakeLists.txt b/lib/remote/CMakeLists.txt index 367fdfb92..9a8400dea 100644 --- a/lib/remote/CMakeLists.txt +++ b/lib/remote/CMakeLists.txt @@ -15,12 +15,15 @@ # along with this program; if not, write to the Free Software Foundation # Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. +mkclass_target(apilistener.ti apilistener.th) mkclass_target(endpoint.ti endpoint.th) +mkclass_target(zone.ti zone.th) mkembedconfig_target(remote-type.conf remote-type.cpp) add_library(remote SHARED - endpoint.cpp endpoint.th jsonrpc.cpp remote-type.cpp + apiclient.cpp apifunction.cpp apilistener.cpp apilistener.th endpoint.cpp + endpoint.th jsonrpc.cpp messageorigin.cpp remote-type.cpp zone.cpp zone.th ) include_directories(${Boost_INCLUDE_DIRS}) @@ -39,4 +42,7 @@ install( LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/icinga2 ) +#install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/icinga2/api\")") +install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/icinga2/api/log\")") +install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/icinga2/api/repository\")") diff --git a/lib/remote/apiclient.cpp b/lib/remote/apiclient.cpp new file mode 100644 index 000000000..cc4bb439b --- /dev/null +++ b/lib/remote/apiclient.cpp @@ -0,0 +1,200 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * + * * + * 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 2 * + * 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, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "remote/apiclient.h" +#include "remote/apilistener.h" +#include "remote/apifunction.h" +#include "remote/jsonrpc.h" +#include "base/application.h" +#include "base/dynamictype.h" +#include "base/objectlock.h" +#include "base/utility.h" +#include "base/logger_fwd.h" +#include "base/exception.h" +#include "base/initialize.h" +#include "config/configitembuilder.h" + +using namespace icinga; + +Timer::Ptr ApiClient::m_KeepAliveTimer; + +INITIALIZE_ONCE(&ApiClient::StaticInitialize); + +static Value SetLogPositionHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); +REGISTER_APIFUNCTION(SetLogPosition, log, &SetLogPositionHandler); + +ApiClient::ApiClient(const Endpoint::Ptr& endpoint, const Stream::Ptr& stream, ConnectionRole role) + : m_Endpoint(endpoint), m_Stream(stream), m_Role(role), m_Seen(Utility::GetTime()) +{ } + +void ApiClient::StaticInitialize(void) +{ + m_KeepAliveTimer = make_shared(); + m_KeepAliveTimer->OnTimerExpired.connect(boost::bind(&ApiClient::KeepAliveTimerHandler)); + m_KeepAliveTimer->SetInterval(5); + m_KeepAliveTimer->Start(); +} + +void ApiClient::Start(void) +{ + boost::thread thread(boost::bind(&ApiClient::MessageThreadProc, static_cast(GetSelf()))); + thread.detach(); +} + +Endpoint::Ptr ApiClient::GetEndpoint(void) const +{ + return m_Endpoint; +} + +Stream::Ptr ApiClient::GetStream(void) const +{ + return m_Stream; +} + +ConnectionRole ApiClient::GetRole(void) const +{ + return m_Role; +} + +void ApiClient::SendMessage(const Dictionary::Ptr& message) +{ + try { + ObjectLock olock(m_Stream); + JsonRpc::SendMessage(m_Stream, message); + if (message->Get("method") != "log::SetLogPosition") + m_Seen = Utility::GetTime(); + } catch (const std::exception& ex) { + std::ostringstream msgbuf; + msgbuf << "Error while sending JSON-RPC message for endpoint '" << m_Endpoint->GetName() << "': " << DiagnosticInformation(ex); + Log(LogWarning, "remote", msgbuf.str()); + + Disconnect(); + } +} + +void ApiClient::Disconnect(void) +{ + Log(LogWarning, "remote", "API client disconnected for endpoint '" + m_Endpoint->GetName() + "'"); + m_Stream->Close(); + m_Endpoint->RemoveClient(GetSelf()); +} + +bool ApiClient::ProcessMessage(void) +{ + Dictionary::Ptr message = JsonRpc::ReadMessage(m_Stream); + + if (!message) + return false; + + if (message->Get("method") != "log::SetLogPosition") + m_Seen = Utility::GetTime(); + + if (message->Contains("ts")) { + double ts = message->Get("ts"); + + /* ignore old messages */ + if (ts < m_Endpoint->GetRemoteLogPosition()) + return true; + + m_Endpoint->SetRemoteLogPosition(ts); + } + + MessageOrigin origin; + origin.FromClient = GetSelf(); + + if (m_Endpoint->GetZone() != Zone::GetLocalZone()) + origin.FromZone = m_Endpoint->GetZone(); + else + origin.FromZone = Zone::GetByName(message->Get("originZone")); + + String method = message->Get("method"); + + Log(LogDebug, "remote", "Received '" + method + "' message from '" + m_Endpoint->GetName() + "'"); + + Dictionary::Ptr resultMessage = make_shared(); + + try { + ApiFunction::Ptr afunc = ApiFunction::GetByName(method); + + if (!afunc) + BOOST_THROW_EXCEPTION(std::invalid_argument("Function '" + method + "' does not exist.")); + + resultMessage->Set("result", afunc->Invoke(origin, message->Get("params"))); + } catch (std::exception& ex) { + resultMessage->Set("error", DiagnosticInformation(ex)); + } + + if (message->Contains("id")) { + resultMessage->Set("jsonrpc", "2.0"); + resultMessage->Set("id", message->Get("id")); + JsonRpc::SendMessage(m_Stream, resultMessage); + } + + return true; +} + +void ApiClient::MessageThreadProc(void) +{ + Utility::SetThreadName("API Client"); + + try { + while (ProcessMessage()) + ; /* empty loop body */ + + Disconnect(); + } catch (const std::exception& ex) { + Log(LogWarning, "remote", "Error while reading JSON-RPC message for endpoint '" + m_Endpoint->GetName() + "': " + DiagnosticInformation(ex)); + } +} + +void ApiClient::KeepAliveTimerHandler(void) +{ + double now = Utility::GetTime(); + + BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects()) { + if (endpoint->GetZone() == Zone::GetLocalZone()) + continue; + + if (endpoint->GetSyncing() || endpoint->GetKeepAlive() <= 0) + continue; + + double timeout = now - endpoint->GetKeepAlive(); + + BOOST_FOREACH(const ApiClient::Ptr& client, endpoint->GetClients()) { + if (client->m_Seen < timeout) { + Log(LogInformation, "remote", "Closing connection with inactive endpoint '" + endpoint->GetName() + "'"); + client->Disconnect(); + } + } + } +} + +Value SetLogPositionHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) +{ + if (!params) + return Empty; + + double log_position = params->Get("log_position"); + Endpoint::Ptr endpoint = origin.FromClient->GetEndpoint(); + + if (log_position > endpoint->GetLocalLogPosition()) + endpoint->SetLocalLogPosition(log_position); + + return Empty; +} diff --git a/components/agent/agentchecktask.h b/lib/remote/apiclient.h similarity index 62% rename from components/agent/agentchecktask.h rename to lib/remote/apiclient.h index e35a34e5c..a9e13210e 100644 --- a/components/agent/agentchecktask.h +++ b/lib/remote/apiclient.h @@ -17,34 +17,63 @@ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * ******************************************************************************/ -#ifndef AGENTCHECKTASK_H -#define AGENTCHECKTASK_H +#ifndef APICLIENT_H +#define APICLIENT_H -#include "icinga/service.h" +#include "remote/endpoint.h" +#include "base/stream.h" #include "base/timer.h" +#include "base/array.h" +#include "remote/i2-remote.h" +#include namespace icinga { +enum ClientRole +{ + ClientInbound, + ClientOutbound +}; + /** - * Agent check type. + * An API client connection. * - * @ingroup methods + * @ingroup remote */ -class AgentCheckTask +class I2_REMOTE_API ApiClient : public Object { public: + DECLARE_PTR_TYPEDEFS(ApiClient); + + ApiClient(const Endpoint::Ptr& endpoint, const Stream::Ptr& stream, ConnectionRole role); + static void StaticInitialize(void); - static void ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); + + void Start(void); + + Endpoint::Ptr GetEndpoint(void) const; + Stream::Ptr GetStream(void) const; + ConnectionRole GetRole(void) const; + + void Disconnect(void); + + void SendMessage(const Dictionary::Ptr& request); private: - AgentCheckTask(void); - - static void AgentTimerHandler(void); + Endpoint::Ptr m_Endpoint; + Stream::Ptr m_Stream; + ConnectionRole m_Role; + double m_Seen; + bool m_Syncing; + + bool ProcessMessage(void); + void MessageThreadProc(void); - static bool SendResult(const Checkable::Ptr& checkable, bool enqueue); + static Timer::Ptr m_KeepAliveTimer; + static void KeepAliveTimerHandler(void); }; } -#endif /* AGENTCHECKTASK_H */ +#endif /* APICLIENT_H */ diff --git a/lib/remote/apifunction.cpp b/lib/remote/apifunction.cpp new file mode 100644 index 000000000..f14bde061 --- /dev/null +++ b/lib/remote/apifunction.cpp @@ -0,0 +1,59 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * + * * + * 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 2 * + * 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, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "remote/apifunction.h" +#include "base/registry.h" +#include "base/singleton.h" + +using namespace icinga; + +ApiFunction::ApiFunction(const Callback& function) +: m_Callback(function) +{ } + +Value ApiFunction::Invoke(const MessageOrigin& origin, const Dictionary::Ptr& arguments) +{ + return m_Callback(origin, arguments); +} + +RegisterApiFunctionHelper::RegisterApiFunctionHelper(const String& name, const ApiFunction::Callback& function) +{ + ApiFunction::Ptr func = make_shared(function); + ApiFunctionRegistry::GetInstance()->Register(name, func); +} + +ApiFunction::Ptr ApiFunction::GetByName(const String& name) +{ + return ApiFunctionRegistry::GetInstance()->GetItem(name); +} + +void ApiFunction::Register(const String& name, const ApiFunction::Ptr& function) +{ + ApiFunctionRegistry::GetInstance()->Register(name, function); +} + +void ApiFunction::Unregister(const String& name) +{ + ApiFunctionRegistry::GetInstance()->Unregister(name); +} + +ApiFunctionRegistry *ApiFunctionRegistry::GetInstance(void) +{ + return Singleton::GetInstance(); +} diff --git a/lib/remote/apifunction.h b/lib/remote/apifunction.h new file mode 100644 index 000000000..41122925f --- /dev/null +++ b/lib/remote/apifunction.h @@ -0,0 +1,87 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * + * * + * 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 2 * + * 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, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#ifndef APIFUNCTION_H +#define APIFUNCTION_H + +#include "remote/i2-remote.h" +#include "remote/apiclient.h" +#include "remote/messageorigin.h" +#include "base/registry.h" +#include "base/singleton.h" +#include "base/value.h" +#include "base/dictionary.h" +#include +#include + +namespace icinga +{ + +/** + * An API function. + * + * @ingroup base + */ +class I2_REMOTE_API ApiFunction : public Object +{ +public: + DECLARE_PTR_TYPEDEFS(ApiFunction); + + typedef boost::function Callback; + + ApiFunction(const Callback& function); + + Value Invoke(const MessageOrigin& origin, const Dictionary::Ptr& arguments); + + static ApiFunction::Ptr GetByName(const String& name); + static void Register(const String& name, const ApiFunction::Ptr& function); + static void Unregister(const String& name); + +private: + Callback m_Callback; +}; + +/** + * A registry for API functions. + * + * @ingroup base + */ +class I2_REMOTE_API ApiFunctionRegistry : public Registry +{ +public: + static ApiFunctionRegistry *GetInstance(void); +}; + +/** + * Helper class for registering ApiFunction implementation classes. + * + * @ingroup base + */ +class I2_REMOTE_API RegisterApiFunctionHelper +{ +public: + RegisterApiFunctionHelper(const String& name, const ApiFunction::Callback& function); +}; + +#define REGISTER_APIFUNCTION(name, ns, callback) \ + I2_EXPORT icinga::RegisterApiFunctionHelper g_RegisterAF_ ## name(#ns "::" #name, callback) + +} + +#endif /* APIFUNCTION_H */ diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp new file mode 100644 index 000000000..b783a9a74 --- /dev/null +++ b/lib/remote/apilistener.cpp @@ -0,0 +1,633 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * + * * + * 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 2 * + * 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, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "remote/apilistener.h" +#include "remote/apiclient.h" +#include "remote/endpoint.h" +#include "remote/jsonrpc.h" +#include "base/convert.h" +#include "base/netstring.h" +#include "base/dynamictype.h" +#include "base/logger_fwd.h" +#include "base/objectlock.h" +#include "base/stdiostream.h" +#include "base/networkstream.h" +#include "base/application.h" +#include "base/context.h" +#include "base/statsfunction.h" +#include + +using namespace icinga; + +REGISTER_TYPE(ApiListener); + +boost::signals2::signal ApiListener::OnMasterChanged; + +REGISTER_STATSFUNCTION(ApiListenerStats, &ApiListener::StatsFunc); + +void ApiListener::OnConfigLoaded(void) +{ + /* set up SSL context */ + shared_ptr cert = GetX509Certificate(GetCertPath()); + SetIdentity(GetCertificateCN(cert)); + Log(LogInformation, "remote", "My API identity: " + GetIdentity()); + + m_SSLContext = MakeSSLContext(GetCertPath(), GetKeyPath(), GetCaPath()); + + if (!GetCrlPath().IsEmpty()) + AddCRLToSSLContext(m_SSLContext, GetCrlPath()); + + if (!Endpoint::GetByName(GetIdentity())) + BOOST_THROW_EXCEPTION(std::runtime_error("Endpoint object for '" + GetIdentity() + "' is missing.")); +} + +/** + * Starts the component. + */ +void ApiListener::Start(void) +{ + if (std::distance(DynamicType::GetObjects().first, DynamicType::GetObjects().second) > 1) + BOOST_THROW_EXCEPTION(std::runtime_error("Only one ApiListener object is allowed.")); + + DynamicObject::Start(); + + { + boost::mutex::scoped_lock(m_LogLock); + RotateLogFile(); + OpenLogFile(); + } + + /* create the primary JSON-RPC listener */ + AddListener(GetBindPort()); + + m_Timer = make_shared(); + m_Timer->OnTimerExpired.connect(boost::bind(&ApiListener::ApiTimerHandler, this)); + m_Timer->SetInterval(5); + m_Timer->Start(); + m_Timer->Reschedule(0); + + OnMasterChanged(true); +} + +ApiListener::Ptr ApiListener::GetInstance(void) +{ + BOOST_FOREACH(const ApiListener::Ptr& listener, DynamicType::GetObjects()) + return listener; + + return ApiListener::Ptr(); +} + +shared_ptr ApiListener::GetSSLContext(void) const +{ + return m_SSLContext; +} + +Endpoint::Ptr ApiListener::GetMaster(void) const +{ + Zone::Ptr zone = Zone::GetLocalZone(); + std::vector names; + + BOOST_FOREACH(const Endpoint::Ptr& endpoint, zone->GetEndpoints()) + if (endpoint->IsConnected() || endpoint->GetName() == GetIdentity()) + names.push_back(endpoint->GetName()); + + std::sort(names.begin(), names.end()); + + return Endpoint::GetByName(*names.begin()); +} + +bool ApiListener::IsMaster(void) const +{ + return GetMaster()->GetName() == GetIdentity(); +} + +/** + * Creates a new JSON-RPC listener on the specified port. + * + * @param service The port to listen on. + */ +void ApiListener::AddListener(const String& service) +{ + ObjectLock olock(this); + + shared_ptr sslContext = m_SSLContext; + + if (!sslContext) + BOOST_THROW_EXCEPTION(std::logic_error("SSL context is required for AddListener()")); + + std::ostringstream s; + s << "Adding new listener: port " << service; + Log(LogInformation, "agent", s.str()); + + TcpSocket::Ptr server = make_shared(); + server->Bind(service, AF_INET6); + + boost::thread thread(boost::bind(&ApiListener::ListenerThreadProc, this, server)); + thread.detach(); + + m_Servers.insert(server); +} + +void ApiListener::ListenerThreadProc(const Socket::Ptr& server) +{ + Utility::SetThreadName("API Listener"); + + server->Listen(); + + for (;;) { + Socket::Ptr client = server->Accept(); + + Utility::QueueAsyncCallback(boost::bind(&ApiListener::NewClientHandler, this, client, RoleServer)); + } +} + +/** + * Creates a new JSON-RPC client and connects to the specified host and port. + * + * @param node The remote host. + * @param service The remote port. + */ +void ApiListener::AddConnection(const String& node, const String& service) { + { + ObjectLock olock(this); + + shared_ptr sslContext = m_SSLContext; + + if (!sslContext) + BOOST_THROW_EXCEPTION(std::logic_error("SSL context is required for AddConnection()")); + } + + TcpSocket::Ptr client = make_shared(); + + client->Connect(node, service); + Utility::QueueAsyncCallback(boost::bind(&ApiListener::NewClientHandler, this, client, RoleClient)); +} + +/** + * Processes a new client connection. + * + * @param client The new client. + */ +void ApiListener::NewClientHandler(const Socket::Ptr& client, ConnectionRole role) +{ + CONTEXT("Handling new API client connection"); + + TlsStream::Ptr tlsStream; + + { + ObjectLock olock(this); + tlsStream = make_shared(client, role, m_SSLContext); + } + + tlsStream->Handshake(); + + shared_ptr cert = tlsStream->GetPeerCertificate(); + String identity = GetCertificateCN(cert); + + Endpoint::Ptr endpoint = Endpoint::GetByName(identity); + + if (!endpoint) { + Log(LogInformation, "remote", "New client for unknown endpoint '" + identity + "'"); + return; + } + + Log(LogInformation, "remote", "New client connection for identity '" + identity + "'"); + + bool need_sync = !endpoint->IsConnected(); + + ApiClient::Ptr aclient = make_shared(endpoint, tlsStream, role); + aclient->Start(); + + if (need_sync) { + { + ObjectLock olock(endpoint); + + endpoint->SetSyncing(true); + } + + ReplayLog(aclient); + } + + endpoint->AddClient(aclient); +} + +void ApiListener::ApiTimerHandler(void) +{ + double now = Utility::GetTime(); + + std::vector files; + Utility::Glob(GetApiDir() + "log/*", boost::bind(&ApiListener::LogGlobHandler, boost::ref(files), _1), GlobFile); + std::sort(files.begin(), files.end()); + + BOOST_FOREACH(int ts, files) { + bool need = false; + + BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects()) { + if (endpoint->GetName() == GetIdentity()) + continue; + + if (endpoint->GetLogDuration() >= 0 && ts < now - endpoint->GetLogDuration()) + continue; + + if (ts > endpoint->GetLocalLogPosition()) { + need = true; + break; + } + } + + if (!need) { + String path = GetApiDir() + "log/" + Convert::ToString(ts); + Log(LogInformation, "remote", "Removing old log file: " + path); + (void)unlink(path.CStr()); + } + } + + if (IsMaster()) { + Zone::Ptr my_zone = Zone::GetLocalZone(); + + BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects()) { + if (endpoint->IsConnected() || endpoint->GetName() == GetIdentity()) + continue; + + if (endpoint->GetHost().IsEmpty() || endpoint->GetPort().IsEmpty()) + continue; + + Zone::Ptr their_zone = endpoint->GetZone(); + + if (my_zone != their_zone && my_zone != their_zone->GetParent() && their_zone != my_zone->GetParent()) + continue; + + AddConnection(endpoint->GetHost(), endpoint->GetPort()); + } + } + + BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects()) { + if (!endpoint->IsConnected()) + continue; + + double ts = endpoint->GetRemoteLogPosition(); + + if (ts == 0) + continue; + + Dictionary::Ptr lparams = make_shared(); + lparams->Set("log_position", ts); + + Dictionary::Ptr lmessage = make_shared(); + lmessage->Set("jsonrpc", "2.0"); + lmessage->Set("method", "log::SetLogPosition"); + lmessage->Set("params", lparams); + + BOOST_FOREACH(const ApiClient::Ptr& client, endpoint->GetClients()) + client->SendMessage(lmessage); + + Log(LogInformation, "remote", "Setting log position for identity '" + endpoint->GetName() + "': " + + Utility::FormatDateTime("%Y/%m/%d %H:%M:%S", ts)); + } + + Log(LogInformation, "remote", "Current master: " + GetMaster()->GetName()); + + std::vector names; + BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects()) + if (endpoint->IsConnected()) + names.push_back(endpoint->GetName() + " (" + Convert::ToString(endpoint->GetClients().size()) + ")"); + + Log(LogInformation, "remote", "Connected endpoints: " + Utility::NaturalJoin(names)); +} + +void ApiListener::RelayMessage(const MessageOrigin& origin, const DynamicObject::Ptr& secobj, const Dictionary::Ptr& message, bool log) +{ + m_RelayQueue.Enqueue(boost::bind(&ApiListener::SyncRelayMessage, this, origin, secobj, message, log)); +} + +void ApiListener::PersistMessage(const Dictionary::Ptr& message) +{ + double ts = message->Get("ts"); + + ASSERT(ts != 0); + + Dictionary::Ptr pmessage = make_shared(); + pmessage->Set("timestamp", ts); + + pmessage->Set("message", JsonSerialize(message)); + + boost::mutex::scoped_lock lock(m_LogLock); + if (m_LogFile) { + NetString::WriteStringToStream(m_LogFile, JsonSerialize(pmessage)); + m_LogMessageCount++; + SetLogMessageTimestamp(ts); + + if (m_LogMessageCount > 50000) { + CloseLogFile(); + RotateLogFile(); + OpenLogFile(); + } + } +} + +void ApiListener::SyncRelayMessage(const MessageOrigin& origin, const DynamicObject::Ptr& secobj, const Dictionary::Ptr& message, bool log) +{ + double ts = Utility::GetTime(); + message->Set("ts", ts); + + Log(LogDebug, "remote", "Relaying '" + message->Get("method") + "' message"); + + if (log) + m_LogQueue.Enqueue(boost::bind(&ApiListener::PersistMessage, this, message)); + + if (origin.FromZone) + message->Set("originZone", origin.FromZone->GetName()); + + bool is_master = IsMaster(); + Endpoint::Ptr master = GetMaster(); + Zone::Ptr my_zone = Zone::GetLocalZone(); + + std::vector skippedEndpoints; + std::set finishedZones; + + BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects()) { + /* don't relay messages to ourselves or disconnected endpoints */ + if (endpoint->GetName() == GetIdentity() || !endpoint->IsConnected()) + continue; + + Zone::Ptr target_zone = endpoint->GetZone(); + + /* don't relay the message to the zone through more than one endpoint */ + if (finishedZones.find(target_zone) != finishedZones.end()) { + skippedEndpoints.push_back(endpoint); + continue; + } + + /* don't relay messages back to the endpoint which we got the message from */ + if (origin.FromClient && endpoint == origin.FromClient->GetEndpoint()) { + skippedEndpoints.push_back(endpoint); + continue; + } + + /* don't relay messages back to the zone which we got the message from */ + if (origin.FromZone && target_zone == origin.FromZone) { + skippedEndpoints.push_back(endpoint); + continue; + } + + /* only relay message to the master if we're not currently the master */ + if (!is_master && master != endpoint) { + skippedEndpoints.push_back(endpoint); + continue; + } + + /* only relay the message to a) the same zone, b) the parent zone and c) direct child zones */ + if (target_zone != my_zone && target_zone != my_zone->GetParent() && + secobj->GetZone() != target_zone->GetName()) { + skippedEndpoints.push_back(endpoint); + continue; + } + + /* only relay messages to zones which have access to the object */ + if (!target_zone->CanAccessObject(secobj)) + continue; + + finishedZones.insert(target_zone); + + { + ObjectLock olock(endpoint); + + if (!endpoint->GetSyncing()) { + Log(LogDebug, "remote", "Sending message to '" + endpoint->GetName() + "'"); + + BOOST_FOREACH(const ApiClient::Ptr& client, endpoint->GetClients()) + client->SendMessage(message); + } + } + } + + BOOST_FOREACH(const Endpoint::Ptr& endpoint, skippedEndpoints) + endpoint->SetLocalLogPosition(ts); +} + +String ApiListener::GetApiDir(void) +{ + return Application::GetLocalStateDir() + "/lib/icinga2/api/"; +} + +/* must hold m_LogLock */ +void ApiListener::OpenLogFile(void) +{ + String path = GetApiDir() + "log/current"; + + std::fstream *fp = new std::fstream(path.CStr(), std::fstream::out | std::ofstream::app); + + if (!fp->good()) { + Log(LogWarning, "cluster", "Could not open spool file: " + path); + return; + } + + StdioStream::Ptr logStream = make_shared(fp, true); +#ifdef HAVE_BIOZLIB + m_LogFile = make_shared(logStream); +#else /* HAVE_BIOZLIB */ + m_LogFile = logStream; +#endif /* HAVE_BIOZLIB */ + m_LogMessageCount = 0; + SetLogMessageTimestamp(Utility::GetTime()); +} + +/* must hold m_LogLock */ +void ApiListener::CloseLogFile(void) +{ + if (!m_LogFile) + return; + + m_LogFile->Close(); + m_LogFile.reset(); +} + +/* must hold m_LogLock */ +void ApiListener::RotateLogFile(void) +{ + double ts = GetLogMessageTimestamp(); + + if (ts == 0) + ts = Utility::GetTime(); + + String oldpath = GetApiDir() + "log/current"; + String newpath = GetApiDir() + "log/" + Convert::ToString(static_cast(ts)+1); + (void) rename(oldpath.CStr(), newpath.CStr()); +} + +void ApiListener::LogGlobHandler(std::vector& files, const String& file) +{ + String name = Utility::BaseName(file); + + int ts; + + try { + ts = Convert::ToLong(name); + } + catch (const std::exception&) { + return; + } + + files.push_back(ts); +} + +void ApiListener::ReplayLog(const ApiClient::Ptr& client) +{ + Endpoint::Ptr endpoint = client->GetEndpoint(); + + CONTEXT("Replaying log for Endpoint '" + endpoint->GetName() + "'"); + + int count = -1; + double peer_ts = endpoint->GetLocalLogPosition(); + bool last_sync = false; + + for (;;) { + boost::mutex::scoped_lock lock(m_LogLock); + + CloseLogFile(); + RotateLogFile(); + + if (count == -1 || count > 50000) { + OpenLogFile(); + lock.unlock(); + } else { + last_sync = true; + } + + count = 0; + + std::vector files; + Utility::Glob(GetApiDir() + "log/*", boost::bind(&ApiListener::LogGlobHandler, boost::ref(files), _1), GlobFile); + std::sort(files.begin(), files.end()); + + BOOST_FOREACH(int ts, files) { + String path = GetApiDir() + "log/" + Convert::ToString(ts); + + if (ts < peer_ts) + continue; + + Log(LogInformation, "cluster", "Replaying log: " + path); + + std::fstream *fp = new std::fstream(path.CStr(), std::fstream::in); + StdioStream::Ptr logStream = make_shared(fp, true); +#ifdef HAVE_BIOZLIB + ZlibStream::Ptr lstream = make_shared(logStream); +#else /* HAVE_BIOZLIB */ + Stream::Ptr lstream = logStream; +#endif /* HAVE_BIOZLIB */ + + String message; + while (true) { + Dictionary::Ptr pmessage; + + try { + if (!NetString::ReadStringFromStream(lstream, &message)) + break; + + pmessage = JsonDeserialize(message); + } catch (const std::exception&) { + Log(LogWarning, "cluster", "Unexpected end-of-file for cluster log: " + path); + + /* Log files may be incomplete or corrupted. This is perfectly OK. */ + break; + } + + if (pmessage->Get("timestamp") <= peer_ts) + continue; + + NetString::WriteStringToStream(client->GetStream(), pmessage->Get("message")); + count++; + + peer_ts = pmessage->Get("timestamp"); + } + + lstream->Close(); + } + + Log(LogInformation, "cluster", "Replayed " + Convert::ToString(count) + " messages."); + + if (last_sync) { + { + ObjectLock olock2(endpoint); + endpoint->SetSyncing(false); + } + + OpenLogFile(); + + break; + } + } +} + +Value ApiListener::StatsFunc(Dictionary::Ptr& status, Dictionary::Ptr& perfdata) +{ + Dictionary::Ptr nodes = make_shared(); + std::pair stats; + + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return 0; + + stats = listener->GetStatus(); + + BOOST_FOREACH(Dictionary::Pair const& kv, stats.second) + perfdata->Set("api_" + kv.first, kv.second); + + status->Set("api", stats.first); + + return 0; +} + +std::pair ApiListener::GetStatus(void) +{ + Dictionary::Ptr status = make_shared(); + Dictionary::Ptr perfdata = make_shared(); + + /* cluster stats */ + status->Set("identity", GetIdentity()); + + double count_endpoints = 0; + Array::Ptr not_connected_endpoints = make_shared(); + Array::Ptr connected_endpoints = make_shared(); + + BOOST_FOREACH(const Endpoint::Ptr& endpoint, DynamicType::GetObjects()) { + if (endpoint->GetName() == GetIdentity()) + continue; + + count_endpoints++; + + if (!endpoint->IsConnected()) + not_connected_endpoints->Add(endpoint->GetName()); + else + connected_endpoints->Add(endpoint->GetName()); + } + + status->Set("num_endpoints", count_endpoints); + status->Set("num_conn_endpoints", connected_endpoints->GetLength()); + status->Set("num_not_conn_endpoints", not_connected_endpoints->GetLength()); + status->Set("conn_endpoints", connected_endpoints); + status->Set("not_conn_endpoints", not_connected_endpoints); + + perfdata->Set("num_endpoints", count_endpoints); + perfdata->Set("num_conn_endpoints", Convert::ToDouble(connected_endpoints->GetLength())); + perfdata->Set("num_not_conn_endpoints", Convert::ToDouble(not_connected_endpoints->GetLength())); + + return std::make_pair(status, perfdata); +} diff --git a/components/agent/agentlistener.h b/lib/remote/apilistener.h similarity index 57% rename from components/agent/agentlistener.h rename to lib/remote/apilistener.h index 6967e3ae8..8fea8dc7a 100644 --- a/components/agent/agentlistener.h +++ b/lib/remote/apilistener.h @@ -17,61 +17,89 @@ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * ******************************************************************************/ -#ifndef AGENTLISTENER_H -#define AGENTLISTENER_H - -#include "agent/agentlistener.th" +#ifndef APILISTENER_H +#define APILISTENER_H + +#include "remote/apilistener.th" +#include "remote/apiclient.h" +#include "remote/endpoint.h" +#include "remote/zone.h" +#include "remote/messageorigin.h" #include "base/dynamicobject.h" #include "base/timer.h" #include "base/array.h" +#include "base/workqueue.h" #include "base/tcpsocket.h" #include "base/tlsstream.h" #include "base/utility.h" #include "base/tlsutility.h" -#include "icinga/service.h" namespace icinga { +class ApiClient; + /** - * @ingroup agent - */ -class AgentListener : public ObjectImpl +* @ingroup remote +*/ +class I2_REMOTE_API ApiListener : public ObjectImpl { public: - DECLARE_PTR_TYPEDEFS(AgentListener); - DECLARE_TYPENAME(AgentListener); + DECLARE_PTR_TYPEDEFS(ApiListener); + DECLARE_TYPENAME(ApiListener); - virtual void Start(void); + static boost::signals2::signal OnMasterChanged; + + static ApiListener::Ptr GetInstance(void); shared_ptr GetSSLContext(void) const; - double GetAgentSeen(const String& agentIdentity); - CheckResult::Ptr GetCheckResult(const String& agentIdentity, const String& hostName, const String& serviceName); + Endpoint::Ptr GetMaster(void) const; + bool IsMaster(void) const; + + static String GetApiDir(void); + + void RelayMessage(const MessageOrigin& origin, const DynamicObject::Ptr& secobj, const Dictionary::Ptr& message, bool log); + + static Value StatsFunc(Dictionary::Ptr& status, Dictionary::Ptr& perfdata); + std::pair GetStatus(void); + +protected: + virtual void OnConfigLoaded(void); + virtual void Start(void); private: shared_ptr m_SSLContext; std::set m_Servers; Timer::Ptr m_Timer; - Dictionary::Ptr m_Results; - - Timer::Ptr m_AgentTimer; - void AgentTimerHandler(void); + void ApiTimerHandler(void); void AddListener(const String& service); void AddConnection(const String& node, const String& service); - void NewClientHandler(const Socket::Ptr& client, TlsRole role); + void NewClientHandler(const Socket::Ptr& client, ConnectionRole role); void ListenerThreadProc(const Socket::Ptr& server); void MessageHandler(const TlsStream::Ptr& sender, const String& identity, const Dictionary::Ptr& message); - static String GetInventoryDir(void); + WorkQueue m_RelayQueue; + WorkQueue m_LogQueue; + + boost::mutex m_LogLock; + Stream::Ptr m_LogFile; + size_t m_LogMessageCount; + + void SyncRelayMessage(const MessageOrigin& origin, const DynamicObject::Ptr& secobj, const Dictionary::Ptr& message, bool log); + void PersistMessage(const Dictionary::Ptr& message); - friend class AgentCheckTask; + void OpenLogFile(void); + void RotateLogFile(void); + void CloseLogFile(void); + static void LogGlobHandler(std::vector& files, const String& file); + void ReplayLog(const ApiClient::Ptr& client); }; } -#endif /* AGENTLISTENER_H */ +#endif /* APILISTENER_H */ diff --git a/components/cluster/clusterlistener.ti b/lib/remote/apilistener.ti similarity index 67% rename from components/cluster/clusterlistener.ti rename to lib/remote/apilistener.ti index 6138ae417..d19d4b992 100644 --- a/components/cluster/clusterlistener.ti +++ b/lib/remote/apilistener.ti @@ -1,19 +1,22 @@ #include "base/dynamicobject.h" -#include "base/application.h" namespace icinga { -class ClusterListener : DynamicObject +class ApiListener : DynamicObject { [config] String cert_path; [config] String key_path; [config] String ca_path; [config] String crl_path; + [config] String bind_host; - [config] String bind_port; - [config] Array::Ptr peers; + [config] String bind_port { + default {{{ return "5665"; }}} + }; + [state] double log_message_timestamp; + String identity; }; diff --git a/lib/remote/endpoint.cpp b/lib/remote/endpoint.cpp index d44d9bd8f..fb01cae88 100644 --- a/lib/remote/endpoint.cpp +++ b/lib/remote/endpoint.cpp @@ -18,116 +18,100 @@ ******************************************************************************/ #include "remote/endpoint.h" +#include "remote/apilistener.h" +#include "remote/apiclient.h" #include "remote/jsonrpc.h" +#include "remote/zone.h" #include "base/application.h" #include "base/dynamictype.h" #include "base/objectlock.h" #include "base/utility.h" #include "base/logger_fwd.h" #include "base/exception.h" -#include "config/configitembuilder.h" using namespace icinga; REGISTER_TYPE(Endpoint); -boost::signals2::signal Endpoint::OnConnected; -boost::signals2::signal Endpoint::OnDisconnected; -boost::signals2::signal Endpoint::OnMessageReceived; +boost::signals2::signal Endpoint::OnConnected; +boost::signals2::signal Endpoint::OnDisconnected; -/** - * Checks whether this endpoint is connected. - * - * @returns true if the endpoint is connected, false otherwise. - */ -bool Endpoint::IsConnected(void) const +void Endpoint::OnConfigLoaded(void) { - return GetClient() != NULL; -} + DynamicObject::OnConfigLoaded(); -bool Endpoint::IsAvailable(void) const -{ - return GetSeen() > Utility::GetTime() - 30; -} + BOOST_FOREACH(const Zone::Ptr& zone, DynamicType::GetObjects()) { + const std::set members = zone->GetEndpoints(); -Stream::Ptr Endpoint::GetClient(void) const -{ - return m_Client; + if (members.find(GetSelf()) != members.end()) { + if (m_Zone) + BOOST_THROW_EXCEPTION(std::runtime_error("Endpoint '" + GetName() + "' is in more than one zone.")); + + m_Zone = zone; + } + } + + if (!m_Zone) + BOOST_THROW_EXCEPTION(std::runtime_error("Endpoint '" + GetName() + "' does not belong to a zone.")); } -void Endpoint::SetClient(const Stream::Ptr& client) +void Endpoint::AddClient(const ApiClient::Ptr& client) { - SetBlockedUntil(Utility::GetTime() + 15); + bool was_master = ApiListener::GetInstance()->IsMaster(); - if (m_Client) - m_Client->Close(); + { + boost::mutex::scoped_lock lock(m_ClientsLock); + m_Clients.insert(client); + } - m_Client = client; + bool is_master = ApiListener::GetInstance()->IsMaster(); - if (client) { - boost::thread thread(boost::bind(&Endpoint::MessageThreadProc, this, client)); - thread.detach(); + if (was_master != is_master) + ApiListener::OnMasterChanged(is_master); - OnConnected(GetSelf()); - Log(LogInformation, "remote", "Endpoint connected: " + GetName()); - } else { - OnDisconnected(GetSelf()); - Log(LogInformation, "remote", "Endpoint disconnected: " + GetName()); - } + OnConnected(GetSelf(), client); } -void Endpoint::SendMessage(const Dictionary::Ptr& message) +void Endpoint::RemoveClient(const ApiClient::Ptr& client) { - Stream::Ptr client = GetClient(); + bool was_master = ApiListener::GetInstance()->IsMaster(); - if (!client) - return; + { + boost::mutex::scoped_lock lock(m_ClientsLock); + m_Clients.erase(client); + } - try { - JsonRpc::SendMessage(client, message); - } catch (const std::exception& ex) { - std::ostringstream msgbuf; - msgbuf << "Error while sending JSON-RPC message for endpoint '" << GetName() << "': " << DiagnosticInformation(ex); - Log(LogWarning, "remote", msgbuf.str()); + bool is_master = ApiListener::GetInstance()->IsMaster(); - m_Client.reset(); + if (was_master != is_master) + ApiListener::OnMasterChanged(is_master); - OnDisconnected(GetSelf()); - Log(LogWarning, "remote", "Endpoint disconnected: " + GetName()); - } + OnDisconnected(GetSelf(), client); } -void Endpoint::MessageThreadProc(const Stream::Ptr& stream) +std::set Endpoint::GetClients(void) const { - Utility::SetThreadName("EndpointMsg"); - - for (;;) { - Dictionary::Ptr message; - - try { - message = JsonRpc::ReadMessage(stream); - } catch (const std::exception& ex) { - Log(LogWarning, "remote", "Error while reading JSON-RPC message for endpoint '" + GetName() + "': " + DiagnosticInformation(ex)); - - m_Client.reset(); - - OnDisconnected(GetSelf()); - Log(LogWarning, "remote", "Endpoint disconnected: " + GetName()); + boost::mutex::scoped_lock lock(m_ClientsLock); + return m_Clients; +} - return; - } +Zone::Ptr Endpoint::GetZone(void) const +{ + return m_Zone; +} - OnMessageReceived(GetSelf(), message); - } +bool Endpoint::IsConnected(void) const +{ + boost::mutex::scoped_lock lock(m_ClientsLock); + return !m_Clients.empty(); } -bool Endpoint::HasFeature(const String& type) const +Endpoint::Ptr Endpoint::GetLocalEndpoint(void) { - Dictionary::Ptr features = GetFeatures(); + ApiListener::Ptr listener = ApiListener::GetInstance(); - if (!features) - return false; + if (!listener) + return Endpoint::Ptr(); - return features->Get(type); + return Endpoint::GetByName(listener->GetIdentity()); } - diff --git a/lib/remote/endpoint.h b/lib/remote/endpoint.h index f7ae17347..9a8bec3c2 100644 --- a/lib/remote/endpoint.h +++ b/lib/remote/endpoint.h @@ -29,12 +29,13 @@ namespace icinga { -class EndpointManager; +class ApiClient; +class Zone; /** * An endpoint that can be used to send and receive messages. * - * @ingroup cluster + * @ingroup remote */ class I2_REMOTE_API Endpoint : public ObjectImpl { @@ -42,26 +43,26 @@ public: DECLARE_PTR_TYPEDEFS(Endpoint); DECLARE_TYPENAME(Endpoint); - static boost::signals2::signal OnConnected; - static boost::signals2::signal OnDisconnected; - static boost::signals2::signal OnMessageReceived; + static boost::signals2::signal&)> OnConnected; + static boost::signals2::signal&)> OnDisconnected; - Stream::Ptr GetClient(void) const; - void SetClient(const Stream::Ptr& client); + void AddClient(const shared_ptr& client); + void RemoveClient(const shared_ptr& client); + std::set > GetClients(void) const; + + shared_ptr GetZone(void) const; bool IsConnected(void) const; - bool IsAvailable(void) const; - void SendMessage(const Dictionary::Ptr& request); + static Endpoint::Ptr GetLocalEndpoint(void); - bool HasFeature(const String& type) const; +protected: + virtual void OnConfigLoaded(void); private: - Stream::Ptr m_Client; - boost::thread m_Thread; - Array::Ptr m_ConnectedEndpoints; - - void MessageThreadProc(const Stream::Ptr& stream); + mutable boost::mutex m_ClientsLock; + std::set > m_Clients; + shared_ptr m_Zone; }; } diff --git a/lib/remote/endpoint.ti b/lib/remote/endpoint.ti index 36d52a4d2..6e1a521d8 100644 --- a/lib/remote/endpoint.ti +++ b/lib/remote/endpoint.ti @@ -6,19 +6,20 @@ namespace icinga class Endpoint : DynamicObject { [config] String host; - [config] String port; - [config] Array::Ptr config_files; - [config] Array::Ptr config_files_recursive; - [config] Array::Ptr accept_config; - [config] int metric; + [config] String port { + default {{{ return "5665"; }}} + }; + [config] double keep_alive { + default {{{ return 300; }}} + }; + [config] double log_duration { + default {{{ return 86400; }}} + }; - [state] double seen; [state] double local_log_position; [state] double remote_log_position; - [state] Dictionary::Ptr features; bool syncing; - double blocked_until; }; } diff --git a/lib/icinga/domain.cpp b/lib/remote/messageorigin.cpp similarity index 89% rename from lib/icinga/domain.cpp rename to lib/remote/messageorigin.cpp index f0a52448c..8369e868c 100644 --- a/lib/icinga/domain.cpp +++ b/lib/remote/messageorigin.cpp @@ -17,14 +17,16 @@ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * ******************************************************************************/ -#include "icinga/domain.h" -#include "base/dynamictype.h" +#include "remote/messageorigin.h" using namespace icinga; -REGISTER_TYPE(Domain); +bool MessageOrigin::IsLocal(void) const +{ + return !FromClient; +} -int Domain::GetPrivileges(const String& instance) const +bool MessageOrigin::IsSameZone(void) const { - return GetAcl()->Get(instance); + return !FromZone; } diff --git a/lib/icinga/domain.h b/lib/remote/messageorigin.h similarity index 80% rename from lib/icinga/domain.h rename to lib/remote/messageorigin.h index 24e97cb55..d96e64fee 100644 --- a/lib/icinga/domain.h +++ b/lib/remote/messageorigin.h @@ -17,30 +17,27 @@ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * ******************************************************************************/ -#ifndef DOMAIN_H -#define DOMAIN_H +#ifndef MESSAGEORIGIN_H +#define MESSAGEORIGIN_H -#include "icinga/i2-icinga.h" -#include "icinga/domain.th" -#include "base/dictionary.h" +#include "remote/zone.h" +#include "remote/apiclient.h" namespace icinga { /** - * A domain. - * - * @ingroup icinga + * @ingroup remote */ -class I2_ICINGA_API Domain : public ObjectImpl +struct I2_REMOTE_API MessageOrigin { -public: - DECLARE_PTR_TYPEDEFS(Domain); - DECLARE_TYPENAME(Domain); + Zone::Ptr FromZone; + ApiClient::Ptr FromClient; - int GetPrivileges(const String& instance) const; + bool IsLocal(void) const; + bool IsSameZone(void) const; }; } -#endif /* DOMAIN_H */ +#endif /* MESSAGEORIGIN_H */ diff --git a/lib/remote/remote-type.conf b/lib/remote/remote-type.conf index e75f017fc..3f8da6042 100644 --- a/lib/remote/remote-type.conf +++ b/lib/remote/remote-type.conf @@ -17,28 +17,34 @@ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * ******************************************************************************/ + %type ApiListener { + %require "cert_path", + %attribute %string "cert_path", + + %require "key_path", + %attribute %string "key_path", + + %require "ca_path", + %attribute %string "ca_path", + + %attribute %string "crl_path", + + %attribute %string "bind_host", + %attribute %string "bind_port" +} + %type Endpoint { - %require "host", %attribute %string "host", - - %require "port", %attribute %string "port", - %attribute %number "metric", - - %attribute %array "config_files" { - %attribute %string "*" - }, + %attribute %number "keep_alive", + %attribute %number "log_duration" +} - %attribute %array "config_files_recursive" { - %attribute %string "*", - %attribute %dictionary "*" { - %attribute %string "path", - %attribute %string "pattern" - } - }, +%type Zone { + %attribute %name(Zone) "parent", - %attribute %array "accept_config" { + %attribute %array "endpoints" { %attribute %name(Endpoint) "*" } } diff --git a/components/cluster/clusterlink.cpp b/lib/remote/zone.cpp similarity index 61% rename from components/cluster/clusterlink.cpp rename to lib/remote/zone.cpp index b52ad8b40..0c7d09ee2 100644 --- a/components/cluster/clusterlink.cpp +++ b/lib/remote/zone.cpp @@ -17,53 +17,56 @@ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * ******************************************************************************/ -#include "cluster/clusterlink.h" +#include "remote/zone.h" +#include "base/application.h" +#include "base/dynamictype.h" +#include "base/objectlock.h" +#include "base/utility.h" +#include "base/logger_fwd.h" +#include "base/exception.h" using namespace icinga; -ClusterLink::ClusterLink(const String& from, const String& to) +REGISTER_TYPE(Zone); + +Zone::Ptr Zone::GetParent(void) const { - if (from < to) { - From = from; - To = to; - } else { - From = to; - To = from; - } + return Zone::GetByName(GetParentRaw()); } -int ClusterLink::GetMetric(void) const +std::set Zone::GetEndpoints(void) const { - int metric = 0; + std::set result; - Endpoint::Ptr fromEp = Endpoint::GetByName(From); - if (fromEp) - metric += fromEp->GetMetric(); + BOOST_FOREACH(const String& endpoint, GetEndpointsRaw()) + result.insert(Endpoint::GetByName(endpoint)); - Endpoint::Ptr toEp = Endpoint::GetByName(To); - if (toEp) - metric += toEp->GetMetric(); - - return metric; + return result; } -bool ClusterLink::operator<(const ClusterLink& other) const +bool Zone::CanAccessObject(const DynamicObject::Ptr& object) const { - if (From < other.From) - return true; + Zone::Ptr object_zone; + + if (dynamic_pointer_cast(object)) + object_zone = static_pointer_cast(object); else - return To < other.To; + Zone::GetByName(object->GetZone()); + + if (!object_zone) + return false; + + while (object_zone) { + if (object_zone.get() == this) + return true; + + object_zone = object_zone->GetParent(); + } + + return false; } -bool ClusterLinkMetricLessComparer::operator()(const ClusterLink& a, const ClusterLink& b) const +Zone::Ptr Zone::GetLocalZone(void) { - int metricA = a.GetMetric(); - int metricB = b.GetMetric(); - - if (metricA < metricB) - return true; - else if (metricB > metricA) - return false; - else - return a < b; + return Endpoint::GetLocalEndpoint()->GetZone(); } diff --git a/components/cluster/clusterlink.h b/lib/remote/zone.h similarity index 75% rename from components/cluster/clusterlink.h rename to lib/remote/zone.h index 9c9e744c8..f17b4ac11 100644 --- a/components/cluster/clusterlink.h +++ b/lib/remote/zone.h @@ -17,33 +17,35 @@ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * ******************************************************************************/ -#ifndef CLUSTERLINK_H -#define CLUSTERLINK_H +#ifndef ZONE_H +#define ZONE_H +#include "remote/zone.th" #include "remote/endpoint.h" +#include "base/array.h" +#include "remote/i2-remote.h" +#include namespace icinga { /** - * @ingroup cluster + * @ingroup remote */ -struct ClusterLink +class I2_REMOTE_API Zone : public ObjectImpl { - String From; - String To; +public: + DECLARE_PTR_TYPEDEFS(Zone); + DECLARE_TYPENAME(Zone); - ClusterLink(const String& from, const String& to); + Zone::Ptr GetParent(void) const; + std::set GetEndpoints(void) const; - int GetMetric(void) const; - bool operator<(const ClusterLink& other) const; -}; + bool CanAccessObject(const DynamicObject::Ptr& object) const; -struct ClusterLinkMetricLessComparer -{ - bool operator()(const ClusterLink& a, const ClusterLink& b) const; + static Zone::Ptr GetLocalZone(void); }; } -#endif /* CLUSTERLINK_H */ +#endif /* ZONE_H */ diff --git a/lib/remote/zone.ti b/lib/remote/zone.ti new file mode 100644 index 000000000..7c1b152ad --- /dev/null +++ b/lib/remote/zone.ti @@ -0,0 +1,12 @@ +#include "base/dynamicobject.h" + +namespace icinga +{ + +class Zone : DynamicObject +{ + [config] String parent (ParentRaw); + [config] Array::Ptr endpoints (EndpointsRaw); +}; + +} diff --git a/test/base-timer.cpp b/test/base-timer.cpp index 1dfaa19f4..e6aa1fb31 100644 --- a/test/base-timer.cpp +++ b/test/base-timer.cpp @@ -60,6 +60,8 @@ static void Callback(int *counter) BOOST_AUTO_TEST_CASE(invoke) { + Utility::Sleep(5); + int counter; Timer::Ptr timer = make_shared(); timer->OnTimerExpired.connect(boost::bind(&Callback, &counter)); diff --git a/test/test.cpp b/test/test.cpp index d83b3ce5e..f4520e7dc 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -20,5 +20,17 @@ #define BOOST_TEST_MAIN #define BOOST_TEST_MODULE icinga2_test +#include "base/application.h" #include +using namespace icinga; + +struct InitLibBase +{ + InitLibBase(void) + { + Application::InitializeBase(); + } +}; + +BOOST_GLOBAL_FIXTURE(InitLibBase);