diff --git a/.travis.yml b/.travis.yml
index 2b6b03eb3..d17501f0f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -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
diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md
index 2ea03200a..4abe1ba99 100644
--- a/doc/10-icinga-template-library.md
+++ b/doc/10-icinga-template-library.md
@@ -1637,7 +1637,61 @@ users\_win\_crit | **Optional**. The critical threshold.
## 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.
+
+### 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
+
+`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
+```
+
+
+### 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.
-### 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):
diff --git a/doc/5-service-monitoring.md b/doc/5-service-monitoring.md
index 89f742c56..1ca6f89b2 100644
--- a/doc/5-service-monitoring.md
+++ b/doc/5-service-monitoring.md
@@ -193,7 +193,7 @@ Instead, choose a plugin and configure its parameters and thresholds. The follow
### 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
diff --git a/doc/6-distributed-monitoring.md b/doc/6-distributed-monitoring.md
index 7553cce33..2b8900154 100644
--- a/doc/6-distributed-monitoring.md
+++ b/doc/6-distributed-monitoring.md
@@ -198,9 +198,9 @@ Here is an example of a master setup for the `icinga2-master1.localdomain` node
[root@icinga2-master1.localdomain /]# icinga2 node wizard
Welcome to the Icinga 2 Setup Wizard!
-
+
We'll guide you through all required configuration details.
-
+
Please specify if this is a satellite setup ('n' installs a master setup) [Y/n]: n
Starting the Master setup routine...
Please specify the common name (CN) [icinga2-master1.localdomain]: icinga2-master1.localdomain
@@ -230,7 +230,7 @@ Here is an example of a master setup for the `icinga2-master1.localdomain` node
information/cli: Updating constants file '/etc/icinga2/constants.conf'.
information/cli: Updating constants file '/etc/icinga2/constants.conf'.
Done.
-
+
Now restart your Icinga 2 daemon to finish the installation!
[root@icinga2-master1.localdomain /]# systemctl restart icinga2
@@ -350,9 +350,9 @@ is configured to accept configuration and commands from the master:
[root@icinga2-client1.localdomain /]# icinga2 node wizard
Welcome to the Icinga 2 Setup Wizard!
-
+
We'll guide you through all required configuration details.
-
+
Please specify if this is a satellite setup ('n' installs a master setup) [Y/n]:
Starting the Node setup routine...
Please specify the common name (CN) [icinga2-client1.localdomain]: icinga2-client1.localdomain
@@ -369,22 +369,22 @@ is configured to accept configuration and commands from the master:
information/base: Writing private key to '/etc/icinga2/pki/icinga2-client1.localdomain.key'.
information/base: Writing X509 certificate to '/etc/icinga2/pki/icinga2-client1.localdomain.crt'.
information/cli: Fetching public certificate from master (192.168.56.101, 5665):
-
+
Certificate information:
-
+
Subject: CN = icinga2-master1.localdomain
Issuer: CN = Icinga CA
Valid From: Feb 23 14:45:32 2016 GMT
Valid Until: Feb 19 14:45:32 2031 GMT
Fingerprint: AC 99 8B 2B 3D B0 01 00 E5 21 FA 05 2E EC D5 A9 EF 9E AA E3
-
+
Is this information correct? [y/N]: y
information/cli: Received trusted master certificate.
-
+
Please specify the request ticket generated on your Icinga 2 master.
(Hint: # icinga2 pki ticket --cn 'icinga2-client1.localdomain'): 4f75d2ecd253575fe9180938ebff7cbca262f96e
information/cli: Requesting certificate with ticket '4f75d2ecd253575fe9180938ebff7cbca262f96e'.
-
+
information/cli: Created backup file '/etc/icinga2/pki/icinga2-client1.localdomain.crt.orig'.
information/cli: Writing signed certificate to file '/etc/icinga2/pki/icinga2-client1.localdomain.crt'.
information/cli: Writing CA certificate to file '/etc/icinga2/pki/ca.crt'.
@@ -2133,6 +2133,85 @@ for the requirements.
### 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.
+
+#### 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.
+
+#### 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)
## Advanced Hints
diff --git a/doc/images/distributed-monitoring/icinga2_distributed_windows_nscp_api_drivesize_icingaweb2.png b/doc/images/distributed-monitoring/icinga2_distributed_windows_nscp_api_drivesize_icingaweb2.png
new file mode 100644
index 000000000..940902567
Binary files /dev/null and b/doc/images/distributed-monitoring/icinga2_distributed_windows_nscp_api_drivesize_icingaweb2.png differ
diff --git a/icinga2.spec b/icinga2.spec
index f5f445854..dd36da89e 100644
--- a/icinga2.spec
+++ b/icinga2.spec
@@ -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
diff --git a/itl/command-plugins-windows.conf b/itl/command-plugins-windows.conf
index 2909c698f..a23aa1f02 100644
--- a/itl/command-plugins-windows.conf
+++ b/itl/command-plugins-windows.conf
@@ -19,7 +19,7 @@
object CheckCommand "disk-windows" {
command = [ PluginDir + "/check_disk.exe" ]
-
+
arguments = {
"-w" = {
value = "$disk_win_warn$"
@@ -43,14 +43,14 @@ object CheckCommand "disk-windows" {
description = "Exclude these drives from check"
}
}
-
+
vars.disk_win_unit = "mb"
//The default
}
-
+
object CheckCommand "load-windows" {
command = [ PluginDir + "/check_load.exe" ]
-
+
arguments = {
"-w" = {
value = "$load_win_warn$"
@@ -65,7 +65,7 @@ object CheckCommand "load-windows" {
object CheckCommand "memory-windows" {
command = [ PluginDir + "/check_memory.exe" ]
-
+
arguments = {
"-w" = {
value = "$memory_win_warn$"
@@ -86,7 +86,7 @@ object CheckCommand "memory-windows" {
object CheckCommand "network-windows" {
command = [ PluginDir + "/check_network.exe" ]
-
+
arguments = {
"-w" = {
value = "$network_win_warn$"
@@ -106,7 +106,7 @@ object CheckCommand "network-windows" {
object CheckCommand "perfmon-windows" {
command = [ PluginDir + "/check_perfmon.exe" ]
-
+
arguments = {
"-w" = {
value = "$perfmon_win_warn$"
@@ -135,7 +135,7 @@ object CheckCommand "perfmon-windows" {
}
}
-
+
vars.performance_win_wait = 1000
vars.perfmon_win_type = "double"
//The default values
@@ -168,7 +168,7 @@ template CheckCommand "ping-common-windows" {
description = "Timeout in ms"
}
}
-
+
vars.ping_win_packets = "5"
vars.ping_win_timeout = "1000"
//The default values
@@ -199,7 +199,7 @@ object CheckCommand "ping6-windows" {
object CheckCommand "procs-windows" {
command = [ PluginDir + "/check_procs.exe" ]
-
+
arguments = {
"-w" = {
value = "$procs_win_warn$"
@@ -218,7 +218,7 @@ object CheckCommand "procs-windows" {
object CheckCommand "service-windows" {
command = [ PluginDir + "/check_service.exe" ]
-
+
arguments = {
"-w" = {
set_if = "$service_win_warn$"
@@ -234,7 +234,7 @@ object CheckCommand "service-windows" {
object CheckCommand "swap-windows" {
command = [ PluginDir + "/check_swap.exe" ]
-
+
arguments = {
"-w" = {
value = "$swap_win_warn$"
@@ -249,14 +249,14 @@ object CheckCommand "swap-windows" {
description = "Unit to display swap in"
}
}
-
+
vars.swap_win_unit = "mb"
//The default
}
object CheckCommand "update-windows" {
command = [ PluginDir + "/check_update.exe" ]
-
+
arguments = {
"-w" = {
set_if = "$update_win_warn$"
@@ -277,7 +277,7 @@ object CheckCommand "update-windows" {
object CheckCommand "uptime-windows" {
command = [ PluginDir + "/check_uptime.exe" ]
-
+
arguments = {
"-w" = {
value = "$uptime_win_warn$"
@@ -292,14 +292,14 @@ object CheckCommand "uptime-windows" {
description = "Time unit to use"
}
}
-
+
vars.uptime_win_unit = "s"
//The default
}
object CheckCommand "users-windows" {
command = [ PluginDir + "/check_users.exe" ]
-
+
arguments = {
"-w" = {
value = "$users_win_warn$"
diff --git a/itl/command-plugins.conf b/itl/command-plugins.conf
index 93b7bf84f..12b59a0cb 100644
--- a/itl/command-plugins.conf
+++ b/itl/command-plugins.conf
@@ -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$"
+}
diff --git a/lib/remote/httpresponse.cpp b/lib/remote/httpresponse.cpp
index d66758690..d8c4e0997 100644
--- a/lib/remote/httpresponse.cpp
+++ b/lib/remote/httpresponse.cpp
@@ -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,7 +150,9 @@ bool HttpResponse::Parse(StreamReadContext& src, bool may_wait)
BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported HTTP version"));
StatusCode = Convert::ToLong(tokens[1]);
- StatusMessage = tokens[2]; // TODO: Join tokens[2..end]
+
+ if (tokens.size() >= 3)
+ StatusMessage = tokens[2]; // TODO: Join tokens[2..end]
m_State = HttpResponseHeaders;
} else if (m_State == HttpResponseHeaders) {
diff --git a/lib/remote/url.cpp b/lib/remote/url.cpp
index 6168019a9..b6cdcb8ba 100644
--- a/lib/remote/url.cpp
+++ b/lib/remote/url.cpp
@@ -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)))
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index 77d4a959d..196f73a26 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -15,28 +15,45 @@
# 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 )
-
+
add_library ( thresholds thresholds )
set_target_properties (
thresholds PROPERTIES
INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/icinga2
FOLDER Plugins
)
-
- 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 )
-
- foreach ( source ${check_SOURCES} )
+
+ 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 )
+
+ foreach ( source ${check_SOURCES} )
string ( REGEX REPLACE ".cpp\$" "" check_OUT "${source}" )
string ( REGEX REPLACE ".cpp\$" ".h" check_HEADER "${source}" )
-
+
add_executable ( ${check_OUT} ${source} ${check_HEADER} )
target_link_libraries ( ${check_OUT} thresholds Shlwapi.lib ${Boost_PROGRAM_OPTIONS_LIBRARY} )
-
+
set_target_properties (
${check_OUT} PROPERTIES
INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/icinga2
@@ -53,8 +70,8 @@ if ( WIN32 )
target_link_libraries ( check_users wtsapi32.lib )
install (
- TARGETS check_disk check_load check_memory check_network check_perfmon check_procs
+ TARGETS check_disk check_load check_memory check_network check_perfmon check_procs
check_ping check_service check_swap check_update check_uptime check_users
- RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} )
+ RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} )
endif ( )
diff --git a/plugins/check_nscp_api.cpp b/plugins/check_nscp_api.cpp
new file mode 100644
index 000000000..6ed5d0676
--- /dev/null
+++ b/plugins/check_nscp_api.cpp
@@ -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
+#include
+
+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 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(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()->required(), "REQUIRED: NSCP API Host")
+ ("port,P", po::value()->default_value("8443"), "NSCP API Port (Default: 8443)")
+ ("password", po::value()->required(), "REQUIRED: NSCP API Password")
+ ("query,q", po::value()->required(), "REQUIRED: NSCP API Query endpoint")
+ ("arguments,a", po::value>()->multitoken(), "NSCP API Query arguments for the endpoint");
+
+ po::basic_command_line_parser 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();
+ if (!vm.count("arguments"))
+ endpoint += '/';
+ else {
+ endpoint += '?';
+ for (String argument : vm["arguments"].as>()) {
+ 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(), vm["port"].as(),
+ vm["password"].as(), endpoint);
+
+ // Application::Exit() is the clean way to exit after calling InitializeBase()
+ Application::Exit(FormatOutput(result));
+ return 255;
+}