diff --git a/application/forms/AddNodeForm.php b/application/forms/AddNodeForm.php index 1d87305..6bfe392 100644 --- a/application/forms/AddNodeForm.php +++ b/application/forms/AddNodeForm.php @@ -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'), diff --git a/application/forms/EditNodeForm.php b/application/forms/EditNodeForm.php index 588c6de..e97ae59 100644 --- a/application/forms/EditNodeForm.php +++ b/application/forms/EditNodeForm.php @@ -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'), diff --git a/application/forms/ProcessForm.php b/application/forms/ProcessForm.php index 4cc3bcf..0e0b853 100644 --- a/application/forms/ProcessForm.php +++ b/application/forms/ProcessForm.php @@ -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'), diff --git a/doc/09-Operators.md b/doc/09-Operators.md index 53613f2..58db97c 100644 --- a/doc/09-Operators.md +++ b/doc/09-Operators.md @@ -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 + +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 The `MIN` operator selects the **WORST** state out of the **BEST n** child node states: diff --git a/doc/screenshot/09_operators/0905_deg-operator.jpg b/doc/screenshot/09_operators/0905_deg-operator.jpg new file mode 100644 index 0000000..9dc05a3 Binary files /dev/null and b/doc/screenshot/09_operators/0905_deg-operator.jpg differ diff --git a/library/Businessprocess/BpNode.php b/library/Businessprocess/BpNode.php index 58a7c11..6944f09 100644 --- a/library/Businessprocess/BpNode.php +++ b/library/Businessprocess/BpNode.php @@ -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(); diff --git a/library/Businessprocess/Storage/LegacyConfigParser.php b/library/Businessprocess/Storage/LegacyConfigParser.php index 4142731..17fc8a5 100644 --- a/library/Businessprocess/Storage/LegacyConfigParser.php +++ b/library/Businessprocess/Storage/LegacyConfigParser.php @@ -322,7 +322,7 @@ class LegacyConfigParser } $op = '&'; - if (preg_match_all('~(?hasNode($val)) { $node->addChild($bp->getNode($val)); diff --git a/library/Businessprocess/Storage/LegacyConfigRenderer.php b/library/Businessprocess/Storage/LegacyConfigRenderer.php index ebe9589..7e2e0b2 100644 --- a/library/Businessprocess/Storage/LegacyConfigRenderer.php +++ b/library/Businessprocess/Storage/LegacyConfigRenderer.php @@ -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 !== '&') { diff --git a/test/php/library/Businessprocess/BpNodeTest.php b/test/php/library/Businessprocess/BpNodeTest.php index c1f224b..c3da723 100644 --- a/test/php/library/Businessprocess/BpNodeTest.php +++ b/test/php/library/Businessprocess/BpNodeTest.php @@ -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(); diff --git a/test/php/library/Businessprocess/HostNodeTest.php b/test/php/library/Businessprocess/HostNodeTest.php index 9839959..ef4155d 100644 --- a/test/php/library/Businessprocess/HostNodeTest.php +++ b/test/php/library/Businessprocess/HostNodeTest.php @@ -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(); diff --git a/test/php/library/Businessprocess/Operators/DegradedOperatorTest.php b/test/php/library/Businessprocess/Operators/DegradedOperatorTest.php new file mode 100644 index 0000000..72ed5e5 --- /dev/null +++ b/test/php/library/Businessprocess/Operators/DegradedOperatorTest.php @@ -0,0 +1,159 @@ +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; + } +}