mirror of https://github.com/Icinga/icinga2.git
Implement support for auto-discovering services for passive agents.
Fixes #6002
This commit is contained in:
parent
a00fbca5f4
commit
ca4fe71d69
|
@ -34,3 +34,6 @@ set_target_properties (
|
||||||
|
|
||||||
install(TARGETS agent RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/icinga2)
|
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\")")
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include "base/networkstream.h"
|
#include "base/networkstream.h"
|
||||||
#include "base/application.h"
|
#include "base/application.h"
|
||||||
#include "base/context.h"
|
#include "base/context.h"
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
using namespace icinga;
|
using namespace icinga;
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ void AgentListener::Start(void)
|
||||||
/* create the primary JSON-RPC listener */
|
/* create the primary JSON-RPC listener */
|
||||||
if (!GetBindPort().IsEmpty())
|
if (!GetBindPort().IsEmpty())
|
||||||
AddListener(GetBindPort());
|
AddListener(GetBindPort());
|
||||||
|
|
||||||
m_AgentTimer = make_shared<Timer>();
|
m_AgentTimer = make_shared<Timer>();
|
||||||
m_AgentTimer->OnTimerExpired.connect(boost::bind(&AgentListener::AgentTimerHandler, this));
|
m_AgentTimer->OnTimerExpired.connect(boost::bind(&AgentListener::AgentTimerHandler, this));
|
||||||
m_AgentTimer->SetInterval(GetUpstreamInterval());
|
m_AgentTimer->SetInterval(GetUpstreamInterval());
|
||||||
|
@ -64,6 +65,11 @@ shared_ptr<SSL_CTX> AgentListener::GetSSLContext(void) const
|
||||||
return m_SSLContext;
|
return m_SSLContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String AgentListener::GetInventoryDir(void)
|
||||||
|
{
|
||||||
|
return Application::GetLocalStateDir() + "/lib/icinga2/agent/inventory/";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new JSON-RPC listener on the specified port.
|
* 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)
|
void AgentListener::MessageHandler(const TlsStream::Ptr& sender, const String& identity, const Dictionary::Ptr& message)
|
||||||
{
|
{
|
||||||
CONTEXT("Processing agent message of type '" + message->Get("method") + "'");
|
CONTEXT("Processing agent message of type '" + message->Get("method") + "'");
|
||||||
|
|
||||||
String method = message->Get("method");
|
String method = message->Get("method");
|
||||||
|
|
||||||
if (identity == GetUpstreamName()) {
|
if (identity == GetUpstreamName()) {
|
||||||
if (method == "get_crs") {
|
if (method == "get_crs") {
|
||||||
Dictionary::Ptr services = make_shared<Dictionary>();
|
Dictionary::Ptr services = make_shared<Dictionary>();
|
||||||
|
|
||||||
Host::Ptr host = Host::GetByName("localhost");
|
Host::Ptr host = Host::GetByName("localhost");
|
||||||
|
|
||||||
if (!host)
|
if (!host)
|
||||||
Log(LogWarning, "agent", "Agent doesn't have any services for 'localhost'.");
|
Log(LogWarning, "agent", "Agent doesn't have any services for 'localhost'.");
|
||||||
else {
|
else {
|
||||||
|
@ -186,34 +192,34 @@ void AgentListener::MessageHandler(const TlsStream::Ptr& sender, const String& i
|
||||||
services->Set(service->GetShortName(), Serialize(service->GetLastCheckResult()));
|
services->Set(service->GetShortName(), Serialize(service->GetLastCheckResult()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary::Ptr params = make_shared<Dictionary>();
|
Dictionary::Ptr params = make_shared<Dictionary>();
|
||||||
params->Set("services", services);
|
params->Set("services", services);
|
||||||
params->Set("host", Serialize(host->GetLastCheckResult()));
|
params->Set("host", Serialize(host->GetLastCheckResult()));
|
||||||
|
|
||||||
Dictionary::Ptr request = make_shared<Dictionary>();
|
Dictionary::Ptr request = make_shared<Dictionary>();
|
||||||
request->Set("method", "push_crs");
|
request->Set("method", "push_crs");
|
||||||
request->Set("params", params);
|
request->Set("params", params);
|
||||||
|
|
||||||
JsonRpc::SendMessage(sender, request);
|
JsonRpc::SendMessage(sender, request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (method == "push_crs") {
|
if (method == "push_crs") {
|
||||||
Host::Ptr host = Host::GetByName(identity);
|
Host::Ptr host = Host::GetByName(identity);
|
||||||
|
|
||||||
if (!host) {
|
if (!host) {
|
||||||
Log(LogWarning, "agent", "Ignoring check results for host '" + identity + "'.");
|
Log(LogWarning, "agent", "Ignoring check results for host '" + identity + "'.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary::Ptr params = message->Get("params");
|
Dictionary::Ptr params = message->Get("params");
|
||||||
|
|
||||||
if (!params)
|
if (!params)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Value hostcr = Deserialize(params->Get("host"), true);
|
Value hostcr = Deserialize(params->Get("host"), true);
|
||||||
|
|
||||||
if (!hostcr.IsObjectType<CheckResult>()) {
|
if (!hostcr.IsObjectType<CheckResult>()) {
|
||||||
Log(LogWarning, "agent", "Ignoring invalid check result for host '" + identity + "'.");
|
Log(LogWarning, "agent", "Ignoring invalid check result for host '" + identity + "'.");
|
||||||
} else {
|
} else {
|
||||||
|
@ -222,30 +228,52 @@ void AgentListener::MessageHandler(const TlsStream::Ptr& sender, const String& i
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary::Ptr services = params->Get("services");
|
Dictionary::Ptr services = params->Get("services");
|
||||||
|
|
||||||
if (!services)
|
if (!services)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Dictionary::Pair kv;
|
Dictionary::Pair kv;
|
||||||
|
|
||||||
BOOST_FOREACH(kv, services) {
|
BOOST_FOREACH(kv, services) {
|
||||||
Service::Ptr service = host->GetServiceByShortName(kv.first);
|
Service::Ptr service = host->GetServiceByShortName(kv.first);
|
||||||
|
|
||||||
if (!service) {
|
if (!service) {
|
||||||
Log(LogWarning, "agent", "Ignoring check result for service '" + kv.first + "' on host '" + identity + "'.");
|
Log(LogWarning, "agent", "Ignoring check result for service '" + kv.first + "' on host '" + identity + "'.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value servicecr = Deserialize(kv.second, true);
|
Value servicecr = Deserialize(kv.second, true);
|
||||||
|
|
||||||
if (!servicecr.IsObjectType<CheckResult>()) {
|
if (!servicecr.IsObjectType<CheckResult>()) {
|
||||||
Log(LogWarning, "agent", "Ignoring invalid check result for service '" + kv.first + "' on host '" + identity + "'.");
|
Log(LogWarning, "agent", "Ignoring invalid check result for service '" + kv.first + "' on host '" + identity + "'.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckResult::Ptr cr = servicecr;
|
CheckResult::Ptr cr = servicecr;
|
||||||
service->ProcessCheckResult(cr);
|
service->ProcessCheckResult(cr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Dictionary::Ptr inventoryDescr = make_shared<Dictionary>();
|
||||||
|
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())
|
if (host.IsEmpty() || port.IsEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
AddConnection(host, port);
|
AddConnection(host, port);
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,9 @@ private:
|
||||||
void ListenerThreadProc(const Socket::Ptr& server);
|
void ListenerThreadProc(const Socket::Ptr& server);
|
||||||
|
|
||||||
void MessageHandler(const TlsStream::Ptr& sender, const String& identity, const Dictionary::Ptr& message);
|
void MessageHandler(const TlsStream::Ptr& sender, const String& identity, const Dictionary::Ptr& message);
|
||||||
|
|
||||||
|
static String GetInventoryDir(void);
|
||||||
|
|
||||||
friend class AgentCheckTask;
|
friend class AgentCheckTask;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,4 +5,6 @@ usr/bin/icinga2-migrate-config
|
||||||
usr/sbin/icinga2-*-feature
|
usr/sbin/icinga2-*-feature
|
||||||
usr/sbin/icinga2-setup-agent
|
usr/sbin/icinga2-setup-agent
|
||||||
usr/sbin/icinga2-discover-agent
|
usr/sbin/icinga2-discover-agent
|
||||||
|
usr/sbin/icinga2-forget-agent
|
||||||
|
usr/sbin/icinga2-list-agents
|
||||||
usr/share/icinga2
|
usr/share/icinga2
|
||||||
|
|
|
@ -426,6 +426,8 @@ exit 0
|
||||||
%{_sbindir}/%{name}-disable-feature
|
%{_sbindir}/%{name}-disable-feature
|
||||||
%{_sbindir}/%{name}-setup-agent
|
%{_sbindir}/%{name}-setup-agent
|
||||||
%{_sbindir}/%{name}-discover-agent
|
%{_sbindir}/%{name}-discover-agent
|
||||||
|
%{_sbindir}/%{name}-forget-agent
|
||||||
|
%{_sbindir}/%{name}-list-agents
|
||||||
%exclude %{_libdir}/%{name}/libdb_ido_mysql*
|
%exclude %{_libdir}/%{name}/libdb_ido_mysql*
|
||||||
%exclude %{_libdir}/%{name}/libdb_ido_pgsql*
|
%exclude %{_libdir}/%{name}/libdb_ido_pgsql*
|
||||||
%{_libdir}/%{name}
|
%{_libdir}/%{name}
|
||||||
|
|
|
@ -21,9 +21,12 @@ add_subdirectory(mkembedconfig)
|
||||||
if(UNIX OR CYGWIN)
|
if(UNIX OR CYGWIN)
|
||||||
configure_file(icinga2-enable-feature.cmake ${CMAKE_CURRENT_BINARY_DIR}/icinga2-enable-feature @ONLY)
|
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-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(
|
install(
|
||||||
FILES ${CMAKE_CURRENT_BINARY_DIR}/icinga2-enable-feature ${CMAKE_CURRENT_BINARY_DIR}/icinga2-discover-agent
|
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}
|
DESTINATION ${CMAKE_INSTALL_SBINDIR}
|
||||||
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
|
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
|
||||||
)
|
)
|
||||||
|
|
|
@ -109,17 +109,20 @@ class NetstringParser(object):
|
||||||
# along with this program; if not, write to the Free Software Foundation
|
# along with this program; if not, write to the Free Software Foundation
|
||||||
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
# 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):
|
def warning(*objs):
|
||||||
print(*objs, file=sys.stderr)
|
print(*objs, file=sys.stderr)
|
||||||
|
|
||||||
if len(sys.argv) < 3:
|
if len(sys.argv) < 2:
|
||||||
warning("Syntax: %s <host> <port>" % (sys.argv[0]))
|
warning("Syntax: %s <host> [<port>]" % (sys.argv[0]))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
host = sys.argv[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"
|
agentpki = "@CMAKE_INSTALL_FULL_SYSCONFDIR@/icinga2/pki/agent"
|
||||||
keyfile = agentpki + "/agent.key"
|
keyfile = agentpki + "/agent.key"
|
||||||
|
@ -164,6 +167,8 @@ while True:
|
||||||
break
|
break
|
||||||
nsp.feed(data)
|
nsp.feed(data)
|
||||||
|
|
||||||
|
ssl_sock.close()
|
||||||
|
|
||||||
if len(nsp.results) != 1:
|
if len(nsp.results) != 1:
|
||||||
warning("Agent returned invalid response: ", repr(nsp.results))
|
warning("Agent returned invalid response: ", repr(nsp.results))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
@ -177,14 +182,11 @@ if method != "push_crs":
|
||||||
|
|
||||||
params = response['params']
|
params = response['params']
|
||||||
|
|
||||||
for service in params['services']:
|
inventory_file = "@CMAKE_INSTALL_FULL_LOCALSTATEDIR@/lib/icinga2/agent/inventory/" + hashlib.sha256(cn).hexdigest()
|
||||||
print(\
|
fp = open(inventory_file, "w")
|
||||||
"""apply Service "%s" {
|
inventory_info = { "identity": cn, "crs": params }
|
||||||
import "agent-service"
|
json.dump(inventory_info, fp)
|
||||||
|
fp.close()
|
||||||
|
|
||||||
assign where host.name == "%s"
|
print("Inventory information has been updated for agent '%s'." % (cn))
|
||||||
}
|
sys.exit(0)
|
||||||
""" % (service, cn))
|
|
||||||
|
|
||||||
# note that closing the SSLSocket will also close the underlying socket
|
|
||||||
ssl_sock.close()
|
|
||||||
|
|
|
@ -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 <identity>" % (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)
|
|
@ -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)
|
Loading…
Reference in New Issue