mirror of
https://github.com/Icinga/icingaweb2-module-businessprocess.git
synced 2025-12-20 23:00:16 -05:00
Internally non-process children are only instantiated once. This means when applying state overrides directly they're used everywhere and do not differ between the containing process. State overrides are now applied explicitly and on demand, decoupling them from children.
639 lines
16 KiB
PHP
639 lines
16 KiB
PHP
<?php
|
|
|
|
namespace Icinga\Module\Businessprocess;
|
|
|
|
use Icinga\Exception\ConfigurationError;
|
|
use Icinga\Exception\NotFoundError;
|
|
use Icinga\Module\Businessprocess\Exception\NestingError;
|
|
|
|
class BpNode extends Node
|
|
{
|
|
const OP_AND = '&';
|
|
const OP_OR = '|';
|
|
const OP_NOT = '!';
|
|
|
|
protected $operator = '&';
|
|
protected $url;
|
|
protected $info_command;
|
|
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(
|
|
'OK' => 0,
|
|
'WARNING' => 0,
|
|
'CRITICAL' => 0,
|
|
'UNKNOWN' => 0,
|
|
'PENDING' => 0,
|
|
'UP' => 0,
|
|
'DOWN' => 0,
|
|
'UNREACHABLE' => 0,
|
|
'MISSING' => 0,
|
|
);
|
|
|
|
protected static $sortStateInversionMap = array(
|
|
4 => 0,
|
|
3 => 0,
|
|
2 => 2,
|
|
1 => 1,
|
|
0 => 4
|
|
);
|
|
|
|
protected $className = 'process';
|
|
|
|
public function __construct($object)
|
|
{
|
|
$this->name = $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 instanceof BpNode) {
|
|
$counters = $child->getStateSummary();
|
|
foreach ($counters as $k => $v) {
|
|
$this->counters[$k] += $v;
|
|
}
|
|
} elseif ($child->isMissing()) {
|
|
$this->counters['MISSING']++;
|
|
} else {
|
|
$state = $child->getStateName($this->getChildState($child));
|
|
$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;
|
|
$this->reorderChildren();
|
|
$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]);
|
|
}
|
|
}
|
|
|
|
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->getName()] = $child;
|
|
}
|
|
|
|
foreach ($child->getMissingChildren() as $m) {
|
|
$missing[$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_NOT:
|
|
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 setInfoCommand($cmd)
|
|
{
|
|
$this->info_command = $cmd;
|
|
}
|
|
|
|
public function hasInfoCommand()
|
|
{
|
|
return $this->info_command !== null;
|
|
}
|
|
|
|
public function getInfoCommand()
|
|
{
|
|
return $this->info_command;
|
|
}
|
|
|
|
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;
|
|
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;
|
|
$this->reorderChildren();
|
|
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 = [];
|
|
$this->reorderChildren();
|
|
foreach ($this->getChildNames() as $name) {
|
|
$this->children[$name] = $this->getBpConfig()->getNode($name);
|
|
$this->children[$name]->addParent($this);
|
|
}
|
|
}
|
|
|
|
return $this->children;
|
|
}
|
|
|
|
/**
|
|
* Reorder this node's children, in case manual order is not applied
|
|
*/
|
|
protected function reorderChildren()
|
|
{
|
|
if ($this->getBpConfig()->getMetadata()->isManuallyOrdered()) {
|
|
return;
|
|
}
|
|
|
|
$childNames = $this->getChildNames();
|
|
natcasesort($childNames);
|
|
$this->childNames = array_values($childNames);
|
|
|
|
if (! empty($this->children)) {
|
|
$children = [];
|
|
foreach ($this->childNames as $name) {
|
|
$children[$name] = $this->children[$name];
|
|
}
|
|
|
|
$this->children = $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';
|
|
break;
|
|
case self::OP_OR:
|
|
return 'OR';
|
|
break;
|
|
case self::OP_NOT:
|
|
return 'NOT';
|
|
break;
|
|
default:
|
|
// MIN
|
|
$this->assertNumericOperator();
|
|
return 'min:' . $this->operator;
|
|
}
|
|
}
|
|
|
|
public function getIcon()
|
|
{
|
|
$this->icon = $this->hasParents() ? 'cubes' : 'sitemap';
|
|
return parent::getIcon();
|
|
}
|
|
}
|