icingaweb2-module-businessp.../library/Businessprocess/BusinessProcess.php

748 lines
18 KiB
PHP
Raw Normal View History

<?php
2014-11-30 09:56:58 -05:00
namespace Icinga\Module\Businessprocess;
2015-03-16 04:08:00 -04:00
use Icinga\Application\Benchmark;
use Icinga\Exception\IcingaException;
use Icinga\Exception\ProgrammingError;
use Icinga\Module\Businessprocess\Exception\NestingError;
use Icinga\Module\Businessprocess\Modification\ProcessChanges;
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
2014-12-02 05:35:21 -05:00
use Icinga\Data\Filter\Filter;
use Exception;
class BusinessProcess
{
const SOFT_STATE = 0;
2015-02-06 18:13:37 -05:00
const HARD_STATE = 1;
2015-03-16 04:08:00 -04:00
/**
* Name of the configured monitoring backend
*
* @var string
*/
protected $backendName;
/**
* Monitoring backend to retrieve states from
*
* @var MonitoringBackend
*/
protected $backend;
2015-02-06 18:13:37 -05:00
/**
* Business process name
*
* @var string
*/
2014-12-01 08:07:30 -05:00
protected $name;
2015-02-06 18:13:37 -05:00
/**
* Business process title
*
* @var string
*/
protected $title;
2015-02-06 18:13:37 -05:00
/**
* State type, soft or hard
*
* @var int
*/
protected $state_type = self::HARD_STATE;
2015-02-06 18:13:37 -05:00
/**
* Warnings, usually filled at process build time
*
* @var array
*/
protected $warnings = array();
2015-02-06 18:13:37 -05:00
2015-03-16 04:08:00 -04:00
/**
* Errors, usually filled at process build time
*
* @var array
*/
protected $errors = array();
2015-02-06 18:13:37 -05:00
/**
* All used node objects
*
* @var array
*/
protected $nodes = array();
2015-02-06 18:13:37 -05:00
/**
* Root node objects
*
* @var array
*/
protected $root_nodes = array();
2015-02-06 18:13:37 -05:00
/**
* All check names { 'hostA;ping' => true, ... }
*
* @var array
*/
protected $all_checks = array();
2015-02-06 18:13:37 -05:00
/**
* All host names { 'hostA' => true, ... }
*
* @var array
*/
protected $hosts = array();
2015-02-06 18:13:37 -05:00
/** @var bool Whether catchable errors should be thrown nonetheless */
protected $throwErrors = false;
protected $loopDetection = array();
2015-02-06 18:13:37 -05:00
/**
2015-03-16 04:08:00 -04:00
* Applied state simulation
2015-02-06 18:13:37 -05:00
*
2015-03-16 04:08:00 -04:00
* @var Simulation
2015-02-06 18:13:37 -05:00
*/
2015-03-16 04:08:00 -04:00
protected $simulation;
2015-02-06 18:13:37 -05:00
2015-03-16 04:08:00 -04:00
protected $locked = true;
protected $changeCount = 0;
protected $simulationCount = 0;
protected $appliedChanges;
public function __construct()
{
}
2015-03-16 04:08:00 -04:00
public function applyChanges(ProcessChanges $changes)
{
$cnt = 0;
foreach ($changes->getChanges() as $change) {
$cnt++;
$change->applyTo($this);
}
$this->changeCount = $cnt;
$this->appliedChanges = $changes;
2015-03-16 04:08:00 -04:00
return $this;
}
public function applySimulation(Simulation $simulation)
{
$cnt = 0;
foreach ($simulation->simulations() as $node => $s) {
if (! $this->hasNode($node)) {
continue;
}
$cnt++;
$this->getNode($node)
->setState($s->state)
->setAck($s->acknowledged)
->setDowntime($s->in_downtime)
->setMissing(false);
2015-03-16 04:08:00 -04:00
}
$this->simulationCount = $cnt;
}
public function countChanges()
{
return $this->changeCount;
}
public function hasChanges()
{
return $this->countChanges() > 0;
}
2014-12-01 08:07:30 -05:00
public function setName($name)
{
$this->name = $name;
return $this;
}
public function getName()
{
return $this->name;
}
public function getHtmlId()
{
return 'businessprocess-' . preg_replace('/[\r\n\t\s]/', '_', $this->getName());
}
public function setTitle($title)
{
$this->title = $title;
return $this;
}
public function getTitle()
{
return $this->title ?: $this->getName();
}
2015-03-16 04:08:00 -04:00
public function hasTitle()
{
2015-03-16 04:08:00 -04:00
return $this->title !== null;
}
public function setBackendName($name)
{
$this->backendName = $name;
return $this;
}
2015-03-16 04:08:00 -04:00
public function getBackendName()
{
return $this->backendName;
}
public function hasBackendName()
{
return $this->backendName !== null;
}
public function setBackend(MonitoringBackend $backend)
{
$this->backend = $backend;
return $this;
}
public function getBackend()
{
if ($this->backend === null) {
2015-03-16 04:08:00 -04:00
$this->backend = MonitoringBackend::createBackend(
$this->getBackendName()
);
}
2015-03-16 04:08:00 -04:00
return $this->backend;
}
public function hasBackend()
{
return $this->backend !== null;
}
public function hasBeenChanged()
{
return false;
}
2015-03-16 04:08:00 -04:00
public function isLocked()
{
return $this->locked;
}
public function lock($lock = true)
{
2015-03-16 04:08:00 -04:00
$this->locked = (bool) $lock;
return $this;
}
2015-03-16 04:08:00 -04:00
public function unlock()
{
return $this->lock(false);
}
public function hasSimulations()
{
2015-03-16 04:08:00 -04:00
return $this->countSimulations() > 0;
}
public function countSimulations()
{
return $this->simulationCount;
}
public function clearAppliedChanges()
{
if ($this->appliedChanges !== null) {
$this->appliedChanges->clear();
}
return $this;
}
public function useSoftStates()
{
$this->state_type = self::SOFT_STATE;
return $this;
}
public function useHardStates()
{
$this->state_type = self::HARD_STATE;
return $this;
}
2015-02-12 19:55:46 -05:00
public function usesSoftStates()
{
return $this->state_type === self::SOFT_STATE;
}
public function usesHardStates()
{
return $this->state_type === self::HARD_STATE;
2015-02-12 19:55:46 -05:00
}
public function addRootNode($name)
{
$this->root_nodes[$name] = $this->getNode($name);
return $this;
}
public function removeRootNode($name)
{
if ($this->isRootNode($name)) {
unset($this->root_nodes[$name]);
}
return $this;
}
public function isRootNode($name)
{
return array_key_exists($name, $this->root_nodes);
}
public function retrieveStatesFromBackend()
{
2015-03-16 04:08:00 -04:00
try {
$this->reallyRetrieveStatesFromBackend();
} catch (Exception $e) {
$this->addError(
2015-03-16 04:08:00 -04:00
$this->translate('Could not retrieve process state: %s'),
$e->getMessage()
);
}
}
public function reallyRetrieveStatesFromBackend()
{
Benchmark::measure('Retrieving states for business process ' . $this->getName());
$backend = $this->getBackend();
// TODO: Split apart, create a dedicated function.
// Separate "parse-logic" from "retrieve-state-logic"
// Allow DB-based backend
// Use IcingaWeb2 Multi-Backend-Support
$hostFilter = array_keys($this->hosts);
2015-02-06 18:13:37 -05:00
if ($this->state_type === self::HARD_STATE) {
2015-01-28 14:02:50 -05:00
$hostStateColumn = 'host_hard_state';
$hostStateChangeColumn = 'host_last_hard_state_change';
$serviceStateColumn = 'service_hard_state';
$serviceStateChangeColumn = 'service_last_hard_state_change';
} else {
2015-01-28 14:02:50 -05:00
$hostStateColumn = 'host_state';
$hostStateChangeColumn = 'host_last_state_change';
$serviceStateColumn = 'service_state';
$serviceStateChangeColumn = 'service_last_state_change';
}
2014-12-02 05:35:21 -05:00
$filter = Filter::matchAny();
foreach ($hostFilter as $host) {
2015-02-06 18:01:30 -05:00
$filter->addFilter(Filter::where('host_name', $host));
2014-12-02 05:35:21 -05:00
}
2015-02-06 18:01:30 -05:00
2015-03-16 04:08:00 -04:00
if ($filter->isEmpty()) {
return $this;
}
$hostStatus = $backend->select()->from('hostStatus', array(
2015-01-28 14:02:50 -05:00
'hostname' => 'host_name',
'last_state_change' => $hostStateChangeColumn,
2015-01-28 14:02:50 -05:00
'in_downtime' => 'host_in_downtime',
'ack' => 'host_acknowledged',
'state' => $hostStateColumn
2014-12-02 05:35:21 -05:00
))->applyFilter($filter)->getQuery()->fetchAll();
$serviceStatus = $backend->select()->from('serviceStatus', array(
2015-01-28 14:02:50 -05:00
'hostname' => 'host_name',
'service' => 'service_description',
'last_state_change' => $serviceStateChangeColumn,
2015-01-28 14:02:50 -05:00
'in_downtime' => 'service_in_downtime',
'ack' => 'service_acknowledged',
'state' => $serviceStateColumn
2014-12-02 05:35:21 -05:00
))->applyFilter($filter)->getQuery()->fetchAll();
foreach ($serviceStatus as $row) {
$this->handleDbRow($row);
}
foreach ($hostStatus as $row) {
$this->handleDbRow($row);
}
ksort($this->root_nodes);
2015-03-16 04:08:00 -04:00
Benchmark::measure('Got states for business process ' . $this->getName());
return $this;
}
protected function handleDbRow($row)
{
$key = $row->hostname;
if (property_exists($row, 'service')) {
$key .= ';' . $row->service;
} else {
$key .= ';Hoststatus';
}
// We fetch more states than we need, so skip unknown ones
if (! $this->hasNode($key)) {
return;
}
$node = $this->getNode($key);
if ($row->state !== null) {
$node->setState($row->state)->setMissing(false);
}
if ($row->last_state_change !== null) {
$node->setLastStateChange($row->last_state_change);
}
if ((int) $row->in_downtime === 1) {
$node->setDowntime(true);
}
if ((int) $row->ack === 1) {
$node->setAck(true);
}
}
/**
* @return BpNode[]
*/
public function getChildren()
{
return $this->getRootNodes();
}
public function countChildren()
{
return count($this->root_nodes);
}
public function getRootNodes()
{
2014-12-02 05:35:21 -05:00
ksort($this->root_nodes);
return $this->root_nodes;
}
public function getNodes()
{
return $this->nodes;
}
public function hasNode($name)
{
return array_key_exists($name, $this->nodes);
}
public function createService($host, $service)
{
$node = new ServiceNode(
$this,
(object) array(
'hostname' => $host,
'service' => $service
)
);
$this->nodes[$host . ';' . $service] = $node;
2015-02-06 18:01:30 -05:00
$this->hosts[$host] = true;
return $node;
}
public function createHost($host)
{
$node = new HostNode($this, (object) array('hostname' => $host));
$this->nodes[$host . ';Hoststatus'] = $node;
2015-02-06 18:01:30 -05:00
$this->hosts[$host] = true;
return $node;
}
/**
* Create and attach a new process (BpNode)
*
* @param string $name Process name
* @param string $operator Operator (defaults to &)
*
* @return BpNode
*/
public function createBp($name, $operator = '&')
{
$node = new BpNode($this, (object) array(
'name' => $name,
'operator' => $operator,
'child_names' => array(),
));
$this->addNode($name, $node);
return $node;
}
2015-02-12 19:55:46 -05:00
public function createImportedNode($config, $name)
{
$node = new ImportedNode($this, (object) array('name' => $name, 'configName' => $config));
$this->nodes[$name] = $node;
return $node;
}
/**
* @param $name
* @return Node
* @throws Exception
*/
public function getNode($name)
{
if (array_key_exists($name, $this->nodes)) {
return $this->nodes[$name];
}
// Fallback: if it is a service, create an empty one:
$this->warn(sprintf('The node "%s" doesn\'t exist', $name));
$pos = strpos($name, ';');
if ($pos !== false) {
$host = substr($name, 0, $pos);
$service = substr($name, $pos + 1);
2016-11-29 05:20:54 -05:00
// TODO: deactivated, this scares me, test it
if (false && $service === 'Hoststatus') {
return $this->createHost($host);
} else {
return $this->createService($host, $service);
}
}
throw new Exception(
sprintf('The node "%s" doesn\'t exist', $name)
);
}
/**
* Set the state for a specific node
*
* @param string $name Node name
* @param int $state Desired state
*
* @return $this
*/
public function setNodeState($name, $state)
{
$this->getNode($name)->setState($state);
return $this;
}
public function addObjectName($name)
{
$this->all_checks[$name] = 1;
return $this;
}
public function addNode($name, Node $node)
{
if (array_key_exists($name, $this->nodes)) {
$this->warn(
sprintf(
2015-03-16 04:08:00 -04:00
mt('businessprocess', 'Node "%s" has been defined twice'),
$name
)
);
}
2015-02-06 18:01:30 -05:00
$this->nodes[$name] = $node;
if ($node->getDisplay() > 0) {
if (! $this->isRootNode($name)) {
$this->addRootNode($name);
}
} else {
if ($this->isRootNode($name)) {
$this->removeRootNode($name);
}
}
return $this;
}
public function removeNode($name)
{
throw new ProgrammingError('Not implemented yet');
}
public function listBpNodes()
{
$nodes = array();
foreach ($this->nodes as $node) {
if (! $node instanceof BpNode) {
continue;
}
$name = (string) $node;
$alias = $node->getAlias();
$nodes[$name] = $name === $alias ? $name : sprintf('%s (%s)', $alias, $node);
}
natsort($nodes);
return $nodes;
}
2015-03-16 04:08:00 -04:00
public function getUnboundNodes()
{
$nodes = array();
foreach ($this->nodes as $node) {
if (! $node instanceof BpNode) {
continue;
}
if ($node->hasParents()) {
continue;
}
if ($node->getDisplay() === 0) {
2015-03-16 04:08:00 -04:00
$nodes[(string) $node] = $node;
}
}
return $nodes;
}
public function hasWarnings()
{
return ! empty($this->warnings);
}
public function getWarnings()
{
return $this->warnings;
}
2015-03-16 04:08:00 -04:00
public function hasErrors()
{
return ! empty($this->errors) || $this->isEmpty();
}
public function getErrors()
{
$errors = $this->errors;
if ($this->isEmpty()) {
$errors[] = sprintf(
$this->translate(
'No business process nodes for "%s" have been defined yet'
),
$this->getTitle()
);
}
return $errors;
}
public function translate($msg)
2015-03-16 04:08:00 -04:00
{
return mt('businessprocess', $msg);
}
protected function warn($msg)
{
2015-03-16 04:08:00 -04:00
$args = func_get_args();
array_shift($args);
if (isset($this->parsing_line_number)) {
$this->warnings[] = sprintf(
2015-03-16 04:08:00 -04:00
$this->translate('Parser waring on %s:%s: %s'),
$this->filename,
$this->parsing_line_number,
2015-03-16 04:08:00 -04:00
vsprintf($msg, $args)
);
} else {
2015-03-16 04:08:00 -04:00
$this->warnings[] = vsprintf($msg, $args);
}
}
2014-11-30 05:30:59 -05:00
/**
* @param string $msg,...
*
* @return $this
*/
public function addError($msg)
{
$args = func_get_args();
array_shift($args);
$msg = vsprintf($msg, $args);
if ($this->throwErrors) {
throw new IcingaException($msg);
}
$this->errors[] = $msg;
return $this;
}
public function throwErrors($throw = true)
2015-03-16 04:08:00 -04:00
{
$this->throwErrors = $throw;
return $this;
2015-03-16 04:08:00 -04:00
}
public function toLegacyConfigString()
{
2015-03-16 04:08:00 -04:00
$settings = array();
if ($this->hasTitle()) {
$settings['Title'] = $this->getTitle();
}
// TODO: backendName?
if ($this->backend) {
$settings['Backend'] = $this->backend->getName();
}
$settings['Statetype'] = $this->usesSoftStates() ? 'soft' : 'hard';
2015-03-16 04:08:00 -04:00
if (false) {
$settings['SLA Hosts'] = implode(', ', array());
}
$conf = "### Business Process Config File ###\n#\n";
foreach ($settings as $key => $value) {
$conf .= sprintf("# %-9s : %s\n", $key, $value);
}
$conf .= "#\n###################################\n\n";
$rendered = array();
foreach ($this->getChildren() as $child) {
$conf .= $child->toLegacyConfigString($rendered);
$rendered[(string) $child] = true;
}
2015-03-16 04:08:00 -04:00
foreach ($this->getUnboundNodes() as $node) {
$conf .= $node->toLegacyConfigString($rendered);
$rendered[(string) $node] = true;
2015-03-16 04:08:00 -04:00
}
return $conf . "\n";
}
public function beginLoopDetection($name)
{
// echo "Begin loop $name\n";
if (array_key_exists($name, $this->loopDetection)) {
$loop = array_keys($this->loopDetection);
$loop[] = $name;
$this->loopDetection = array();
throw new NestingError('Loop detected: %s', implode(' -> ', $loop));
}
$this->loopDetection[$name] = true;
}
public function endLoopDetection($name)
{
// echo "End loop $this->name\n";
unset($this->loopDetection[$name]);
}
2015-03-16 04:08:00 -04:00
public function isEmpty()
{
return $this->countChildren() === 0;
}
}