diff --git a/application/forms/AddNodeForm.php b/application/forms/AddNodeForm.php
index 43afa4c..5ef11c7 100644
--- a/application/forms/AddNodeForm.php
+++ b/application/forms/AddNodeForm.php
@@ -7,6 +7,7 @@ use Icinga\Module\Businessprocess\BpConfig;
use Icinga\Module\Businessprocess\Common\EnumList;
use Icinga\Module\Businessprocess\ImportedNode;
use Icinga\Module\Businessprocess\Modification\ProcessChanges;
+use Icinga\Module\Businessprocess\Node;
use Icinga\Module\Businessprocess\Storage\Storage;
use Icinga\Module\Businessprocess\Web\Form\QuickForm;
use Icinga\Module\Businessprocess\Web\Form\Validator\NoDuplicateChildrenValidator;
@@ -113,21 +114,7 @@ class AddNodeForm extends QuickForm
$this->addElement('select', 'operator', array(
'label' => $this->translate('Operator'),
'required' => true,
- 'multiOptions' => array(
- '&' => $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'),
- '4' => $this->translate('MIN 4'),
- '5' => $this->translate('MIN 5'),
- '6' => $this->translate('MIN 6'),
- '7' => $this->translate('MIN 7'),
- '8' => $this->translate('MIN 8'),
- '9' => $this->translate('MIN 9'),
- )
+ 'multiOptions' => Node::getOperators()
));
$display = 1;
diff --git a/application/forms/EditNodeForm.php b/application/forms/EditNodeForm.php
index eceb065..f26dd04 100644
--- a/application/forms/EditNodeForm.php
+++ b/application/forms/EditNodeForm.php
@@ -111,21 +111,7 @@ class EditNodeForm extends QuickForm
$this->addElement('select', 'operator', array(
'label' => $this->translate('Operator'),
'required' => true,
- 'multiOptions' => array(
- '&' => $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'),
- '4' => $this->translate('MIN 4'),
- '5' => $this->translate('MIN 5'),
- '6' => $this->translate('MIN 6'),
- '7' => $this->translate('MIN 7'),
- '8' => $this->translate('MIN 8'),
- '9' => $this->translate('MIN 9'),
- )
+ 'multiOptions' => Node::getOperators()
));
$display = $this->getNode()->getDisplay() ?: 1;
diff --git a/application/forms/ProcessForm.php b/application/forms/ProcessForm.php
index be1abbf..604b774 100644
--- a/application/forms/ProcessForm.php
+++ b/application/forms/ProcessForm.php
@@ -5,6 +5,7 @@ namespace Icinga\Module\Businessprocess\Forms;
use Icinga\Module\Businessprocess\BpNode;
use Icinga\Module\Businessprocess\BpConfig;
use Icinga\Module\Businessprocess\Modification\ProcessChanges;
+use Icinga\Module\Businessprocess\Node;
use Icinga\Module\Businessprocess\Web\Form\QuickForm;
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
use Icinga\Web\Notification;
@@ -58,21 +59,7 @@ class ProcessForm extends QuickForm
$this->addElement('select', 'operator', array(
'label' => $this->translate('Operator'),
'required' => true,
- 'multiOptions' => array(
- '&' => $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'),
- '4' => $this->translate('MIN 4'),
- '5' => $this->translate('MIN 5'),
- '6' => $this->translate('MIN 6'),
- '7' => $this->translate('MIN 7'),
- '8' => $this->translate('MIN 8'),
- '9' => $this->translate('MIN 9'),
- )
+ 'multiOptions' => Node::getOperators()
));
if ($this->node !== null) {
diff --git a/doc/09-Operators.md b/doc/09-Operators.md
index 58db97c..ba2f51d 100644
--- a/doc/09-Operators.md
+++ b/doc/09-Operators.md
@@ -17,6 +17,16 @@ The `OR` operator selects the **BEST** state of its child nodes:

+## XOR
+
+The `XOR` operator shows OK if only one of n children is OK at the same time. In all other cases the parent node is CRITICAL.
+Useful for a service on n servers, only one of which may be running. If both were running,
+race conditions and duplication of data could occur.
+
+
+
+
+
## DEGRADED
The `DEGRADED` operator behaves like an `AND`, but if the resulting
diff --git a/doc/screenshot/09_operators/0901_and-operator.png b/doc/screenshot/09_operators/0901_and-operator.png
index a939843..c6e7775 100644
Binary files a/doc/screenshot/09_operators/0901_and-operator.png and b/doc/screenshot/09_operators/0901_and-operator.png differ
diff --git a/doc/screenshot/09_operators/0902_or-operator.png b/doc/screenshot/09_operators/0902_or-operator.png
index ff6dafa..fd05ec3 100644
Binary files a/doc/screenshot/09_operators/0902_or-operator.png and b/doc/screenshot/09_operators/0902_or-operator.png differ
diff --git a/doc/screenshot/09_operators/0903_or-operator-without-ok.png b/doc/screenshot/09_operators/0903_or-operator-without-ok.png
index b8097ee..e9fcd4e 100644
Binary files a/doc/screenshot/09_operators/0903_or-operator-without-ok.png and b/doc/screenshot/09_operators/0903_or-operator-without-ok.png differ
diff --git a/doc/screenshot/09_operators/0904_min-operator.png b/doc/screenshot/09_operators/0904_min-operator.png
index 04fa9be..fd05ec3 100644
Binary files a/doc/screenshot/09_operators/0904_min-operator.png and b/doc/screenshot/09_operators/0904_min-operator.png differ
diff --git a/doc/screenshot/09_operators/0906_xor-operator.png b/doc/screenshot/09_operators/0906_xor-operator.png
new file mode 100644
index 0000000..fd05ec3
Binary files /dev/null and b/doc/screenshot/09_operators/0906_xor-operator.png differ
diff --git a/doc/screenshot/09_operators/0907_xor-operator-not-ok.png b/doc/screenshot/09_operators/0907_xor-operator-not-ok.png
new file mode 100644
index 0000000..8ec41b3
Binary files /dev/null and b/doc/screenshot/09_operators/0907_xor-operator-not-ok.png differ
diff --git a/library/Businessprocess/BpNode.php b/library/Businessprocess/BpNode.php
index a61c984..10e3655 100644
--- a/library/Businessprocess/BpNode.php
+++ b/library/Businessprocess/BpNode.php
@@ -10,6 +10,7 @@ class BpNode extends Node
{
const OP_AND = '&';
const OP_OR = '|';
+ const OP_XOR = '^';
const OP_NOT = '!';
const OP_DEGRADED = '%';
@@ -303,6 +304,7 @@ class BpNode extends Node
switch ($operator) {
case self::OP_AND:
case self::OP_OR:
+ case self::OP_XOR:
case self::OP_NOT:
case self::OP_DEGRADED:
return;
@@ -476,6 +478,21 @@ class BpNode extends Node
case self::OP_OR:
$sort_state = min($sort_states);
break;
+ case self::OP_XOR:
+ $actualGood = 0;
+ foreach ($sort_states as $s) {
+ if ($this->sortStateTostate($s) === self::ICINGA_OK) {
+ $actualGood++;
+ }
+ }
+
+ if ($actualGood === 1) {
+ $this->state = self::ICINGA_OK;
+ } else {
+ $this->state = self::ICINGA_CRITICAL;
+ }
+
+ return $this;
case self::OP_DEGRADED:
$maxState = max($sort_states);
$flags = $maxState & 0xf;
@@ -645,6 +662,8 @@ class BpNode extends Node
break;
case self::OP_OR:
return 'OR';
+ case self::OP_XOR:
+ return 'XOR';
break;
case self::OP_NOT:
return 'NOT';
diff --git a/library/Businessprocess/Node.php b/library/Businessprocess/Node.php
index a9eb44c..73236ce 100644
--- a/library/Businessprocess/Node.php
+++ b/library/Businessprocess/Node.php
@@ -483,6 +483,31 @@ abstract class Node
return $this->name;
}
+ /**
+ * Get the Node operators
+ *
+ * @return array
+ */
+ public static function getOperators(): array
+ {
+ return [
+ '&' => t('AND'),
+ '|' => t('OR'),
+ '^' => t('XOR'),
+ '!' => t('NOT'),
+ '%' => t('DEGRADED'),
+ '1' => t('MIN 1'),
+ '2' => t('MIN 2'),
+ '3' => t('MIN 3'),
+ '4' => t('MIN 4'),
+ '5' => t('MIN 5'),
+ '6' => t('MIN 6'),
+ '7' => t('MIN 7'),
+ '8' => t('MIN 8'),
+ '9' => t('MIN 9'),
+ ];
+ }
+
public function getIdentifier()
{
return '@' . $this->getBpConfig()->getName() . ':' . $this->getName();
diff --git a/library/Businessprocess/Storage/LegacyConfigParser.php b/library/Businessprocess/Storage/LegacyConfigParser.php
index 834e56d..437e369 100644
--- a/library/Businessprocess/Storage/LegacyConfigParser.php
+++ b/library/Businessprocess/Storage/LegacyConfigParser.php
@@ -349,7 +349,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 5b099fb..430a7a5 100644
--- a/library/Businessprocess/Storage/LegacyConfigRenderer.php
+++ b/library/Businessprocess/Storage/LegacyConfigRenderer.php
@@ -197,7 +197,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 !== '&') {