summaryrefslogtreecommitdiffstats
path: root/library/Businessprocess/BpNode.php
diff options
context:
space:
mode:
Diffstat (limited to 'library/Businessprocess/BpNode.php')
-rw-r--r--library/Businessprocess/BpNode.php646
1 files changed, 646 insertions, 0 deletions
diff --git a/library/Businessprocess/BpNode.php b/library/Businessprocess/BpNode.php
new file mode 100644
index 0000000..ab76e3e
--- /dev/null
+++ b/library/Businessprocess/BpNode.php
@@ -0,0 +1,646 @@
+<?php
+
+namespace Icinga\Module\Businessprocess;
+
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\NotFoundError;
+use Icinga\Module\Businessprocess\Exception\NestingError;
+use ipl\Web\Widget\Icon;
+
+class BpNode extends Node
+{
+ const OP_AND = '&';
+ const OP_OR = '|';
+ const OP_XOR = '^';
+ const OP_NOT = '!';
+ const OP_DEGRADED = '%';
+
+ protected $operator = '&';
+
+ protected $url;
+
+ protected $display = 0;
+
+ /** @var ?Node[] */
+ protected $children;
+
+ /** @var array */
+ protected $childNames = array();
+ protected $counters;
+ protected $missing = null;
+ protected $empty = null;
+ protected $missingChildren;
+ protected $stateOverrides = [];
+
+ protected static $emptyStateSummary = array(
+ 'CRITICAL' => 0,
+ 'CRITICAL-HANDLED' => 0,
+ 'WARNING' => 0,
+ 'WARNING-HANDLED' => 0,
+ 'UNKNOWN' => 0,
+ 'UNKNOWN-HANDLED' => 0,
+ 'OK' => 0,
+ 'PENDING' => 0,
+ 'MISSING' => 0,
+ 'EMPTY' => 0,
+ );
+
+ protected static $sortStateInversionMap = array(
+ 4 => 0,
+ 3 => 0,
+ 2 => 2,
+ 1 => 1,
+ 0 => 4
+ );
+
+ protected $className = 'process';
+
+ public function __construct($object)
+ {
+ $this->name = BpConfig::escapeName($object->name);
+ $this->alias = BpConfig::unescapeName($object->name);
+ $this->operator = $object->operator;
+ $this->childNames = $object->child_names;
+ }
+
+ public function getStateSummary()
+ {
+ if ($this->counters === null) {
+ $this->getState();
+ $this->counters = self::$emptyStateSummary;
+
+ foreach ($this->getChildren() as $child) {
+ if ($child->isMissing()) {
+ $this->counters['MISSING']++;
+ } else {
+ $state = $child->getStateName($this->getChildState($child));
+ if ($child->isHandled() && ($state !== 'UP' && $state !== 'OK')) {
+ $state = $state . '-HANDLED';
+ }
+
+ if ($state === 'DOWN') {
+ $this->counters['CRITICAL']++;
+ } elseif ($state === 'DOWN-HANDLED') {
+ $this->counters['CRITICAL-HANDLED']++;
+ } elseif ($state === 'UNREACHABLE') {
+ $this->counters['UNKNOWN']++;
+ } elseif ($state === 'UNREACHABLE-HANDLED') {
+ $this->counters['UNKNOWN-HANDLED']++;
+ } elseif ($state === 'PENDING-HANDLED') {
+ $this->counters['PENDING']++;
+ } elseif ($state === 'UP') {
+ $this->counters['OK']++;
+ } else {
+ $this->counters[$state]++;
+ }
+ }
+ }
+ }
+ return $this->counters;
+ }
+
+ public function hasProblems()
+ {
+ if ($this->isProblem()) {
+ return true;
+ }
+
+ $okStates = array('OK', 'UP', 'PENDING', 'MISSING');
+
+ foreach ($this->getStateSummary() as $state => $cnt) {
+ if ($cnt !== 0 && ! in_array($state, $okStates)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param Node $node
+ * @return $this
+ * @throws ConfigurationError
+ */
+ public function addChild(Node $node)
+ {
+ if ($this->children === null) {
+ $this->getChildren();
+ }
+
+ $name = $node->getName();
+ if (array_key_exists($name, $this->children)) {
+ throw new ConfigurationError(
+ 'Node "%s" has been defined more than once',
+ $name
+ );
+ }
+
+ $this->children[$name] = $node;
+ $this->childNames[] = $name;
+ $node->addParent($this);
+ return $this;
+ }
+
+ public function getProblematicChildren()
+ {
+ $problems = array();
+
+ foreach ($this->getChildren() as $child) {
+ if (isset($this->stateOverrides[$child->getName()])) {
+ $problem = $this->getChildState($child) > 0;
+ } else {
+ $problem = $child->isProblem() || ($child instanceof BpNode && $child->hasProblems());
+ }
+
+ if ($problem) {
+ $problems[] = $child;
+ }
+ }
+
+ return $problems;
+ }
+
+ public function hasChild($name)
+ {
+ return in_array($name, $this->getChildNames());
+ }
+
+ public function removeChild($name)
+ {
+ if (($key = array_search($name, $this->getChildNames())) !== false) {
+ unset($this->childNames[$key]);
+
+ if (! empty($this->children)) {
+ unset($this->children[$name]);
+ }
+
+ $this->childNames = array_values($this->childNames);
+ }
+
+ return $this;
+ }
+
+ public function getProblemTree()
+ {
+ $tree = array();
+
+ foreach ($this->getProblematicChildren() as $child) {
+ $name = $child->getName();
+ $tree[$name] = array(
+ 'node' => $child,
+ 'children' => array()
+ );
+ if ($child instanceof BpNode) {
+ $tree[$name]['children'] = $child->getProblemTree();
+ }
+ }
+
+ return $tree;
+ }
+
+ /**
+ * Get the problem nodes as tree reduced to the nodes which have the same state as the business process
+ *
+ * @param bool $rootCause Reduce nodes to the nodes which are responsible for the state of the business process
+ *
+ * @return array
+ */
+ public function getProblemTreeBlame($rootCause = false)
+ {
+ $tree = [];
+ $nodeState = $this->getState();
+
+ if ($nodeState !== 0) {
+ foreach ($this->getChildren() as $child) {
+ $childState = $this->getChildState($child);
+ $childState = $rootCause ? $child->getSortingState($childState) : $childState;
+ if (($rootCause ? $this->getSortingState() : $nodeState) === $childState) {
+ $name = $child->getName();
+ $tree[$name] = [
+ 'children' => [],
+ 'node' => $child
+ ];
+ if ($child instanceof BpNode) {
+ $tree[$name]['children'] = $child->getProblemTreeBlame($rootCause);
+ }
+ }
+ }
+ }
+
+ return $tree;
+ }
+
+
+ public function isMissing()
+ {
+ if ($this->missing === null) {
+ $exists = false;
+ $bp = $this->getBpConfig();
+ $bp->beginLoopDetection($this->name);
+ foreach ($this->getChildren() as $child) {
+ if (! $child->isMissing()) {
+ $exists = true;
+ }
+ }
+ $bp->endLoopDetection($this->name);
+ $this->missing = ! $exists && ! empty($this->getChildren());
+ }
+ return $this->missing;
+ }
+
+ public function isEmpty()
+ {
+ $bp = $this->getBpConfig();
+ $empty = true;
+ if ($this->countChildren()) {
+ $bp->beginLoopDetection($this->name);
+ foreach ($this->getChildren() as $child) {
+ if ($child instanceof MonitoredNode) {
+ $empty = false;
+ break;
+ } elseif (!$child->isEmpty()) {
+ $empty = false;
+ }
+ }
+ $bp->endLoopDetection($this->name);
+ }
+ $this->empty = $empty;
+
+ return $this->empty;
+ }
+
+
+ public function getMissingChildren()
+ {
+ if ($this->missingChildren === null) {
+ $missing = array();
+
+ foreach ($this->getChildren() as $child) {
+ if ($child->isMissing()) {
+ $missing[$child->getAlias() ?? $child->getName()] = $child;
+ }
+
+ foreach ($child->getMissingChildren() as $m) {
+ $missing[$m->getAlias() ?? $m->getName()] = $m;
+ }
+ }
+
+ $this->missingChildren = $missing;
+ }
+
+ return $this->missingChildren;
+ }
+
+ public function getOperator()
+ {
+ return $this->operator;
+ }
+
+ public function setOperator($operator)
+ {
+ $this->assertValidOperator($operator);
+ $this->operator = $operator;
+ return $this;
+ }
+
+ protected function assertValidOperator($operator)
+ {
+ switch ($operator) {
+ case self::OP_AND:
+ case self::OP_OR:
+ case self::OP_XOR:
+ case self::OP_NOT:
+ case self::OP_DEGRADED:
+ return;
+ default:
+ if (is_numeric($operator)) {
+ return;
+ }
+ }
+
+ throw new ConfigurationError(
+ 'Got invalid operator: %s',
+ $operator
+ );
+ }
+
+ public function setInfoUrl($url)
+ {
+ $this->url = $url;
+ return $this;
+ }
+
+ public function hasInfoUrl()
+ {
+ return ! empty($this->url);
+ }
+
+ public function getInfoUrl()
+ {
+ return $this->url;
+ }
+
+ public function setStateOverrides(array $overrides, $name = null)
+ {
+ if ($name === null) {
+ $this->stateOverrides = $overrides;
+ } else {
+ $this->stateOverrides[$name] = $overrides;
+ }
+
+ return $this;
+ }
+
+ public function getStateOverrides($name = null)
+ {
+ $overrides = null;
+ if ($name !== null) {
+ if (isset($this->stateOverrides[$name])) {
+ $overrides = $this->stateOverrides[$name];
+ }
+ } else {
+ $overrides = $this->stateOverrides;
+ }
+
+ return $overrides;
+ }
+
+ public function getAlias()
+ {
+ return $this->alias ? preg_replace('~_~', ' ', $this->alias) : $this->name;
+ }
+
+ /**
+ * @return int
+ */
+ public function getState()
+ {
+ if ($this->state === null) {
+ try {
+ $this->reCalculateState();
+ } catch (NestingError $e) {
+ $this->getBpConfig()->addError(
+ $this->getBpConfig()->translate('Nesting error detected: %s'),
+ $e->getMessage()
+ );
+
+ // Failing nodes are unknown
+ $this->state = 3;
+ }
+ }
+
+ return $this->state;
+ }
+
+ /**
+ * Get the given child's state, possibly adjusted by override rules
+ *
+ * @param Node|string $child
+ * @return int
+ */
+ public function getChildState($child)
+ {
+ if (! $child instanceof Node) {
+ $child = $this->getChildByName($child);
+ }
+
+ $childName = $child->getName();
+ $childState = $child->getState();
+ if (! isset($this->stateOverrides[$childName][$childState])) {
+ return $childState;
+ }
+
+ return $this->stateOverrides[$childName][$childState];
+ }
+
+ public function getHtmlId()
+ {
+ return 'businessprocess-' . preg_replace('/[\r\n\t\s]/', '_', $this->getName());
+ }
+
+ protected function invertSortingState($state)
+ {
+ return self::$sortStateInversionMap[$state >> self::SHIFT_FLAGS] << self::SHIFT_FLAGS;
+ }
+
+ /**
+ * @return $this
+ */
+ public function reCalculateState()
+ {
+ $bp = $this->getBpConfig();
+
+ $sort_states = array();
+ $lastStateChange = 0;
+
+ if ($this->isEmpty()) {
+ // TODO: delegate this to operators, should mostly fail
+ $this->setState(self::NODE_EMPTY);
+ return $this;
+ }
+
+ foreach ($this->getChildren() as $child) {
+ $bp->beginLoopDetection($this->name);
+ if ($child instanceof MonitoredNode && $child->isMissing()) {
+ if ($child instanceof HostNode) {
+ $child->setState(self::ICINGA_UNREACHABLE);
+ } else {
+ $child->setState(self::ICINGA_UNKNOWN);
+ }
+
+ $child->setMissing();
+ }
+ $sort_states[] = $child->getSortingState($this->getChildState($child));
+ $lastStateChange = max($lastStateChange, $child->getLastStateChange());
+ $bp->endLoopDetection($this->name);
+ }
+
+ $this->setLastStateChange($lastStateChange);
+
+ switch ($this->getOperator()) {
+ case self::OP_AND:
+ $sort_state = max($sort_states);
+ break;
+ case self::OP_NOT:
+ $sort_state = $this->invertSortingState(max($sort_states));
+ break;
+ 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;
+
+ $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;
+
+ if (count($sort_states) >= $this->operator) {
+ $actualGood = 0;
+ foreach ($sort_states as $s) {
+ if (($s >> self::SHIFT_FLAGS) === self::ICINGA_OK) {
+ $actualGood++;
+ }
+ }
+
+ if ($actualGood >= $this->operator) {
+ // condition is fulfilled
+ $sort_state = self::ICINGA_OK;
+ } else {
+ // worst state if not fulfilled
+ $sort_state = max($sort_states);
+ }
+ }
+ }
+ if ($sort_state & self::FLAG_DOWNTIME) {
+ $this->setDowntime(true);
+ }
+ if ($sort_state & self::FLAG_ACK) {
+ $this->setAck(true);
+ }
+
+ $this->state = $this->sortStateTostate($sort_state);
+ return $this;
+ }
+
+ public function checkForLoops()
+ {
+ $bp = $this->getBpConfig();
+ foreach ($this->getChildren() as $child) {
+ $bp->beginLoopDetection($this->name);
+ if ($child instanceof BpNode) {
+ $child->checkForLoops();
+ }
+ $bp->endLoopDetection($this->name);
+ }
+
+ return $this;
+ }
+
+ public function setDisplay($display)
+ {
+ $this->display = (int) $display;
+ return $this;
+ }
+
+ public function getDisplay()
+ {
+ return $this->display;
+ }
+
+ public function setChildNames($names)
+ {
+ $this->childNames = $names;
+ $this->children = null;
+ return $this;
+ }
+
+ public function hasChildren($filter = null)
+ {
+ $childNames = $this->getChildNames();
+ return !empty($childNames);
+ }
+
+ public function getChildNames()
+ {
+ return $this->childNames;
+ }
+
+ public function getChildren($filter = null)
+ {
+ if ($this->children === null) {
+ $this->children = [];
+ foreach ($this->getChildNames() as $name) {
+ $this->children[$name] = $this->getBpConfig()->getNode($name);
+ $this->children[$name]->addParent($this);
+ }
+ }
+
+ return $this->children;
+ }
+
+ /**
+ * return BpNode[]
+ */
+ public function getChildBpNodes()
+ {
+ $children = array();
+
+ foreach ($this->getChildren() as $name => $child) {
+ if ($child instanceof BpNode) {
+ $children[$name] = $child;
+ }
+ }
+
+ return $children;
+ }
+
+ /**
+ * @param $childName
+ * @return Node
+ * @throws NotFoundError
+ */
+ public function getChildByName($childName)
+ {
+ foreach ($this->getChildren() as $name => $child) {
+ if ($name === $childName) {
+ return $child;
+ }
+ }
+
+ throw new NotFoundError('Trying to get missing child %s', $childName);
+ }
+
+ protected function assertNumericOperator()
+ {
+ if (! is_numeric($this->getOperator())) {
+ throw new ConfigurationError('Got invalid operator: %s', $this->operator);
+ }
+ }
+
+ public function operatorHtml()
+ {
+ switch ($this->getOperator()) {
+ case self::OP_AND:
+ return 'AND';
+ case self::OP_OR:
+ return 'OR';
+ case self::OP_XOR:
+ return 'XOR';
+ case self::OP_NOT:
+ return 'NOT';
+ case self::OP_DEGRADED:
+ return 'DEG';
+ default:
+ // MIN
+ $this->assertNumericOperator();
+ return 'min:' . $this->operator;
+ }
+ }
+
+ public function getIcon(): Icon
+ {
+ $this->icon = $this->hasParents() ? 'cubes' : 'sitemap';
+ return parent::getIcon();
+ }
+}