Add check_nscp_api plugin for NSClient++ API checks

refs #4721
This commit is contained in:
Jean Flach 2016-10-10 11:42:18 +02:00 committed by Michael Friedrich
parent ba0498e2d7
commit 39c24e9ec9
12 changed files with 549 additions and 52 deletions

View File

@ -21,7 +21,7 @@ addons:
before_script: before_script:
- mkdir build - mkdir build
- cd 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: script:
- make - make

View File

@ -1637,7 +1637,61 @@ users\_win\_crit | **Optional**. The critical threshold.
## <a id="nscp-plugin-check-commands"></a> Plugin Check Commands for NSClient++ ## <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 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: [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. 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 The check command object for NSClient++ is available as `nscp-local`.
Check command object for NSClient++
Custom attributes passed as [command parameters](3-monitoring-basics.md#command-passing-parameters): Custom attributes passed as [command parameters](3-monitoring-basics.md#command-passing-parameters):

View File

@ -193,7 +193,7 @@ Instead, choose a plugin and configure its parameters and thresholds. The follow
### <a id="service-monitoring-windows"></a> Windows Monitoring ### <a id="service-monitoring-windows"></a> Windows Monitoring
* [check_wmi_plus](http://www.edcint.co.nz/checkwmiplus/) * [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 * [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 * vbs and Powershell scripts

View File

@ -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 [root@icinga2-master1.localdomain /]# icinga2 node wizard
Welcome to the Icinga 2 Setup Wizard! Welcome to the Icinga 2 Setup Wizard!
We'll guide you through all required configuration details. 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 Please specify if this is a satellite setup ('n' installs a master setup) [Y/n]: n
Starting the Master setup routine... Starting the Master setup routine...
Please specify the common name (CN) [icinga2-master1.localdomain]: icinga2-master1.localdomain 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'.
information/cli: Updating constants file '/etc/icinga2/constants.conf'. information/cli: Updating constants file '/etc/icinga2/constants.conf'.
Done. Done.
Now restart your Icinga 2 daemon to finish the installation! Now restart your Icinga 2 daemon to finish the installation!
[root@icinga2-master1.localdomain /]# systemctl restart icinga2 [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 [root@icinga2-client1.localdomain /]# icinga2 node wizard
Welcome to the Icinga 2 Setup Wizard! Welcome to the Icinga 2 Setup Wizard!
We'll guide you through all required configuration details. We'll guide you through all required configuration details.
Please specify if this is a satellite setup ('n' installs a master setup) [Y/n]: Please specify if this is a satellite setup ('n' installs a master setup) [Y/n]:
Starting the Node setup routine... Starting the Node setup routine...
Please specify the common name (CN) [icinga2-client1.localdomain]: icinga2-client1.localdomain 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 private key to '/etc/icinga2/pki/icinga2-client1.localdomain.key'.
information/base: Writing X509 certificate to '/etc/icinga2/pki/icinga2-client1.localdomain.crt'. 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): information/cli: Fetching public certificate from master (192.168.56.101, 5665):
Certificate information: Certificate information:
Subject: CN = icinga2-master1.localdomain Subject: CN = icinga2-master1.localdomain
Issuer: CN = Icinga CA Issuer: CN = Icinga CA
Valid From: Feb 23 14:45:32 2016 GMT Valid From: Feb 23 14:45:32 2016 GMT
Valid Until: Feb 19 14:45:32 2031 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 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 Is this information correct? [y/N]: y
information/cli: Received trusted master certificate. information/cli: Received trusted master certificate.
Please specify the request ticket generated on your Icinga 2 master. Please specify the request ticket generated on your Icinga 2 master.
(Hint: # icinga2 pki ticket --cn 'icinga2-client1.localdomain'): 4f75d2ecd253575fe9180938ebff7cbca262f96e (Hint: # icinga2 pki ticket --cn 'icinga2-client1.localdomain'): 4f75d2ecd253575fe9180938ebff7cbca262f96e
information/cli: Requesting certificate with ticket '4f75d2ecd253575fe9180938ebff7cbca262f96e'. information/cli: Requesting certificate with ticket '4f75d2ecd253575fe9180938ebff7cbca262f96e'.
information/cli: Created backup file '/etc/icinga2/pki/icinga2-client1.localdomain.crt.orig'. 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 signed certificate to file '/etc/icinga2/pki/icinga2-client1.localdomain.crt'.
information/cli: Writing CA certificate to file '/etc/icinga2/pki/ca.crt'. information/cli: Writing CA certificate to file '/etc/icinga2/pki/ca.crt'.
@ -2133,6 +2133,85 @@ for the requirements.
### <a id="distributed-monitoring-windows-nscp"></a> Windows Client and NSClient++ ### <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 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 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) 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 :) 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 ## <a id="distributed-monitoring-advanced-hints"></a> Advanced Hints

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -25,6 +25,7 @@
%endif %endif
%define _libexecdir %{_prefix}/lib/ %define _libexecdir %{_prefix}/lib/
%define plugindir %{_libdir}/nagios/plugins
%if "%{_vendor}" == "redhat" %if "%{_vendor}" == "redhat"
%define apachename httpd %define apachename httpd
@ -43,6 +44,7 @@
%endif %endif
%if "%{_vendor}" == "suse" %if "%{_vendor}" == "suse"
%define plugindir %{_prefix}/lib/nagios/plugins
%define apachename apache2 %define apachename apache2
%define apacheconfdir %{_sysconfdir}/apache2/conf.d %define apacheconfdir %{_sysconfdir}/apache2/conf.d
%define apacheuser wwwrun %define apacheuser wwwrun
@ -330,7 +332,7 @@ CMAKE_OPTS="$CMAKE_OPTS \
%endif %endif
%if "%{_vendor}" != "suse" %if "%{_vendor}" != "suse"
CMAKE_OPTS="$CMAKE_OPTS -DICINGA2_PLUGINDIR=%{_libdir}/nagios/plugins" CMAKE_OPTS="$CMAKE_OPTS -DICINGA2_PLUGINDIR=%{plugindir}"
%else %else
%if 0%{?suse_version} < 1310 %if 0%{?suse_version} < 1310
CMAKE_OPTS="$CMAKE_OPTS -DBOOST_LIBRARYDIR=%{_libdir}/boost153 \ CMAKE_OPTS="$CMAKE_OPTS -DBOOST_LIBRARYDIR=%{_libdir}/boost153 \
@ -340,7 +342,7 @@ CMAKE_OPTS="$CMAKE_OPTS -DBOOST_LIBRARYDIR=%{_libdir}/boost153 \
-DBUILD_TESTING=FALSE \ -DBUILD_TESTING=FALSE \
-DBoost_NO_BOOST_CMAKE=TRUE" -DBoost_NO_BOOST_CMAKE=TRUE"
%endif %endif
CMAKE_OPTS="$CMAKE_OPTS -DICINGA2_PLUGINDIR=%{_prefix}/lib/nagios/plugins" CMAKE_OPTS="$CMAKE_OPTS -DICINGA2_PLUGINDIR=%{plugindir}"
%endif %endif
%if 0%{?use_systemd} %if 0%{?use_systemd}
@ -674,6 +676,7 @@ fi
%{_sbindir}/%{name} %{_sbindir}/%{name}
%dir %{_libdir}/%{name}/sbin %dir %{_libdir}/%{name}/sbin
%{_libdir}/%{name}/sbin/%{name} %{_libdir}/%{name}/sbin/%{name}
%{plugindir}/check_nscp_api
%{_datadir}/%{name} %{_datadir}/%{name}
%exclude %{_datadir}/%{name}/include %exclude %{_datadir}/%{name}/include
%{_mandir}/man8/%{name}.8.gz %{_mandir}/man8/%{name}.8.gz

View File

@ -19,7 +19,7 @@
object CheckCommand "disk-windows" { object CheckCommand "disk-windows" {
command = [ PluginDir + "/check_disk.exe" ] command = [ PluginDir + "/check_disk.exe" ]
arguments = { arguments = {
"-w" = { "-w" = {
value = "$disk_win_warn$" value = "$disk_win_warn$"
@ -43,14 +43,14 @@ object CheckCommand "disk-windows" {
description = "Exclude these drives from check" description = "Exclude these drives from check"
} }
} }
vars.disk_win_unit = "mb" vars.disk_win_unit = "mb"
//The default //The default
} }
object CheckCommand "load-windows" { object CheckCommand "load-windows" {
command = [ PluginDir + "/check_load.exe" ] command = [ PluginDir + "/check_load.exe" ]
arguments = { arguments = {
"-w" = { "-w" = {
value = "$load_win_warn$" value = "$load_win_warn$"
@ -65,7 +65,7 @@ object CheckCommand "load-windows" {
object CheckCommand "memory-windows" { object CheckCommand "memory-windows" {
command = [ PluginDir + "/check_memory.exe" ] command = [ PluginDir + "/check_memory.exe" ]
arguments = { arguments = {
"-w" = { "-w" = {
value = "$memory_win_warn$" value = "$memory_win_warn$"
@ -86,7 +86,7 @@ object CheckCommand "memory-windows" {
object CheckCommand "network-windows" { object CheckCommand "network-windows" {
command = [ PluginDir + "/check_network.exe" ] command = [ PluginDir + "/check_network.exe" ]
arguments = { arguments = {
"-w" = { "-w" = {
value = "$network_win_warn$" value = "$network_win_warn$"
@ -106,7 +106,7 @@ object CheckCommand "network-windows" {
object CheckCommand "perfmon-windows" { object CheckCommand "perfmon-windows" {
command = [ PluginDir + "/check_perfmon.exe" ] command = [ PluginDir + "/check_perfmon.exe" ]
arguments = { arguments = {
"-w" = { "-w" = {
value = "$perfmon_win_warn$" value = "$perfmon_win_warn$"
@ -135,7 +135,7 @@ object CheckCommand "perfmon-windows" {
} }
} }
vars.performance_win_wait = 1000 vars.performance_win_wait = 1000
vars.perfmon_win_type = "double" vars.perfmon_win_type = "double"
//The default values //The default values
@ -168,7 +168,7 @@ template CheckCommand "ping-common-windows" {
description = "Timeout in ms" description = "Timeout in ms"
} }
} }
vars.ping_win_packets = "5" vars.ping_win_packets = "5"
vars.ping_win_timeout = "1000" vars.ping_win_timeout = "1000"
//The default values //The default values
@ -199,7 +199,7 @@ object CheckCommand "ping6-windows" {
object CheckCommand "procs-windows" { object CheckCommand "procs-windows" {
command = [ PluginDir + "/check_procs.exe" ] command = [ PluginDir + "/check_procs.exe" ]
arguments = { arguments = {
"-w" = { "-w" = {
value = "$procs_win_warn$" value = "$procs_win_warn$"
@ -218,7 +218,7 @@ object CheckCommand "procs-windows" {
object CheckCommand "service-windows" { object CheckCommand "service-windows" {
command = [ PluginDir + "/check_service.exe" ] command = [ PluginDir + "/check_service.exe" ]
arguments = { arguments = {
"-w" = { "-w" = {
set_if = "$service_win_warn$" set_if = "$service_win_warn$"
@ -234,7 +234,7 @@ object CheckCommand "service-windows" {
object CheckCommand "swap-windows" { object CheckCommand "swap-windows" {
command = [ PluginDir + "/check_swap.exe" ] command = [ PluginDir + "/check_swap.exe" ]
arguments = { arguments = {
"-w" = { "-w" = {
value = "$swap_win_warn$" value = "$swap_win_warn$"
@ -249,14 +249,14 @@ object CheckCommand "swap-windows" {
description = "Unit to display swap in" description = "Unit to display swap in"
} }
} }
vars.swap_win_unit = "mb" vars.swap_win_unit = "mb"
//The default //The default
} }
object CheckCommand "update-windows" { object CheckCommand "update-windows" {
command = [ PluginDir + "/check_update.exe" ] command = [ PluginDir + "/check_update.exe" ]
arguments = { arguments = {
"-w" = { "-w" = {
set_if = "$update_win_warn$" set_if = "$update_win_warn$"
@ -277,7 +277,7 @@ object CheckCommand "update-windows" {
object CheckCommand "uptime-windows" { object CheckCommand "uptime-windows" {
command = [ PluginDir + "/check_uptime.exe" ] command = [ PluginDir + "/check_uptime.exe" ]
arguments = { arguments = {
"-w" = { "-w" = {
value = "$uptime_win_warn$" value = "$uptime_win_warn$"
@ -292,14 +292,14 @@ object CheckCommand "uptime-windows" {
description = "Time unit to use" description = "Time unit to use"
} }
} }
vars.uptime_win_unit = "s" vars.uptime_win_unit = "s"
//The default //The default
} }
object CheckCommand "users-windows" { object CheckCommand "users-windows" {
command = [ PluginDir + "/check_users.exe" ] command = [ PluginDir + "/check_users.exe" ]
arguments = { arguments = {
"-w" = { "-w" = {
value = "$users_win_warn$" value = "$users_win_warn$"

View File

@ -3025,3 +3025,36 @@ object CheckCommand "radius" {
vars.radius_address = "$check_address$" 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$"
}

View File

@ -139,8 +139,8 @@ bool HttpResponse::Parse(StreamReadContext& src, bool may_wait)
boost::algorithm::split(tokens, line, boost::is_any_of(" ")); boost::algorithm::split(tokens, line, boost::is_any_of(" "));
Log(LogDebug, "HttpRequest") Log(LogDebug, "HttpRequest")
<< "line: " << line << ", tokens: " << tokens.size(); << "line: " << line << ", tokens: " << tokens.size();
if (tokens.size() < 3) if (tokens.size() < 2)
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request")); BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP response (Status line)"));
if (tokens[0] == "HTTP/1.0") if (tokens[0] == "HTTP/1.0")
ProtocolVersion = HttpVersion10; ProtocolVersion = HttpVersion10;
@ -150,7 +150,9 @@ bool HttpResponse::Parse(StreamReadContext& src, bool may_wait)
BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported HTTP version")); BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported HTTP version"));
StatusCode = Convert::ToLong(tokens[1]); 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; m_State = HttpResponseHeaders;
} else if (m_State == HttpResponseHeaders) { } else if (m_State == HttpResponseHeaders) {

View File

@ -36,7 +36,9 @@ Url::Url(const String& base_url)
if (url.GetLength() == 0) if (url.GetLength() == 0)
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid URL Empty URL.")); 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 (pHelper != String::NPos) {
if (!ParseScheme(url.SubStr(0, pHelper))) if (!ParseScheme(url.SubStr(0, pHelper)))

View File

@ -15,28 +15,45 @@
# 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.
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 ) if ( WIN32 )
add_definitions ( -DUNICODE -D_UNICODE ) add_definitions ( -DUNICODE -D_UNICODE )
add_library ( thresholds thresholds ) add_library ( thresholds thresholds )
set_target_properties ( set_target_properties (
thresholds PROPERTIES thresholds PROPERTIES
INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/icinga2 INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/icinga2
FOLDER Plugins FOLDER Plugins
) )
list ( APPEND 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_disk.cpp check_load.cpp check_memory.cpp check_network.cpp check_perfmon.cpp
check_procs.cpp check_service.cpp check_swap.cpp check_update.cpp check_uptime.cpp check_users.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} )
foreach ( source ${check_SOURCES} )
string ( REGEX REPLACE ".cpp\$" "" check_OUT "${source}" ) string ( REGEX REPLACE ".cpp\$" "" check_OUT "${source}" )
string ( REGEX REPLACE ".cpp\$" ".h" check_HEADER "${source}" ) string ( REGEX REPLACE ".cpp\$" ".h" check_HEADER "${source}" )
add_executable ( ${check_OUT} ${source} ${check_HEADER} ) add_executable ( ${check_OUT} ${source} ${check_HEADER} )
target_link_libraries ( ${check_OUT} thresholds Shlwapi.lib ${Boost_PROGRAM_OPTIONS_LIBRARY} ) target_link_libraries ( ${check_OUT} thresholds Shlwapi.lib ${Boost_PROGRAM_OPTIONS_LIBRARY} )
set_target_properties ( set_target_properties (
${check_OUT} PROPERTIES ${check_OUT} PROPERTIES
INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/icinga2 INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/icinga2
@ -53,8 +70,8 @@ if ( WIN32 )
target_link_libraries ( check_users wtsapi32.lib ) target_link_libraries ( check_users wtsapi32.lib )
install ( 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 check_ping check_service check_swap check_update check_uptime check_users
RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} ) RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} )
endif ( ) endif ( )

310
plugins/check_nscp_api.cpp Normal file
View File

@ -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;
}