From ca4fe71d6907fb893e8ab4cca4c3eb8401b1ab09 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Sun, 13 Apr 2014 09:22:27 +0200 Subject: [PATCH] Implement support for auto-discovering services for passive agents. Fixes #6002 --- components/agent/CMakeLists.txt | 3 ++ components/agent/agentlistener.cpp | 68 +++++++++++++++++++++--------- components/agent/agentlistener.h | 4 +- debian/icinga2-common.install | 2 + icinga2.spec | 2 + tools/CMakeLists.txt | 3 ++ tools/icinga2-discover-agent.cmake | 30 +++++++------ tools/icinga2-forget-agent.cmake | 40 ++++++++++++++++++ tools/icinga2-list-agents.cmake | 36 ++++++++++++++++ 9 files changed, 153 insertions(+), 35 deletions(-) create mode 100644 tools/icinga2-forget-agent.cmake create mode 100644 tools/icinga2-list-agents.cmake diff --git a/components/agent/CMakeLists.txt b/components/agent/CMakeLists.txt index 6a956e943..67396c1ec 100644 --- a/components/agent/CMakeLists.txt +++ b/components/agent/CMakeLists.txt @@ -34,3 +34,6 @@ set_target_properties ( 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/agentlistener.cpp b/components/agent/agentlistener.cpp index 469a9b95c..ed216cc44 100644 --- a/components/agent/agentlistener.cpp +++ b/components/agent/agentlistener.cpp @@ -27,6 +27,7 @@ #include "base/networkstream.h" #include "base/application.h" #include "base/context.h" +#include using namespace icinga; @@ -52,7 +53,7 @@ void AgentListener::Start(void) /* 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()); @@ -64,6 +65,11 @@ 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. * @@ -170,15 +176,15 @@ void AgentListener::NewClientHandler(const Socket::Ptr& client, TlsRole role) 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 services = make_shared(); Host::Ptr host = Host::GetByName("localhost"); - + if (!host) Log(LogWarning, "agent", "Agent doesn't have any services for 'localhost'."); else { @@ -186,34 +192,34 @@ void AgentListener::MessageHandler(const TlsStream::Ptr& sender, const String& i services->Set(service->GetShortName(), Serialize(service->GetLastCheckResult())); } } - + Dictionary::Ptr params = make_shared(); params->Set("services", services); params->Set("host", Serialize(host->GetLastCheckResult())); - + Dictionary::Ptr request = make_shared(); request->Set("method", "push_crs"); request->Set("params", params); - + JsonRpc::SendMessage(sender, request); } } - + if (method == "push_crs") { Host::Ptr host = Host::GetByName(identity); - + if (!host) { Log(LogWarning, "agent", "Ignoring check results for host '" + identity + "'."); return; } - + Dictionary::Ptr params = message->Get("params"); - + if (!params) return; Value hostcr = Deserialize(params->Get("host"), true); - + if (!hostcr.IsObjectType()) { Log(LogWarning, "agent", "Ignoring invalid check result for host '" + identity + "'."); } else { @@ -222,30 +228,52 @@ void AgentListener::MessageHandler(const TlsStream::Ptr& sender, const String& i } Dictionary::Ptr services = params->Get("services"); - + if (!services) return; - + Dictionary::Pair kv; - + BOOST_FOREACH(kv, services) { Service::Ptr service = host->GetServiceByShortName(kv.first); - + if (!service) { Log(LogWarning, "agent", "Ignoring check result for service '" + kv.first + "' on host '" + identity + "'."); continue; } - + Value servicecr = Deserialize(kv.second, true); - + if (!servicecr.IsObjectType()) { Log(LogWarning, "agent", "Ignoring invalid check result for service '" + kv.first + "' on host '" + identity + "'."); continue; } - + CheckResult::Ptr cr = servicecr; service->ProcessCheckResult(cr); } + + Dictionary::Ptr inventoryDescr = make_shared(); + inventoryDescr->Set("identity", identity); + inventoryDescr->Set("crs", 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)); + } } } @@ -256,6 +284,6 @@ void AgentListener::AgentTimerHandler(void) if (host.IsEmpty() || port.IsEmpty()) return; - + AddConnection(host, port); } diff --git a/components/agent/agentlistener.h b/components/agent/agentlistener.h index 6025b62ea..d1d862daa 100644 --- a/components/agent/agentlistener.h +++ b/components/agent/agentlistener.h @@ -61,7 +61,9 @@ private: void ListenerThreadProc(const Socket::Ptr& server); void MessageHandler(const TlsStream::Ptr& sender, const String& identity, const Dictionary::Ptr& message); - + + static String GetInventoryDir(void); + friend class AgentCheckTask; }; diff --git a/debian/icinga2-common.install b/debian/icinga2-common.install index f7cd060c8..3843570b2 100644 --- a/debian/icinga2-common.install +++ b/debian/icinga2-common.install @@ -5,4 +5,6 @@ usr/bin/icinga2-migrate-config usr/sbin/icinga2-*-feature usr/sbin/icinga2-setup-agent usr/sbin/icinga2-discover-agent +usr/sbin/icinga2-forget-agent +usr/sbin/icinga2-list-agents usr/share/icinga2 diff --git a/icinga2.spec b/icinga2.spec index e50aa84f2..1cab802ac 100644 --- a/icinga2.spec +++ b/icinga2.spec @@ -426,6 +426,8 @@ exit 0 %{_sbindir}/%{name}-disable-feature %{_sbindir}/%{name}-setup-agent %{_sbindir}/%{name}-discover-agent +%{_sbindir}/%{name}-forget-agent +%{_sbindir}/%{name}-list-agents %exclude %{_libdir}/%{name}/libdb_ido_mysql* %exclude %{_libdir}/%{name}/libdb_ido_pgsql* %{_libdir}/%{name} diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index a8f57613c..fac3f5a17 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -21,9 +21,12 @@ add_subdirectory(mkembedconfig) if(UNIX OR CYGWIN) configure_file(icinga2-enable-feature.cmake ${CMAKE_CURRENT_BINARY_DIR}/icinga2-enable-feature @ONLY) configure_file(icinga2-discover-agent.cmake ${CMAKE_CURRENT_BINARY_DIR}/icinga2-discover-agent @ONLY) + configure_file(icinga2-forget-agent.cmake ${CMAKE_CURRENT_BINARY_DIR}/icinga2-forget-agent @ONLY) + configure_file(icinga2-list-agents.cmake ${CMAKE_CURRENT_BINARY_DIR}/icinga2-list-agents @ONLY) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/icinga2-enable-feature ${CMAKE_CURRENT_BINARY_DIR}/icinga2-discover-agent + ${CMAKE_CURRENT_BINARY_DIR}/icinga2-forget-agent ${CMAKE_CURRENT_BINARY_DIR}/icinga2-list-agents DESTINATION ${CMAKE_INSTALL_SBINDIR} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE ) diff --git a/tools/icinga2-discover-agent.cmake b/tools/icinga2-discover-agent.cmake index 62625085a..af7761887 100644 --- a/tools/icinga2-discover-agent.cmake +++ b/tools/icinga2-discover-agent.cmake @@ -109,17 +109,20 @@ class NetstringParser(object): # along with this program; if not, write to the Free Software Foundation # Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. -import socket, ssl, pprint, sys, json, os +import socket, ssl, sys, json, os, hashlib def warning(*objs): print(*objs, file=sys.stderr) -if len(sys.argv) < 3: - warning("Syntax: %s " % (sys.argv[0])) +if len(sys.argv) < 2: + warning("Syntax: %s []" % (sys.argv[0])) sys.exit(1) host = sys.argv[1] -port = int(sys.argv[2]) +if len(sys.argv) > 2: + port = int(sys.argv[2]) +else: + port = 8483 agentpki = "@CMAKE_INSTALL_FULL_SYSCONFDIR@/icinga2/pki/agent" keyfile = agentpki + "/agent.key" @@ -164,6 +167,8 @@ while True: break nsp.feed(data) +ssl_sock.close() + if len(nsp.results) != 1: warning("Agent returned invalid response: ", repr(nsp.results)) sys.exit(1) @@ -177,14 +182,11 @@ if method != "push_crs": params = response['params'] -for service in params['services']: - print(\ -"""apply Service "%s" { - import "agent-service" +inventory_file = "@CMAKE_INSTALL_FULL_LOCALSTATEDIR@/lib/icinga2/agent/inventory/" + hashlib.sha256(cn).hexdigest() +fp = open(inventory_file, "w") +inventory_info = { "identity": cn, "crs": params } +json.dump(inventory_info, fp) +fp.close() - assign where host.name == "%s" -} -""" % (service, cn)) - -# note that closing the SSLSocket will also close the underlying socket -ssl_sock.close() +print("Inventory information has been updated for agent '%s'." % (cn)) +sys.exit(0) diff --git a/tools/icinga2-forget-agent.cmake b/tools/icinga2-forget-agent.cmake new file mode 100644 index 000000000..8cabe8832 --- /dev/null +++ b/tools/icinga2-forget-agent.cmake @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# 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. + +from __future__ import print_function +import sys, os, hashlib + +def warning(*objs): + print(*objs, file=sys.stderr) + +if len(sys.argv) < 2: + warning("Syntax: %s " % (sys.argv[0])) + sys.exit(1) + +cn = sys.argv[1] + +inventory_file = "@CMAKE_INSTALL_FULL_LOCALSTATEDIR@/lib/icinga2/agent/inventory/" + hashlib.sha256(cn).hexdigest() + +if not os.path.isfile(inventory_file): + warning("There's no inventory file for agent '%s'.") + sys.exit(0) + +os.unlink(inventory_file) + +print("Inventory information has been removed for agent '%s'." % (cn)) +sys.exit(0) diff --git a/tools/icinga2-list-agents.cmake b/tools/icinga2-list-agents.cmake new file mode 100644 index 000000000..6f289ac2f --- /dev/null +++ b/tools/icinga2-list-agents.cmake @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# 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. + +from __future__ import print_function +import sys, os, json + +def warning(*objs): + print(*objs, file=sys.stderr) + +inventory_dir = "@CMAKE_INSTALL_FULL_LOCALSTATEDIR@/lib/icinga2/agent/inventory/" + +inventory = {} + +for root, dirs, files in os.walk(inventory_dir): + for file in files: + fp = open(root + file, "r") + inventory_info = json.load(fp) + inventory[inventory_info["identity"]] = inventory_info["crs"]["services"].keys() + +json.dump(inventory, sys.stdout) +sys.exit(0)