Skip to content

Commit

Permalink
Merge pull request #204 from Icinga:feature/forward_checks_to_interna…
Browse files Browse the repository at this point in the history
…l_api

Feature: Adds experimental feature for internal API checks

Adds experimental feature to forward checks executed by the Icinga Agent to an internal REST-Api, to reduce the performance impact on systems with lower ressources available
  • Loading branch information
LordHepipud authored Feb 24, 2021
2 parents a5a401f + 7b847bd commit 96afdc7
Show file tree
Hide file tree
Showing 19 changed files with 510 additions and 77 deletions.
4 changes: 4 additions & 0 deletions doc/31-Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic
* [#206](https://github.com/Icinga/icinga-powershell-framework/pull/206) Fixes background service check daemon for collecting metrics over time which will no longer share data between configured checks which might cause higher CPU load and a possible memory leak
* [#208](https://github.com/Icinga/icinga-powershell-framework/pull/208) Fixes `Convert-IcingaPluginThresholds` which sometimes did not return proper numeric usable values for our internal functions, causing issues on plugin calls. In addition the function now also supports the handling for % units.

### Experimental

* [#204](https://github.com/Icinga/icinga-powershell-framework/pull/204) Adds experimental feature to forward checks executed by the Icinga Agent to an internal REST-Api, to reduce the performance impact on systems with lower resources available

## 1.3.1 (2021-02-04)

[Issue and PRs](https://github.com/Icinga/icinga-powershell-framework/milestone/12?closed=1)
Expand Down
28 changes: 26 additions & 2 deletions icinga-powershell-framework.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,19 @@
'.\lib\core\logging\Write-IcingaConsoleOutput.psm1',
'.\lib\core\logging\Write-IcingaConsoleNotice.psm1',
'.\lib\core\logging\Write-IcingaConsoleWarning.psm1',
'.\lib\core\tools\Read-IcingaFileContent.psm1'
'.\lib\core\tools\Read-IcingaFileContent.psm1',
'.\lib\core\framework\Invoke-IcingaInternalServiceCall.psm1',
'.\lib\core\framework\Get-IcingaFrameworkApiChecks.psm1',
'.\lib\daemon\Get-IcingaBackgroundDaemons.psm1',
'.\lib\webserver\Enable-IcingaUntrustedCertificateValidation.psm1',
'.\lib\core\logging\Write-IcingaEventMessage.psm1',
'.\lib\icinga\plugin\Exit-IcingaExecutePlugin.psm1',
'.\lib\icinga\exception\Exit-IcingaPluginNotInstalled.psm1',
'.\lib\icinga\exception\Exit-IcingaThrowException.psm1',
'.\lib\web\Disable-IcingaProgressPreference.psm1',
'.\lib\core\tools\New-IcingaNewLine.psm1',
'.\lib\core\logging\Write-IcingaConsolePlain.psm1',
'.\lib\core\tools\Test-IcingaFunction.psm1'
)
FunctionsToExport = @(
'Use-Icinga',
Expand All @@ -39,7 +51,19 @@
'Write-IcingaConsoleOutput',
'Write-IcingaConsoleNotice',
'Write-IcingaConsoleWarning',
'Read-IcingaFileContent'
'Read-IcingaFileContent',
'Invoke-IcingaInternalServiceCall',
'Get-IcingaFrameworkApiChecks',
'Get-IcingaBackgroundDaemons',
'Enable-IcingaUntrustedCertificateValidation',
'Write-IcingaEventMessage',
'Exit-IcingaExecutePlugin',
'Exit-IcingaPluginNotInstalled',
'Exit-IcingaThrowException',
'Disable-IcingaProgressPreference',
'New-IcingaNewLine',
'Write-IcingaConsolePlain',
'Test-IcingaFunction'
)
CmdletsToExport = @('*')
VariablesToExport = '*'
Expand Down
15 changes: 13 additions & 2 deletions icinga-powershell-framework.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,21 @@ function Use-Icinga()
param(
[switch]$LibOnly = $FALSE,
[switch]$Daemon = $FALSE,
[switch]$DebugMode = $FALSE
[switch]$DebugMode = $FALSE,
[switch]$Minimal = $FALSE
);

Disable-IcingaProgressPreference;

if ($Minimal) {
# If we load the minimal Framework files, we have to ensure our enums are loaded
Import-Module ([string]::Format('{0}\lib\icinga\exception\Icinga_IcingaExceptionEnums.psm1', $PSScriptRoot)) -Global;
Import-Module ([string]::Format('{0}\lib\icinga\enums\Icinga_IcingaEnums.psm1', $PSScriptRoot)) -Global;
Import-Module ([string]::Format('{0}\lib\core\logging\Icinga_EventLog_Enums.psm1', $PSScriptRoot)) -Global;

return;
}

# Ensure we autoload the Icinga Plugin collection, provided by the external
# module 'icinga-powershell-plugins'
if (Get-Command 'Use-IcingaPlugins' -ErrorAction SilentlyContinue) {
Expand Down Expand Up @@ -61,7 +73,6 @@ function Use-Icinga()
}
}
New-IcingaPerformanceCounterCache;
Disable-IcingaProgressPreference;

# Enable DebugMode in case it is enabled in our config
if (Get-IcingaFrameworkDebugMode) {
Expand Down
53 changes: 53 additions & 0 deletions lib/core/framework/Add-IcingaFrameworkApiCommand.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<#
.SYNOPSIS
Adds a Cmdlet to an REST-Api endpoint which is either whitelisted or blacklisted.
Whitelisted Cmdlets can be executed over API endpoints, blacklisted not.
Use '*' for wildcard matches
.DESCRIPTION
Adds a Cmdlet to an REST-Api endpoint which is either whitelisted or blacklisted.
Whitelisted Cmdlets can be executed over API endpoints, blacklisted not.
Use '*' for wildcard matches
.FUNCTIONALITY
Enables or disables Cmdlets for REST-Api endpoints
.EXAMPLE
PS>Add-IcingaFrameworkApiCommand -Command 'Invoke-IcingaCheck*' -Endpoint 'checker';
.EXAMPLE
PS>Add-IcingaFrameworkApiCommand -Command 'Invoke-IcingaCheck*' -Endpoint 'checker' -Blacklist;
.LINK
https://github.com/Icinga/icinga-powershell-framework
#>

function Add-IcingaFrameworkApiCommand()
{
param (
[string]$Command = '',
[string]$Endpoint = '',
[switch]$Blacklist = $FALSE
);

if ([string]::IsNullOrEmpty($Command) -Or [string]::IsNullOrEmpty($Endpoint)) {
return;
}

$Commands = $null;
$ConfigPath = ([string]::Format('Framework.RESTApiCommands.{0}.Whitelist', $Endpoint));
[array]$Values = @();

if ($Blacklist) {
$ConfigPath = ([string]::Format('Framework.RESTApiCommands.{0}.Blacklist', $Endpoint));
}

$Commands = Get-IcingaPowerShellConfig -Path $ConfigPath;

if ((Test-IcingaPowerShellConfigItem -ConfigObject $Commands -ConfigKey $Command)) {
return;
}

if ($null -ne $Commands) {
$Values = $Commands;
}

$Values += $Command;

Set-IcingaPowerShellConfig -Path $ConfigPath -Value $Values;
}
19 changes: 19 additions & 0 deletions lib/core/framework/Disable-IcingaFrameworkApiChecks.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<#
.SYNOPSIS
Disables the feature to forward all executed checks to an internal
installed API to run them within a daemon
.DESCRIPTION
Disables the feature to forward all executed checks to an internal
installed API to run them within a daemon
.FUNCTIONALITY
Disables the Icinga for Windows Api checks forwarded
.EXAMPLE
PS>Disable-IcingaFrameworkApiChecks;
.LINK
https://github.com/Icinga/icinga-powershell-framework
#>

function Disable-IcingaFrameworkApiChecks()
{
Set-IcingaPowerShellConfig -Path 'Framework.Experimental.UseApiChecks' -Value $FALSE;
}
21 changes: 21 additions & 0 deletions lib/core/framework/Enable-IcingaFrameworkApiChecks.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<#
.SYNOPSIS
Enables the feature to forward all executed checks to an internal
installed API to run them within a daemon
.DESCRIPTION
Enables the feature to forward all executed checks to an internal
installed API to run them within a daemon
.FUNCTIONALITY
Enables the Icinga for Windows Api checks forwarded
.EXAMPLE
PS>Enable-IcingaFrameworkApiChecks;
.LINK
https://github.com/Icinga/icinga-powershell-framework
#>

function Enable-IcingaFrameworkApiChecks()
{
Set-IcingaPowerShellConfig -Path 'Framework.Experimental.UseApiChecks' -Value $TRUE;

Write-IcingaConsoleWarning 'Experimental Feature: Please ensure to install the packages "icinga-powershell-restapi" and "icinga-powershell-apichecks", install the Icinga for Windows background service and also register the daemon with "Register-IcingaBackgroundDaemon -Command {0}". Afterwards all services will be executed by the background daemon in case it is running.' -Objects "'Start-IcingaWindowsRESTApi'";
}
28 changes: 28 additions & 0 deletions lib/core/framework/Get-IcingaFrameworkApiChecks.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<#
.SYNOPSIS
Fetches the current enable/disable state of the feature
for executing checks of the internal REST-Api
.DESCRIPTION
Fetches the current enable/disable state of the feature
for executing checks of the internal REST-Api
.FUNCTIONALITY
Get the current API check execution configuration of the
Icinga PowerShell Framework
.EXAMPLE
PS>Get-IcingaFrameworkApiChecks;
.LINK
https://github.com/Icinga/icinga-powershell-framework
.OUTPUTS
System.Boolean
#>

function Get-IcingaFrameworkApiChecks()
{
$CodeCaching = Get-IcingaPowerShellConfig -Path 'Framework.Experimental.UseApiChecks';

if ($null -eq $CodeCaching) {
return $FALSE;
}

return $CodeCaching;
}
121 changes: 121 additions & 0 deletions lib/core/framework/Invoke-IcingaInternalServiceCall.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
function Invoke-IcingaInternalServiceCall()
{
param (
[string]$Command = '',
[array]$Arguments = @()
);

# If our Framework is running as daemon, never call our api
if ($global:IcingaDaemonData.FrameworkRunningAsDaemon) {
return;
}

# If the API forward feature is disabled, do nothing
if ((Get-IcingaFrameworkApiChecks) -eq $FALSE) {
return;
}

# Test our Icinga for Windows service. If the service is not installed or not running, execute the plugin locally
$IcingaForWindowsService = (Get-Service 'icingapowershell' -ErrorAction SilentlyContinue);

if ($null -eq $IcingaForWindowsService -Or $IcingaForWindowsService.Status -ne 'Running') {
return;
}

# In case the REST-Api module ist not configured, do nothing
$BackgroundDaemons = Get-IcingaBackgroundDaemons;

if ($null -eq $BackgroundDaemons -Or $BackgroundDaemons.ContainsKey('Start-IcingaWindowsRESTApi') -eq $FALSE) {
return;
}

# If neither 'icinga-powershell-restapi' or 'icinga-powershell-apichecks' is installed, execute the plugin locally
if ((Test-IcingaFunction 'Invoke-IcingaApiChecksRESTCall') -eq $FALSE -Or (Test-IcingaFunction 'Start-IcingaWindowsRESTApi') -eq $FALSE) {
return;
}

$RestApiPort = 5668;
[int]$Timeout = 30;
$Daemon = $BackgroundDaemons['Start-IcingaWindowsRESTApi'];

# Fetch our deamon configuration
if ($Daemon.ContainsKey('-Port')) {
$RestApiPort = $Daemon['-Port'];
} elseif ($Daemon.ContainsKey('Port')) {
$RestApiPort = $Daemon['Port'];
}
if ($Daemon.ContainsKey('-Timeout')) {
$Timeout = $Daemon['-Timeout'];
} elseif ($Daemon.ContainsKey('Timeout')) {
$Timeout = $Daemon['Timeout'];
}

Enable-IcingaUntrustedCertificateValidation -SuppressMessages;

[hashtable]$CommandArguments = @{ };
[hashtable]$DebugArguments = @{ };
[hashtable]$ConvertedArgs = @{ };
[int]$ArgumentIndex = 0;

# Resolve our array arguments provided by $args and build proper check arguments
while ($ArgumentIndex -lt $Arguments.Count) {
$Value = $Arguments[$ArgumentIndex];
[string]$Argument = [string]$Value;
$ArgumentValue = $null;

if ($Value[0] -eq '-') {
if (($ArgumentIndex + 1) -lt $Arguments.Count) {
[string]$NextValue = $Arguments[$ArgumentIndex + 1];
if ($NextValue[0] -eq '-') {
$ArgumentValue = $TRUE;
} else {
$ArgumentValue = $Arguments[$ArgumentIndex + 1];
}
} else {
$ArgumentValue = $TRUE;
}
} else {
$ArgumentIndex += 1;
continue;
}

$Argument = $Argument.Replace('-', '');

$ConvertedArgs.Add($Argument, $ArgumentValue);
$ArgumentIndex += 1;
}

# Now queue the check inside our REST-Api
try {
$ApiResult = Invoke-WebRequest -Method POST -UseBasicParsing -Uri ([string]::Format('https://localhost:{0}/v1/checker?command={1}', $RestApiPort, $Command)) -Body ($CommandArguments | ConvertTo-Json -Depth 100) -ContentType 'application/json' -TimeoutSec $Timeout;
} catch {
# Something went wrong -> fallback to local execution
$ExMsg = $_.Exception.message;
# Fallback to execute plugin locally
Write-IcingaEventMessage -Namespace 'Framework' -EventId 1553 -Objects $ExMsg, $Command, $DebugArguments;
return;
}

# Resolve our result from the API
$IcingaResult = ConvertFrom-Json -InputObject $ApiResult;
$IcingaCR = '';

# In case we didn't receive a check result, fallback to local execution
if ($null -eq $IcingaResult.$Command.checkresult) {
Write-IcingaEventMessage -Namespace 'Framework' -EventId 1553 -Objects 'The check result for the executed command was empty', $Command, $DebugArguments;
return;
}

$IcingaCR = ($IcingaResult.$Command.checkresult.Replace("`r`n", "`n"));

if ($IcingaResult.$Command.perfdata.Count -ne 0) {
$IcingaCR += ' | ';
foreach ($perfdata in $IcingaResult.$Command.perfdata) {
$IcingaCR += $perfdata;
}
}

# Print our response and exit with the provide exit code
Write-IcingaConsolePlain $IcingaCR;
exit $IcingaResult.$Command.exitcode;
}
49 changes: 49 additions & 0 deletions lib/core/framework/Remove-IcingaFrameworkApiCommand.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<#
.SYNOPSIS
Removes a Cmdlet from an REST-Api endpoints whitelist or blacklist.
.DESCRIPTION
Removes a Cmdlet from an REST-Api endpoints whitelist or blacklist.
.FUNCTIONALITY
Removes Cmdlets for REST-Api endpoints
.EXAMPLE
PS>Remove-IcingaFrameworkApiCommand -Command 'Invoke-IcingaCheck*' -Endpoint 'checker';
.EXAMPLE
PS>Add-IcingaFrameworkApiCommand -Command 'Invoke-IcingaCheck*' -Endpoint 'checker' -Blacklist;
.LINK
https://github.com/Icinga/icinga-powershell-framework
#>

function Remove-IcingaFrameworkApiCommand()
{
param (
[string]$Command = '',
[string]$Endpoint = '',
[switch]$Blacklist = $FALSE
);

if ([string]::IsNullOrEmpty($Command) -Or [string]::IsNullOrEmpty($Endpoint)) {
return;
}

$Commands = $null;
$ConfigPath = ([string]::Format('Framework.RESTApiCommands.{0}.Whitelist', $Endpoint));
[array]$Values = @();

if ($Blacklist) {
$ConfigPath = ([string]::Format('Framework.RESTApiCommands.{0}.Blacklist', $Endpoint));
}

$Commands = Get-IcingaPowerShellConfig -Path $ConfigPath;

if ($null -eq $Commands) {
return;
}

foreach ($element in $Commands) {
if ($element.ToLower() -ne $Command.ToLower()) {
$Values += $element;
}
}

Set-IcingaPowerShellConfig -Path $ConfigPath -Value $Values;
}
Loading

0 comments on commit 96afdc7

Please sign in to comment.