true, ... } * * @var array */ protected $hosts = array(); /** @var bool Whether catchable errors should be thrown nonetheless */ protected $throwErrors = false; protected $loopDetection = array(); /** * Applied state simulation * * @var Simulation */ protected $simulation; protected $changeCount = 0; protected $simulationCount = 0; /** @var ProcessChanges */ protected $appliedChanges; /** @var bool Whether the config is faulty */ protected $isFaulty = false; public function __construct() { } /** * Retrieve metadata for this configuration * * @return Metadata */ public function getMetadata() { if ($this->metadata === null) { $this->metadata = new Metadata($this->name); } return $this->metadata; } /** * Set metadata * * @param Metadata $metadata * * @return $this */ public function setMetadata(Metadata $metadata) { $this->metadata = $metadata; return $this; } /** * Apply pending process changes * * @param ProcessChanges $changes * * @return $this */ public function applyChanges(ProcessChanges $changes) { $cnt = 0; foreach ($changes->getChanges() as $change) { $cnt++; $change->applyTo($this); } $this->changeCount = $cnt; $this->appliedChanges = $changes; return $this; } /** * Apply a state simulation * * @param Simulation $simulation * * @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); } $this->simulationCount = $cnt; return $this; } /** * Number of applied changes * * @return int */ public function countChanges() { return $this->changeCount; } /** * Whether changes have been applied to this configuration * * @return bool */ public function hasChanges() { return $this->countChanges() > 0; } /** * @param $name * * @return $this */ public function setName($name) { $this->name = $name; return $this; } /** * @return string */ public function getName() { return $this->name; } /** * @return string */ 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->getMetadata()->getTitle(); } public function hasTitle() { return $this->getMetadata()->has('Title'); } public function getBackendName() { return $this->getMetadata()->get('Backend'); } public function hasBackendName() { return $this->getMetadata()->has('Backend'); } public function setBackend($backend) { $this->backend = $backend; return $this; } public function getBackend() { if ($this->backend === null) { if (Module::exists('icingadb') && (! $this->hasBackendName() && IcingadbSupport::useIcingaDbAsBackend())) { $this->backend = IcingaDbObject::fetchDb(); } else { $this->backend = MonitoringBackend::instance( $this->getBackendName() ); } } return $this->backend; } public function isReferenced() { foreach ($this->storage()->listProcessNames() as $bpName) { if ($bpName !== $this->getName()) { $bp = $this->storage()->loadProcess($bpName); foreach ($bp->getImportedNodes() as $importedNode) { if ($importedNode->getConfigName() === $this->getName()) { return true; } } } } return false; } public function hasBackend() { return $this->backend !== null; } public function hasBeenChanged() { return false; } public function hasSimulations() { return $this->countSimulations() > 0; } public function countSimulations() { return $this->simulationCount; } public function clearAppliedChanges() { if ($this->appliedChanges !== null) { $this->appliedChanges->clear(); } return $this; } public function getStateType() { if ($this->state_type === null) { if ($this->getMetadata()->has('Statetype')) { switch ($this->getMetadata()->get('Statetype')) { case 'hard': $this->state_type = self::HARD_STATE; break; case 'soft': $this->state_type = self::SOFT_STATE; break; } } else { $this->state_type = self::HARD_STATE; } } return $this->state_type; } public function useSoftStates() { $this->state_type = self::SOFT_STATE; return $this; } public function useHardStates() { $this->state_type = self::HARD_STATE; return $this; } public function usesSoftStates() { return $this->getStateType() === self::SOFT_STATE; } public function usesHardStates() { return $this->getStateType() === self::HARD_STATE; } 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); } /** * @return BpNode[] */ public function getChildren() { return $this->getRootNodes(); } /** * @return int */ public function countChildren() { return count($this->root_nodes); } /** * @return BpNode[] */ public function getRootNodes() { return $this->root_nodes; } public function listRootNodes() { return array_keys($this->root_nodes); } public function getNodes() { return $this->nodes; } public function hasNode($name) { if (array_key_exists($name, $this->nodes)) { return true; } elseif ($name[0] === '@') { list($configName, $nodeName) = preg_split('~:\s*~', substr($name, 1), 2); return $this->getImportedConfig($configName)->hasNode($nodeName); } return false; } public function hasRootNode($name) { return array_key_exists($name, $this->root_nodes); } public function createService($host, $service) { $node = new ServiceNode( (object) array( 'hostname' => $host, 'service' => $service ) ); $node->setBpConfig($this); $this->nodes[$node->getName()] = $node; $this->hosts[$host] = true; return $node; } public function createHost($host) { $node = new HostNode((object) array('hostname' => $host)); $node->setBpConfig($this); $this->nodes[$node->getName()] = $node; $this->hosts[$host] = true; return $node; } public function calculateAllStates() { foreach ($this->getRootNodes() as $node) { $node->getState(); } return $this; } public function clearAllStates() { foreach ($this->getBpNodes() as $node) { $node->clearState(); } return $this; } public function listInvolvedHostNames(&$usedConfigs = null) { $hosts = $this->hosts; if (! empty($this->importedNodes)) { $usedConfigs[$this->getName()] = true; foreach ($this->importedNodes as $node) { if (isset($usedConfigs[$node->getConfigName()])) { continue; } $hosts += array_flip($node->getBpConfig()->listInvolvedHostNames($usedConfigs)); } } return array_keys($hosts); } /** * 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((object) array( 'name' => $name, 'operator' => $operator, 'child_names' => array(), )); $node->setBpConfig($this); $this->addNode($name, $node); return $node; } public function createMissingBp($name) { return $this->createBp($name)->setMissing(); } public function getMissingChildren() { $missing = array(); foreach ($this->getRootNodes() as $root) { $missing += $root->getMissingChildren(); } return $missing; } public function createImportedNode($config, $name = null) { $params = (object) array('configName' => $config); if ($name !== null) { $params->node = $name; } $node = new ImportedNode($this, $params); $this->importedNodes[$node->getName()] = $node; $this->nodes[$node->getName()] = $node; return $node; } public function getImportedNodes() { return $this->importedNodes; } public function getImportedConfig($name) { if (! isset($this->importedConfigs[$name])) { try { $import = $this->storage()->loadProcess($name); } catch (Exception $e) { $import = (new static()) ->setName($name) ->setFaulty(); } if ($this->usesSoftStates()) { $import->useSoftStates(); } else { $import->useHardStates(); } $this->importedConfigs[$name] = $import; } return $this->importedConfigs[$name]; } public function listInvolvedConfigs(&$configs = null) { if ($configs === null) { $configs[$this->getName()] = $this; } foreach ($this->importedNodes as $node) { if (! isset($configs[$node->getConfigName()])) { $config = $node->getBpConfig(); $configs[$node->getConfigName()] = $config; $config->listInvolvedConfigs($configs); } } return $configs; } /** * @return LegacyStorage */ protected function storage() { if ($this->storage === null) { $this->storage = LegacyStorage::getInstance(); } return $this->storage; } /** * @param string $name * @return MonitoredNode|BpNode * @throws Exception */ public function getNode($name) { if ($name === '__unbound__') { return $this->getUnboundBaseNode(); } if (array_key_exists($name, $this->nodes)) { return $this->nodes[$name]; } if ($name[0] === '@') { list($configName, $nodeName) = preg_split('~:\s*~', substr($name, 1), 2); return $this->getImportedConfig($configName)->getNode($nodeName); } // Fallback: if it is a service, create an empty one: $this->warn(sprintf('The node "%s" doesn\'t exist', $name)); [$name, $suffix] = self::splitNodeName($name); if ($suffix !== null) { if ($suffix === 'Hoststatus') { return $this->createHost($name); } else { return $this->createService($name, $suffix); } } throw new Exception( sprintf('The node "%s" doesn\'t exist', $name) ); } /** * @return BpNode */ public function getUnboundBaseNode() { // Hint: state is useless here, but triggers parent/child "calculation" // This is an ugly workaround and should be made obsolete $this->calculateAllStates(); $names = array_keys($this->getUnboundNodes()); $bp = new BpNode((object) array( 'name' => '__unbound__', 'operator' => '&', 'child_names' => $names )); $bp->setBpConfig($this); $bp->setAlias($this->translate('Unbound nodes')); return $bp; } /** * @param $name * @return BpNode * * @throws NotFoundError */ public function getBpNode($name) { if ($this->hasBpNode($name)) { return $this->nodes[$name]; } $msg = $this->isFaulty() ? sprintf( t('Trying to import node "%s" from faulty config file "%s.conf"'), self::unescapeName($name), $this->getName() ) : sprintf(t('Trying to access a missing business process node "%s"'), $name); throw new NotFoundError($msg); } /** * @param $name * * @return bool */ public function hasBpNode($name) { return array_key_exists($name, $this->nodes) && $this->nodes[$name] instanceof BpNode; } /** * 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; } /** * Add the given node to the given BpNode * * @param $name * @param BpNode $node * * @return $this */ public function addNode($name, BpNode $node) { if (array_key_exists($name, $this->nodes)) { $this->warn( sprintf( mt('businessprocess', 'Node "%s" has been defined twice'), $name ) ); } $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; } /** * Remove all occurrences of a specific node by name * * @param $name */ public function removeNode($name) { unset($this->nodes[$name]); if (array_key_exists($name, $this->root_nodes)) { unset($this->root_nodes[$name]); } foreach ($this->getBpNodes() as $node) { if ($node->hasChild($name)) { $node->removeChild($name); } } } /** * Get all business process nodes * * @return BpNode[] */ public function getBpNodes() { $nodes = array(); foreach ($this->nodes as $node) { if ($node instanceof BpNode) { $nodes[$node->getName()] = $node; } } return $nodes; } /** * List all business process node names * * @return array */ public function listBpNodes() { $nodes = array(); foreach ($this->getBpNodes() as $name => $node) { $alias = $node->getAlias(); $nodes[$name] = $name === $alias ? $name : sprintf('%s (%s)', $alias, $node); } return $nodes; } /** * All business process nodes defined in this config but not * assigned to any parent * * @return BpNode[] */ public function getUnboundNodes() { $nodes = array(); foreach ($this->getBpNodes() as $name => $node) { if ($node->hasParents()) { continue; } if ($node->getDisplay() === 0) { $nodes[$name] = $node; } } return $nodes; } /** * @return bool */ public function hasWarnings() { return ! empty($this->warnings); } /** * @return array */ public function getWarnings() { return $this->warnings; } /** * @return bool */ public function hasErrors() { return ! empty($this->errors) || $this->isEmpty(); } /** * @return array */ 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; } /** * Translation helper * * @param $msg * * @return mixed|string */ public function translate($msg) { return mt('businessprocess', $msg); } /** * Add a message to our warning stack * * @param $msg */ protected function warn($msg) { $args = func_get_args(); array_shift($args); $this->warnings[] = vsprintf($msg, $args); } /** * @param string $msg,... * * @return $this * * @throws IcingaException */ public function addError($msg) { $args = func_get_args(); array_shift($args); if (! empty($args)) { $msg = vsprintf($msg, $args); } if ($this->throwErrors) { throw new IcingaException($msg); } if (! in_array($msg, $this->errors)) { $this->errors[] = $msg; } return $this; } /** * Decide whether errors should be thrown or collected * * @param bool $throw * * @return $this */ public function throwErrors($throw = true) { $this->throwErrors = $throw; return $this; } /** * Begin loop detection for the given name * * Will throw a NestingError in case this node will be met again below itself * * @param $name * * @throws NestingError */ 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; } /** * Remove the given name from the loop detection stack * * @param $name */ public function endLoopDetection($name) { // echo "End loop $this->name\n"; unset($this->loopDetection[$name]); } /** * Whether this configuration has any Nodes * * @return bool */ public function isEmpty() { // This is faster if (! empty($this->root_nodes)) { return false; } return count($this->listBpNodes()) === 0; } /** * Export the config to array * * @param bool $flat If false, children will be added to the array key children, else the array will be flat * * @return array */ public function toArray($flat = false) { $data = [ 'name' => $this->getTitle(), 'path' => $this->getTitle() ]; $children = []; foreach ($this->getChildren() as $node) { if ($flat) { $children = array_merge($children, $node->toArray($data, $flat)); } else { $children[] = $node->toArray($data, $flat); } } if ($flat) { $data = [$data]; if (! empty($children)) { $data = array_merge($data, $children); } } else { $data['children'] = $children; } return $data; } /** * Escape the given node name * * @param string $name * * @return string */ public static function escapeName(string $name): string { return preg_replace('/((?isFaulty = $isFaulty; return $this; } /** * Get whether the config is faulty * * @return bool */ public function isFaulty(): bool { return $this->isFaulty; } }