mirror of https://github.com/Icinga/icinga2.git
Merge pull request #5239 from Icinga/feature/check_nscp-4721
Add NSCP API check plugin for NSClient++ HTTP API
This commit is contained in:
commit
c6b375dcbd
|
@ -21,7 +21,7 @@ addons:
|
|||
before_script:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/tmp/icinga2
|
||||
- cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/tmp/icinga2 -DICINGA2_PLUGINDIR=/tmp/icinga2/sbin
|
||||
|
||||
script:
|
||||
- make
|
||||
|
|
|
@ -1637,7 +1637,61 @@ users\_win\_crit | **Optional**. The critical threshold.
|
|||
|
||||
## <a id="nscp-plugin-check-commands"></a> Plugin Check Commands for NSClient++
|
||||
|
||||
Icinga 2 can use the `nscp client` command to run arbitrary NSClient++ checks.
|
||||
There are two methods available for querying NSClient++:
|
||||
|
||||
* Query the [HTTP API](10-icinga-template-library.md#nscp-check-api) locally or remotely (requires a running NSClient++ service)
|
||||
* Run a [local CLI check](10-icinga-template-library.md#nscp-check-local) (does not require NSClient++ as a service)
|
||||
|
||||
Both methods have their advantages and disadvantages. One thing to
|
||||
note: If you rely on performance counter delta calculations such as
|
||||
CPU utilization, please use the HTTP API instead of the CLI sample call.
|
||||
|
||||
### <a id="nscp-check-api"></a> nscp_api
|
||||
|
||||
`check_nscp_api` is part of the Icinga 2 plugins. This plugin is available for
|
||||
both, Windows and Linux/Unix.
|
||||
|
||||
Verify that the ITL CheckCommand is included:
|
||||
|
||||
vim /etc/icinga2/icinga2.conf
|
||||
|
||||
include <plugins>
|
||||
|
||||
`check_nscp_api` runs queries against the NSClient++ API. Therefore NSClient++ needs to have
|
||||
the `webserver` module enabled, configured and loaded.
|
||||
|
||||
You can install the webserver using the following CLI commands:
|
||||
|
||||
./nscp.exe web install
|
||||
./nscp.exe web password — –set icinga
|
||||
|
||||
Now you can define specific [queries](https://docs.nsclient.org/reference/check/CheckHelpers.html#queries)
|
||||
and integrate them into Icinga 2.
|
||||
|
||||
The check plugin `check_nscp_api` can be integrated with the `nscp_api` CheckCommand object:
|
||||
|
||||
Custom attributes:
|
||||
|
||||
Name | Description
|
||||
:----------------------|:----------------------
|
||||
nscp\_api\_host | **Required**. NSCP API host address. Defaults to "$address$" if the host's `address` attribute is set, "$address6$" otherwise.
|
||||
nscp\_api\_port | **Optional**. NSCP API port. Defaults to `8443`.
|
||||
nscp\_api\_passwd | **Required**. NSCP API password. Please check the NSCP documentation for setup details.
|
||||
nscp\_api\_query | **Required**. NSCP API query endpoint. Refer to the NSCP documentation for possible values.
|
||||
nscp\_api\_arguments | **Optional**. NSCP API arguments dictionary either as single strings or key-value pairs using `=`. Refer to the NSCP documentation.
|
||||
|
||||
`nscp_api_arguments` can be used to pass required thresholds to the executed check. The example below
|
||||
checks the CPU utilization and specifies warning and critical thresholds.
|
||||
|
||||
```
|
||||
check_nscp_api --host 10.0.10.148 --password icinga --query check_cpu --arguments show-all warning='load>40' critical='load>30'
|
||||
check_cpu CRITICAL: critical(5m: 48%, 1m: 36%), 5s: 0% | 'total 5m'=48%;40;30 'total 1m'=36%;40;30 'total 5s'=0%;40;30
|
||||
```
|
||||
|
||||
|
||||
### <a id="nscp-check-local"></a> nscp-local
|
||||
|
||||
Icinga 2 can use the `nscp client` command to run arbitrary NSClient++ checks locally on the client.
|
||||
|
||||
You can enable these check commands by adding the following the include directive in your
|
||||
[icinga2.conf](4-configuring-icinga-2.md#icinga2-conf) configuration file:
|
||||
|
@ -1655,9 +1709,7 @@ not be necessary to manually set this constant.
|
|||
|
||||
Note that it is not necessary to run NSClient++ as a Windows service for these commands to work.
|
||||
|
||||
### <a id="nscp-check-local"></a> nscp-local
|
||||
|
||||
Check command object for NSClient++
|
||||
The check command object for NSClient++ is available as `nscp-local`.
|
||||
|
||||
Custom attributes passed as [command parameters](3-monitoring-basics.md#command-passing-parameters):
|
||||
|
||||
|
|
|
@ -193,7 +193,7 @@ Instead, choose a plugin and configure its parameters and thresholds. The follow
|
|||
### <a id="service-monitoring-windows"></a> Windows Monitoring
|
||||
|
||||
* [check_wmi_plus](http://www.edcint.co.nz/checkwmiplus/)
|
||||
* [NSClient++](https://www.nsclient.org) (in combination with the Icinga 2 client as [nscp-local](10-icinga-template-library.md#nscp-plugin-check-commands) check commands)
|
||||
* [NSClient++](https://www.nsclient.org) (in combination with the Icinga 2 client and either [check_nscp_api](10-icinga-template-library.md#nscp-check-api) or [nscp-local](10-icinga-template-library.md#nscp-plugin-check-commands) check commands)
|
||||
* [Icinga 2 Windows Plugins](10-icinga-template-library.md#windows-plugins) (disk, load, memory, network, performance counters, ping, procs, service, swap, updates, uptime, users
|
||||
* vbs and Powershell scripts
|
||||
|
||||
|
|
|
@ -2133,6 +2133,85 @@ for the requirements.
|
|||
|
||||
### <a id="distributed-monitoring-windows-nscp"></a> Windows Client and NSClient++
|
||||
|
||||
There are two methods available for querying NSClient++:
|
||||
|
||||
* Query the [HTTP API](6-distributed-monitoring.md#distributed-monitoring-windows-nscp-check-api) locally or remotely (requires a running NSClient++ service)
|
||||
* Run a [local CLI check](6-distributed-monitoring.md#distributed-monitoring-windows-nscp-check-local) (does not require NSClient++ as a service)
|
||||
|
||||
Both methods have their advantages and disadvantages. One thing to
|
||||
note: If you rely on performance counter delta calculations such as
|
||||
CPU utilization, please use the HTTP API instead of the CLI sample call.
|
||||
|
||||
#### <a id="distributed-monitoring-windows-nscp-check-api"></a> NSCLient++ with check_nscp_api
|
||||
|
||||
The [Windows setup](6-distributed-monitoring.md#distributed-monitoring-setup-client-windows) already allows
|
||||
you to install the NSClient++ package. In addition to the Windows plugins you can
|
||||
use the [nscp_api command](10-icinga-template-library.md#nscp-check-api) provided by the Icinga Template Library (ITL).
|
||||
|
||||
The initial setup for the NSClient++ API and the required arguments
|
||||
is the described in the ITL chapter for the [nscp_api](10-icinga-template-library.md#nscp-check-api) CheckCommand.
|
||||
|
||||
Based on the [master with clients](6-distributed-monitoring.md#distributed-monitoring-master-clients)
|
||||
scenario we'll now add a local nscp check which queries the NSClient++ API to check the free disk space.
|
||||
|
||||
Define a host object called `icinga2-client2.localdomain` on the master. Add the `nscp_api_password`
|
||||
custom attribute and specify the drives to check.
|
||||
|
||||
[root@icinga2-master1.localdomain /]# cd /etc/icinga2/zones.d/master
|
||||
[root@icinga2-master1.localdomain /etc/icinga2/zones.d/master]# vim hosts.conf
|
||||
|
||||
object Host "icinga2-client1.localdomain" {
|
||||
check_command = "hostalive"
|
||||
address = "192.168.56.111"
|
||||
vars.client_endpoint = name //follows the convention that host name == endpoint name
|
||||
vars.os_type = "Windows"
|
||||
vars.nscp_api_password = "icinga"
|
||||
vars.drives = [ "C:", "D:" ]
|
||||
}
|
||||
|
||||
The service checks are generated using an [apply for](3-monitoring-basics.md#using-apply-for)
|
||||
rule based on `host.vars.drives`:
|
||||
|
||||
[root@icinga2-master1.localdomain /etc/icinga2/zones.d/master]# vim services.conf
|
||||
|
||||
apply Service for "nscp-api-" (drive in host.vars.drives) {
|
||||
import "generic-service"
|
||||
|
||||
check_command = "nscp_api"
|
||||
command_endpoint = host.vars.client_endpoint
|
||||
|
||||
//display_name = "nscp-drive-" + drive
|
||||
|
||||
vars.nscp_api_host = "localhost"
|
||||
vars.nscp_api_query = "check_drivesize"
|
||||
vars.nscp_api_password = host.vars.nscp_api_password
|
||||
vars.nscp_api_arguments = [ "drive=" + drive ]
|
||||
|
||||
ignore where host.vars.os_type != "Windows"
|
||||
}
|
||||
|
||||
Validate the configuration and restart Icinga 2.
|
||||
|
||||
[root@icinga2-master1.localdomain /]# icinga2 daemon -C
|
||||
[root@icinga2-master1.localdomain /]# systemctl restart icinga2
|
||||
|
||||
Two new services ("nscp-drive-D:" and "nscp-drive-C:") will be visible in Icinga Web 2.
|
||||
|
||||
![Icinga 2 Distributed Monitoring Windows Client with NSClient++ nscp-api](images/distributed-monitoring/icinga2_distributed_windows_nscp_api_drivesize_icingaweb2.png)
|
||||
|
||||
Note: You can also omit the `command_endpoint` configuration to execute
|
||||
the command on the master. This also requires a different value for `nscp_api_host`
|
||||
which defaults to `host.address`.
|
||||
|
||||
//command_endpoint = host.vars.client_endpoint
|
||||
|
||||
//vars.nscp_api_host = "localhost"
|
||||
|
||||
You can verify the check execution by looking at the `Check Source` attribute
|
||||
in Icinga Web 2 or the REST API.
|
||||
|
||||
#### <a id="distributed-monitoring-windows-nscp-check-local"></a> NSCLient++ with nscp-local
|
||||
|
||||
The [Windows setup](6-distributed-monitoring.md#distributed-monitoring-setup-client-windows) already allows
|
||||
you to install the NSClient++ package. In addition to the Windows plugins you can
|
||||
use the [nscp-local commands](10-icinga-template-library.md#nscp-plugin-check-commands)
|
||||
|
@ -2190,8 +2269,7 @@ Validate the configuration and restart Icinga 2.
|
|||
|
||||
Open Icinga Web 2 and check your newly added Windows NSClient++ check :)
|
||||
|
||||
![Icinga 2 Distributed Monitoring Windows Client with NSClient++](images/distributed-monitoring/icinga2_distributed_windows_nscp_counter_icingaweb2.png)
|
||||
|
||||
![Icinga 2 Distributed Monitoring Windows Client with NSClient++ nscp-local](images/distributed-monitoring/icinga2_distributed_windows_nscp_counter_icingaweb2.png)
|
||||
|
||||
## <a id="distributed-monitoring-advanced-hints"></a> Advanced Hints
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
|
@ -25,6 +25,7 @@
|
|||
%endif
|
||||
|
||||
%define _libexecdir %{_prefix}/lib/
|
||||
%define plugindir %{_libdir}/nagios/plugins
|
||||
|
||||
%if "%{_vendor}" == "redhat"
|
||||
%define apachename httpd
|
||||
|
@ -43,6 +44,7 @@
|
|||
%endif
|
||||
|
||||
%if "%{_vendor}" == "suse"
|
||||
%define plugindir %{_prefix}/lib/nagios/plugins
|
||||
%define apachename apache2
|
||||
%define apacheconfdir %{_sysconfdir}/apache2/conf.d
|
||||
%define apacheuser wwwrun
|
||||
|
@ -330,7 +332,7 @@ CMAKE_OPTS="$CMAKE_OPTS \
|
|||
%endif
|
||||
|
||||
%if "%{_vendor}" != "suse"
|
||||
CMAKE_OPTS="$CMAKE_OPTS -DICINGA2_PLUGINDIR=%{_libdir}/nagios/plugins"
|
||||
CMAKE_OPTS="$CMAKE_OPTS -DICINGA2_PLUGINDIR=%{plugindir}"
|
||||
%else
|
||||
%if 0%{?suse_version} < 1310
|
||||
CMAKE_OPTS="$CMAKE_OPTS -DBOOST_LIBRARYDIR=%{_libdir}/boost153 \
|
||||
|
@ -340,7 +342,7 @@ CMAKE_OPTS="$CMAKE_OPTS -DBOOST_LIBRARYDIR=%{_libdir}/boost153 \
|
|||
-DBUILD_TESTING=FALSE \
|
||||
-DBoost_NO_BOOST_CMAKE=TRUE"
|
||||
%endif
|
||||
CMAKE_OPTS="$CMAKE_OPTS -DICINGA2_PLUGINDIR=%{_prefix}/lib/nagios/plugins"
|
||||
CMAKE_OPTS="$CMAKE_OPTS -DICINGA2_PLUGINDIR=%{plugindir}"
|
||||
%endif
|
||||
|
||||
%if 0%{?use_systemd}
|
||||
|
@ -674,6 +676,7 @@ fi
|
|||
%{_sbindir}/%{name}
|
||||
%dir %{_libdir}/%{name}/sbin
|
||||
%{_libdir}/%{name}/sbin/%{name}
|
||||
%{plugindir}/check_nscp_api
|
||||
%{_datadir}/%{name}
|
||||
%exclude %{_datadir}/%{name}/include
|
||||
%{_mandir}/man8/%{name}.8.gz
|
||||
|
|
|
@ -3025,3 +3025,36 @@ object CheckCommand "radius" {
|
|||
|
||||
vars.radius_address = "$check_address$"
|
||||
}
|
||||
|
||||
object CheckCommand "nscp_api" {
|
||||
import "ipv4-or-ipv6"
|
||||
|
||||
command = [ PluginDir + "/check_nscp_api" ]
|
||||
|
||||
arguments = {
|
||||
"-H" = {
|
||||
value = "$nscp_api_host$"
|
||||
description = "NSCP API host address"
|
||||
required = true
|
||||
}
|
||||
"-P" = {
|
||||
value = "$nscp_api_port$"
|
||||
description = "NSCP API host port. Defaults to 8443."
|
||||
}
|
||||
"--password" = {
|
||||
value = "$nscp_api_password$"
|
||||
description = "NSCP API password"
|
||||
}
|
||||
"-q" = {
|
||||
value = "$nscp_api_query$"
|
||||
description = "NSCPI API Query endpoint to use"
|
||||
}
|
||||
"-a" = {
|
||||
value = "$nscp_api_arguments$"
|
||||
description = "NSCP API Query arguments"
|
||||
repeat_key = true
|
||||
}
|
||||
}
|
||||
|
||||
vars.nscp_api_host = "$check_address$"
|
||||
}
|
||||
|
|
|
@ -139,8 +139,8 @@ bool HttpResponse::Parse(StreamReadContext& src, bool may_wait)
|
|||
boost::algorithm::split(tokens, line, boost::is_any_of(" "));
|
||||
Log(LogDebug, "HttpRequest")
|
||||
<< "line: " << line << ", tokens: " << tokens.size();
|
||||
if (tokens.size() < 3)
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
|
||||
if (tokens.size() < 2)
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP response (Status line)"));
|
||||
|
||||
if (tokens[0] == "HTTP/1.0")
|
||||
ProtocolVersion = HttpVersion10;
|
||||
|
@ -150,6 +150,8 @@ bool HttpResponse::Parse(StreamReadContext& src, bool may_wait)
|
|||
BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported HTTP version"));
|
||||
|
||||
StatusCode = Convert::ToLong(tokens[1]);
|
||||
|
||||
if (tokens.size() >= 3)
|
||||
StatusMessage = tokens[2]; // TODO: Join tokens[2..end]
|
||||
|
||||
m_State = HttpResponseHeaders;
|
||||
|
|
|
@ -36,7 +36,9 @@ Url::Url(const String& base_url)
|
|||
if (url.GetLength() == 0)
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL Empty URL."));
|
||||
|
||||
size_t pHelper = url.Find(":");
|
||||
size_t pHelper = String::NPos;
|
||||
if (url[0] != '/')
|
||||
pHelper = url.Find(":");
|
||||
|
||||
if (pHelper != String::NPos) {
|
||||
if (!ParseScheme(url.SubStr(0, pHelper)))
|
||||
|
|
|
@ -15,6 +15,22 @@
|
|||
# along with this program; if not, write to the Free Software Foundation
|
||||
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
add_executable ( check_nscp_api check_nscp_api.cpp )
|
||||
target_link_libraries ( check_nscp_api ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_SYSTEM_LIBRARY} base remote )
|
||||
set_target_properties (
|
||||
check_nscp_api PROPERTIES
|
||||
INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/icinga2
|
||||
DEFINE_SYMBOL I2_PLUGINS_BUILD
|
||||
FOLDER Plugins )
|
||||
|
||||
# Prefer the PluginDir constant which is set to /sbin on Windows
|
||||
|
||||
if ( WIN32 )
|
||||
install ( TARGETS check_nscp_api RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} )
|
||||
else()
|
||||
install ( TARGETS check_nscp_api RUNTIME DESTINATION ${ICINGA2_PLUGINDIR} )
|
||||
endif()
|
||||
|
||||
if ( WIN32 )
|
||||
|
||||
add_definitions ( -DUNICODE -D_UNICODE )
|
||||
|
@ -27,8 +43,9 @@ if ( WIN32 )
|
|||
)
|
||||
|
||||
list ( APPEND check_SOURCES
|
||||
check_disk.cpp check_load.cpp check_memory.cpp check_network.cpp check_perfmon.cpp check_ping.cpp
|
||||
check_procs.cpp check_service.cpp check_swap.cpp check_update.cpp check_uptime.cpp check_users.cpp )
|
||||
check_disk.cpp check_load.cpp check_memory.cpp check_network.cpp check_perfmon.cpp
|
||||
check_ping.cpp check_procs.cpp check_service.cpp check_swap.cpp check_update.cpp check_uptime.cpp
|
||||
check_users.cpp )
|
||||
|
||||
foreach ( source ${check_SOURCES} )
|
||||
string ( REGEX REPLACE ".cpp\$" "" check_OUT "${source}" )
|
||||
|
|
|
@ -0,0 +1,310 @@
|
|||
/******************************************************************************
|
||||
* Icinga 2 *
|
||||
* Copyright (C) 2012-2017 Icinga Development Team (https://www.icinga.com/) *
|
||||
* *
|
||||
* 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. *
|
||||
******************************************************************************/
|
||||
|
||||
#define VERSION "1.0.0"
|
||||
|
||||
#include "remote/httpclientconnection.hpp"
|
||||
#include "remote/httprequest.hpp"
|
||||
#include "remote/url-characters.hpp"
|
||||
#include "base/application.hpp"
|
||||
#include "base/json.hpp"
|
||||
#include "base/string.hpp"
|
||||
#include "base/exception.hpp"
|
||||
#include <boost/program_options.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
|
||||
using namespace icinga;
|
||||
namespace po = boost::program_options;
|
||||
|
||||
bool l_Debug = false;
|
||||
|
||||
/*
|
||||
* This function is called by an 'HttpRequest' once the server answers. After doing a short check on the 'response' it
|
||||
* decodes it to a Dictionary and then tells 'QueryEndpoint()' that it's done
|
||||
*/
|
||||
static void ResultHttpCompletionCallback(const HttpRequest& request, HttpResponse& response, bool& ready,
|
||||
boost::condition_variable& cv, boost::mutex& mtx, Dictionary::Ptr& result)
|
||||
{
|
||||
String body;
|
||||
char buffer[1024];
|
||||
size_t count;
|
||||
|
||||
while ((count = response.ReadBody(buffer, sizeof(buffer))) > 0)
|
||||
body += String(buffer, buffer + count);
|
||||
|
||||
if (l_Debug) {
|
||||
std::cout << "Received answer\n"
|
||||
<< "\tHTTP code: " << response.StatusCode << "\n"
|
||||
<< "\tHTTP message: '" << response.StatusMessage << "'\n"
|
||||
<< "\tHTTP body: '" << body << "'.\n";
|
||||
}
|
||||
|
||||
// Only try to decode the body if the 'HttpRequest' was successful
|
||||
if (response.StatusCode != 200)
|
||||
result = Dictionary::Ptr();
|
||||
else
|
||||
result = JsonDecode(body);
|
||||
|
||||
// Unlock our mutex, set ready and notify 'QueryEndpoint()'
|
||||
boost::mutex::scoped_lock lock(mtx);
|
||||
ready = true;
|
||||
cv.notify_all();
|
||||
}
|
||||
|
||||
/*
|
||||
* This function takes all the information required to query an nscp instance on
|
||||
* 'host':'port' with 'password'. The String 'endpoint' contains the specific
|
||||
* query name and all the arguments formatted as an URL.
|
||||
*/
|
||||
static Dictionary::Ptr QueryEndpoint(const String& host, const String& port, const String& password,
|
||||
const String& endpoint)
|
||||
{
|
||||
HttpClientConnection::Ptr m_Connection = new HttpClientConnection(host, port, true);
|
||||
|
||||
try {
|
||||
bool ready = false;
|
||||
boost::condition_variable cv;
|
||||
boost::mutex mtx;
|
||||
Dictionary::Ptr result;
|
||||
boost::shared_ptr<HttpRequest> req = m_Connection->NewRequest();
|
||||
req->RequestMethod = "GET";
|
||||
|
||||
// Url() will call Utillity::UnescapeString() which will thrown an exception if it finds a lonely %
|
||||
req->RequestUrl = new Url(endpoint);
|
||||
req->AddHeader("password", password);
|
||||
if (l_Debug)
|
||||
std::cout << "Sending request to 'https://" << host << ":" << port << req->RequestUrl->Format() << "'\n";
|
||||
|
||||
// Submits the request. The 'ResultHttpCompletionCallback' is called once the HttpRequest receives an answer,
|
||||
// which then sets 'ready' to true
|
||||
m_Connection->SubmitRequest(req, boost::bind(ResultHttpCompletionCallback, _1, _2,
|
||||
boost::ref(ready), boost::ref(cv), boost::ref(mtx), boost::ref(result)));
|
||||
|
||||
// We need to spinlock here because our 'HttpRequest' works asynchronous
|
||||
boost::mutex::scoped_lock lock(mtx);
|
||||
while (!ready) {
|
||||
cv.wait(lock);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
// Exceptions should only happen in extreme edge cases we can't recover from
|
||||
std::cout << "Caught exception: " << DiagnosticInformation(ex, false) << '\n';
|
||||
return Dictionary::Ptr();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Takes a Dictionary 'result' and constructs an icinga compliant output string.
|
||||
* If 'result' is not in the expected format it returns 3 ("UNKNOWN") and prints an informative, icinga compliant,
|
||||
* output string.
|
||||
*/
|
||||
static int FormatOutput(const Dictionary::Ptr& result)
|
||||
{
|
||||
if (!result) {
|
||||
std::cout << "UNKNOWN: No data received.\n";
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (l_Debug)
|
||||
std::cout << "\tJSON Body:\n" << result->ToString() << '\n';
|
||||
|
||||
Array::Ptr payloads = result->Get("payload");
|
||||
if (!payloads) {
|
||||
std::cout << "UNKNOWN: Answer format error: Answer is missing 'payload'.\n";
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (payloads->GetLength() == 0) {
|
||||
std::cout << "UNKNOWN: Answer format error: 'payload' was empty.\n";
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (payloads->GetLength() > 1) {
|
||||
std::cout << "UNKNOWN: Answer format error: Multiple payloads are not supported.";
|
||||
return 3;
|
||||
}
|
||||
|
||||
Dictionary::Ptr payload;
|
||||
try {
|
||||
payload = payloads->Get(0);
|
||||
} catch (const std::exception& ex) {
|
||||
std::cout << "UNKNOWN: Answer format error: 'payload' was not a Dictionary.\n";
|
||||
return 3;
|
||||
}
|
||||
|
||||
Array::Ptr lines;
|
||||
try {
|
||||
lines = payload->Get("lines");
|
||||
} catch (const std::exception&) {
|
||||
std::cout << "UNKNOWN: Answer format error: 'payload' is missing 'lines'.\n";
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (!lines) {
|
||||
std::cout << "UNKNOWN: Answer format error: 'lines' is Null.\n";
|
||||
return 3;
|
||||
}
|
||||
|
||||
std::stringstream ssout;
|
||||
ObjectLock olock(lines);
|
||||
|
||||
for (const Value& vline : lines) {
|
||||
Dictionary::Ptr line;
|
||||
try {
|
||||
line = vline;
|
||||
} catch (const std::exception& ex) {
|
||||
std::cout << "UNKNOWN: Answer format error: 'lines' entry was not a Dictionary.\n";
|
||||
return 3;
|
||||
}
|
||||
if (!line) {
|
||||
std::cout << "UNKNOWN: Answer format error: 'lines' entry was Null.\n";
|
||||
return 3;
|
||||
}
|
||||
|
||||
ssout << payload->Get("command") << ' ' << line->Get("message") << " | ";
|
||||
|
||||
if (!line->Contains("perf")) {
|
||||
ssout << '\n';
|
||||
break;
|
||||
}
|
||||
|
||||
Array::Ptr perfs = line->Get("perf");
|
||||
ObjectLock olock(perfs);
|
||||
|
||||
for (const Dictionary::Ptr& perf : perfs) {
|
||||
ssout << "'" << perf->Get("alias") << "'=";
|
||||
Dictionary::Ptr values = perf->Contains("int_value") ? perf->Get("int_value") : perf->Get("float_value");
|
||||
ssout << values->Get("value") << values->Get("unit") << ';' << values->Get("warning") << ';' << values->Get("critical");
|
||||
|
||||
if (values->Contains("minimum") || values->Contains("maximum")) {
|
||||
ssout << ';';
|
||||
|
||||
if (values->Contains("minimum"))
|
||||
ssout << values->Get("minimum");
|
||||
|
||||
if (values->Contains("maximum"))
|
||||
ssout << ';' << values->Get("maximum");
|
||||
}
|
||||
|
||||
ssout << ' ';
|
||||
}
|
||||
|
||||
ssout << '\n';
|
||||
}
|
||||
|
||||
//TODO: Fix
|
||||
String state = static_cast<String>(payload->Get("result")).ToUpper();
|
||||
int creturn = state == "OK" ? 0 :
|
||||
state == "WARNING" ? 1 :
|
||||
state == "CRITICAL" ? 2 :
|
||||
state == "UNKNOWN" ? 3 : 4;
|
||||
|
||||
if (creturn == 4) {
|
||||
std::cout << "check_nscp UNKNOWN Answer format error: 'result' was not a known state.\n";
|
||||
return 3;
|
||||
}
|
||||
|
||||
std::cout << ssout.rdbuf();
|
||||
return creturn;
|
||||
}
|
||||
|
||||
/*
|
||||
* Process arguments, initialize environment and shut down gracefully.
|
||||
*/
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
po::variables_map vm;
|
||||
po::options_description desc("Options");
|
||||
|
||||
desc.add_options()
|
||||
("help,h", "Print usage message and exit")
|
||||
("version,V", "Print version and exit")
|
||||
("debug,d", "Verbose/Debug output")
|
||||
("host,H", po::value<String>()->required(), "REQUIRED: NSCP API Host")
|
||||
("port,P", po::value<String>()->default_value("8443"), "NSCP API Port (Default: 8443)")
|
||||
("password", po::value<String>()->required(), "REQUIRED: NSCP API Password")
|
||||
("query,q", po::value<String>()->required(), "REQUIRED: NSCP API Query endpoint")
|
||||
("arguments,a", po::value<std::vector<String>>()->multitoken(), "NSCP API Query arguments for the endpoint");
|
||||
|
||||
po::basic_command_line_parser<char> parser(argc, argv);
|
||||
|
||||
try {
|
||||
po::store(
|
||||
parser
|
||||
.options(desc)
|
||||
.style(
|
||||
po::command_line_style::unix_style |
|
||||
po::command_line_style::allow_long_disguise)
|
||||
.run(),
|
||||
vm);
|
||||
|
||||
if (vm.count("version")) {
|
||||
std::cout << "Version: " << VERSION << '\n';
|
||||
Application::Exit(0);
|
||||
}
|
||||
|
||||
if (vm.count("help")) {
|
||||
std::cout << argv[0] << " Help\n\tVersion: " << VERSION << '\n';
|
||||
std::cout << "check_nscp_api is a program used to query the NSClient++ API.\n";
|
||||
std::cout << desc;
|
||||
std::cout << "For detailed information on possible queries and their arguments refer to the NSClient++ documentation.\n";
|
||||
Application::Exit(0);
|
||||
}
|
||||
|
||||
vm.notify();
|
||||
} catch (std::exception& e) {
|
||||
std::cout << e.what() << '\n' << desc << '\n';
|
||||
Application::Exit(3);
|
||||
}
|
||||
|
||||
if (vm.count("debug")) {
|
||||
l_Debug = true;
|
||||
}
|
||||
|
||||
// Create the URL string and escape certain characters since Url() follows RFC 3986
|
||||
String endpoint = "/query/" + vm["query"].as<String>();
|
||||
if (!vm.count("arguments"))
|
||||
endpoint += '/';
|
||||
else {
|
||||
endpoint += '?';
|
||||
for (String argument : vm["arguments"].as<std::vector<String>>()) {
|
||||
String::SizeType pos = argument.FindFirstOf("=");
|
||||
if (pos == String::NPos)
|
||||
endpoint += Utility::EscapeString(argument, ACQUERY_ENCODE, false);
|
||||
else {
|
||||
String key = argument.SubStr(0, pos);
|
||||
String val = argument.SubStr(pos + 1);
|
||||
endpoint += Utility::EscapeString(key, ACQUERY_ENCODE, false) + "=" + Utility::EscapeString(val, ACQUERY_ENCODE, false);
|
||||
}
|
||||
endpoint += '&';
|
||||
}
|
||||
}
|
||||
|
||||
// This needs to happen for HttpRequest to work
|
||||
Application::InitializeBase();
|
||||
|
||||
Dictionary::Ptr result = QueryEndpoint(vm["host"].as<String>(), vm["port"].as<String>(),
|
||||
vm["password"].as<String>(), endpoint);
|
||||
|
||||
// Application::Exit() is the clean way to exit after calling InitializeBase()
|
||||
Application::Exit(FormatOutput(result));
|
||||
return 255;
|
||||
}
|
Loading…
Reference in New Issue