Add Degraded operator implementation

Refs #298
This commit is contained in:
Valentina Da Rold 2021-07-15 11:24:37 +02:00 committed by Mattia Codato
parent 3fe1c9d1a7
commit b78983f539
11 changed files with 194 additions and 13 deletions

View file

@ -117,6 +117,7 @@ class AddNodeForm extends QuickForm
'&' => $this->translate('AND'),
'|' => $this->translate('OR'),
'!' => $this->translate('NOT'),
'%' => $this->translate('DEGRADED'),
'1' => $this->translate('MIN 1'),
'2' => $this->translate('MIN 2'),
'3' => $this->translate('MIN 3'),

View file

@ -114,6 +114,7 @@ class EditNodeForm extends QuickForm
'&' => $this->translate('AND'),
'|' => $this->translate('OR'),
'!' => $this->translate('NOT'),
'%' => $this->translate('DEGRADED'),
'1' => $this->translate('MIN 1'),
'2' => $this->translate('MIN 2'),
'3' => $this->translate('MIN 3'),

View file

@ -61,6 +61,7 @@ class ProcessForm extends QuickForm
'&' => $this->translate('AND'),
'|' => $this->translate('OR'),
'!' => $this->translate('NOT'),
'%' => $this->translate('DEGRADED'),
'1' => $this->translate('MIN 1'),
'2' => $this->translate('MIN 2'),
'3' => $this->translate('MIN 3'),

View file

@ -17,6 +17,15 @@ The `OR` operator selects the **BEST** state of its child nodes:
![Or Operator #2](screenshot/09_operators/0903_or-operator-without-ok.png)
## DEGRADED <a id="deg-operator">
The `DEGRADED` operator behaves like an `AND`, but if the resulting
state is **CRITICAL** it transforms it into a **WARNING**.
Refer to the table below for the case-by-case
analysis of the statuses.
![Degraded Operator](screenshot/09_operators/0905_deg-operator.jpg)
## MIN n <a id="min-operator">
The `MIN` operator selects the **WORST** state out of the **BEST n** child node states:

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View file

@ -11,6 +11,7 @@ class BpNode extends Node
const OP_AND = '&';
const OP_OR = '|';
const OP_NOT = '!';
const OP_DEGRADED = '%';
protected $operator = '&';
protected $url;
@ -289,6 +290,7 @@ class BpNode extends Node
case self::OP_AND:
case self::OP_OR:
case self::OP_NOT:
case self::OP_DEGRADED:
return;
default:
if (is_numeric($operator)) {
@ -460,6 +462,15 @@ class BpNode extends Node
case self::OP_OR:
$sort_state = min($sort_states);
break;
case self::OP_DEGRADED:
$maxState = max($sort_states);
$flags = $maxState & 0xf;
$maxIcingaState = $this->sortStateTostate($maxState);
$warningState = ($this->stateToSortState(self::ICINGA_WARNING) << self::SHIFT_FLAGS) + $flags;
$sort_state = ($maxIcingaState === self::ICINGA_CRITICAL) ? $warningState : $maxState;
break;
default:
// MIN:
$sort_state = 3 << self::SHIFT_FLAGS;
@ -624,6 +635,9 @@ class BpNode extends Node
case self::OP_NOT:
return 'NOT';
break;
case self::OP_DEGRADED:
return 'DEG';
break;
default:
// MIN
$this->assertNumericOperator();

View file

@ -322,7 +322,7 @@ class LegacyConfigParser
}
$op = '&';
if (preg_match_all('~(?<!\\\\)([\|\+&\!])~', $value, $m)) {
if (preg_match_all('~(?<!\\\\)([\|\+&\!\%])~', $value, $m)) {
$op = implode('', $m[1]);
for ($i = 1; $i < strlen($op); $i++) {
if ($op[$i] !== $op[$i - 1]) {
@ -351,7 +351,7 @@ class LegacyConfigParser
$cmps = preg_split('~\s*(?<!\\\\)\\' . $op . '\s*~', $value, -1, PREG_SPLIT_NO_EMPTY);
foreach ($cmps as $val) {
$val = preg_replace('~(\\\\([\|\+&\!]))~', '$2', $val);
$val = preg_replace('~(\\\\([\|\+&\!\%]))~', '$2', $val);
if (strpos($val, ';') !== false) {
if ($bp->hasNode($val)) {
$node->addChild($bp->getNode($val));

View file

@ -186,7 +186,7 @@ class LegacyConfigRenderer
$op = static::renderOperator($node);
$children = $node->getChildNames();
$str = implode(' ' . $op . ' ', array_map(function ($val) {
return preg_replace('~([\|\+&\!])~', '\\\\$1', $val);
return preg_replace('~([\|\+&\!\%])~', '\\\\$1', $val);
}, $children));
if ((count($children) < 2) && $op !== '&') {

View file

@ -7,22 +7,20 @@ use Icinga\Module\Businessprocess\Test\BaseTestCase;
class BpNodeTest extends BaseTestCase
{
/**
* @expectedException \Icinga\Module\Businessprocess\Exception\NestingError
*/
public function testThrowsNestingErrorWhenCheckedForLoops()
{
$this->expectException(\Icinga\Module\Businessprocess\Exception\NestingError::class);
/** @var BpNode $bpNode */
$bpNode = $this->makeLoop()->getNode('d');
$bpNode->checkForLoops();
}
/**
* @expectedExceptionMessage d -> a -> b -> c -> a
* @expectedException \Icinga\Module\Businessprocess\Exception\NestingError
*/
public function testNestingErrorReportsFullLoop()
{
$this->expectException(\Icinga\Module\Businessprocess\Exception\NestingError::class);
$this->expectExceptionMessage('d -> a -> b -> c -> a');
/** @var BpNode $bpNode */
$bpNode = $this->makeLoop()->getNode('d');
$bpNode->checkForLoops();

View file

@ -41,11 +41,9 @@ class HostNodeTest extends BaseTestCase
);
}
/**
* @expectedException \Icinga\Exception\ProgrammingError
*/
public function testSettingAnInvalidStateFails()
{
$this->expectException(\Icinga\Exception\ProgrammingError::class);
$bp = new BpConfig();
$host = $bp->createHost('localhost')->setState(98);
$bp->createBp('p')->addChild($host)->getState();

View file

@ -0,0 +1,159 @@
<?php
namespace Tests\Icinga\Module\Businessprocess\Operator;
use Icinga\Module\Businessprocess\BpConfig;
use Icinga\Module\Businessprocess\Test\BaseTestCase;
use Icinga\Module\Businessprocess\Storage\LegacyStorage;
class DegradedOperatorTest extends BaseTestCase
{
public function testDegradedOperatorCanBeParsed()
{
$storage = new LegacyStorage($this->emptyConfigSection());
$expressions = [
'a = b;c',
'a = b;c % c;d % d;e',
];
foreach ($expressions as $expression) {
$this->assertInstanceOf(
'Icinga\\Module\\Businessprocess\\BpConfig',
$storage->loadFromString('dummy', $expression)
);
}
}
public function testThreeTimesCriticalIsWarning()
{
$bp = $this->getBp();
$bp->setNodeState('b;c', 2);
$bp->setNodeState('c;d', 2);
$bp->setNodeState('d;e', 2);
$this->assertEquals(
'WARNING',
$bp->getNode('a')->getStateName()
);
}
public function testTwoTimesCriticalAndOkIsWarning()
{
$bp = $this->getBp();
$bp->setNodeState('b;c', 2);
$bp->setNodeState('c;d', 0);
$bp->setNodeState('d;e', 2);
$this->assertEquals(
'WARNING',
$bp->getNode('a')->getStateName()
);
}
public function testCriticalAndWarningAndOkIsWarning()
{
$bp = $this->getBp();
$bp->setNodeState('b;c', 2);
$bp->setNodeState('c;d', 1);
$bp->setNodeState('d;e', 0);
$this->assertEquals(
'WARNING',
$bp->getNode('a')->getStateName()
);
}
public function testUnknownAndWarningAndOkIsUnknown()
{
$bp = $this->getBp();
$bp->setNodeState('b;c', 0);
$bp->setNodeState('c;d', 1);
$bp->setNodeState('d;e', 3);
$this->assertEquals(
'UNKNOWN',
$bp->getNode('a')->getStateName()
);
}
public function testTwoTimesWarningAndOkIsWarning()
{
$bp = $this->getBp();
$bp->setNodeState('b;c', 0);
$bp->setNodeState('c;d', 1);
$bp->setNodeState('d;e', 1);
$this->assertEquals(
'WARNING',
$bp->getNode('a')->getStateName()
);
}
public function testUnknownAndWarningAndCriticalIsWarning()
{
$bp = $this->getBp();
$bp->setNodeState('b;c', 2);
$bp->setNodeState('c;d', 1);
$bp->setNodeState('d;e', 3);
$this->assertEquals(
'WARNING',
$bp->getNode('a')->getStateName()
);
}
public function testThreeTimesOkIsOk()
{
$bp = $this->getBp();
$bp->setNodeState('b;c', 0);
$bp->setNodeState('c;d', 0);
$bp->setNodeState('d;e', 0);
$this->assertEquals(
'OK',
$bp->getNode('a')->getStateName()
);
}
public function testSimpleDegOperationWorksCorrectly()
{
$bp = new BpConfig();
$bp->throwErrors();
$host = $bp->createHost('localhost')->setState(0);
$service = $bp->createService('localhost', 'ping')->setState(2);
$p = $bp->createBp('p');
$p->setOperator('%');
$p->addChild($host);
$p->addChild($service);
$this->assertEquals(
'UP',
$host->getStateName()
);
$this->assertEquals(
'CRITICAL',
$service->getStateName()
);
$this->assertEquals(
'WARNING',
$p->getStateName()
);
}
/**
* @return BpConfig
*/
protected function getBp()
{
$storage = new LegacyStorage($this->emptyConfigSection());
$expression = 'a = b;c % c;d % d;e';
$bp = $storage->loadFromString('dummy', $expression);
$bp->createBp('b');
$bp->createBp('c');
$bp->createBp('d');
return $bp;
}
}