425 lines
12 KiB
PHP
425 lines
12 KiB
PHP
<?php
|
|
|
|
namespace Icinga\Module\Director\Resolver;
|
|
|
|
use Icinga\Application\Benchmark;
|
|
use Icinga\Module\Director\Db;
|
|
use Icinga\Module\Director\Objects\IcingaObject;
|
|
use RuntimeException;
|
|
|
|
class TemplateTree
|
|
{
|
|
protected $connection;
|
|
|
|
protected $db;
|
|
|
|
protected $parents;
|
|
|
|
protected $children;
|
|
|
|
protected $rootNodes;
|
|
|
|
protected $tree;
|
|
|
|
protected $type;
|
|
|
|
protected $objectMaps;
|
|
|
|
protected $names;
|
|
|
|
protected $templateNameToId;
|
|
|
|
public function __construct($type, Db $connection)
|
|
{
|
|
$this->type = $type;
|
|
$this->connection = $connection;
|
|
$this->db = $connection->getDbAdapter();
|
|
}
|
|
|
|
public function getType()
|
|
{
|
|
return $this->type;
|
|
}
|
|
|
|
public function listParentNamesFor(IcingaObject $object)
|
|
{
|
|
$id = (int) $object->getProperty('id');
|
|
$this->requireTree();
|
|
|
|
if (array_key_exists($id, $this->parents)) {
|
|
return array_values($this->parents[$id]);
|
|
}
|
|
|
|
$this->requireObjectMaps();
|
|
|
|
$parents = [];
|
|
if (array_key_exists($id, $this->objectMaps)) {
|
|
foreach ($this->objectMaps[$id] as $pid) {
|
|
if (array_key_exists($pid, $this->names)) {
|
|
$parents[] = $this->names[$pid];
|
|
} else {
|
|
throw new RuntimeException(sprintf(
|
|
'Got invalid parent id %d for %s "%s"',
|
|
$pid,
|
|
$this->type,
|
|
$object->getObjectName()
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
return $parents;
|
|
}
|
|
|
|
protected function loadObjectMaps()
|
|
{
|
|
$this->requireTree();
|
|
|
|
$map = [];
|
|
$db = $this->db;
|
|
$type = $this->type;
|
|
$table = "icinga_${type}_inheritance";
|
|
|
|
$query = $db->select()->from(
|
|
['i' => $table],
|
|
[
|
|
'object' => "i.${type}_id",
|
|
'parent' => "i.parent_${type}_id",
|
|
]
|
|
)->order('i.weight');
|
|
|
|
foreach ($db->fetchAll($query) as $row) {
|
|
$id = (int) $row->object;
|
|
if (! array_key_exists($id, $map)) {
|
|
$map[$id] = [];
|
|
}
|
|
$map[$id][] = (int) $row->parent;
|
|
}
|
|
|
|
$this->objectMaps = $map;
|
|
}
|
|
|
|
public function listParentIdsFor(IcingaObject $object)
|
|
{
|
|
return array_keys($this->getParentsFor($object));
|
|
}
|
|
|
|
public function listAncestorIdsFor(IcingaObject $object)
|
|
{
|
|
return array_keys($this->getAncestorsFor($object));
|
|
}
|
|
|
|
public function listChildIdsFor(IcingaObject $object)
|
|
{
|
|
return array_keys($this->getChildrenFor($object));
|
|
}
|
|
|
|
public function listDescendantIdsFor(IcingaObject $object)
|
|
{
|
|
return array_keys($this->getDescendantsFor($object));
|
|
}
|
|
|
|
public function getParentsFor(IcingaObject $object)
|
|
{
|
|
// can not use hasBeenLoadedFromDb() when in onStore()
|
|
$id = $object->getProperty('id');
|
|
if ($id !== null) {
|
|
return $this->getParentsById($object->getProperty('id'));
|
|
} else {
|
|
throw new RuntimeException(
|
|
'Loading parents for unstored objects has not been implemented yet'
|
|
);
|
|
// return $this->getParentsForUnstoredObject($object);
|
|
}
|
|
}
|
|
|
|
public function getAncestorsFor(IcingaObject $object)
|
|
{
|
|
if ($object->hasBeenModified()
|
|
&& $object->gotImports()
|
|
&& $object->imports()->hasBeenModified()
|
|
) {
|
|
return $this->getAncestorsForUnstoredObject($object);
|
|
} else {
|
|
return $this->getAncestorsById($object->getProperty('id'));
|
|
}
|
|
}
|
|
|
|
protected function getAncestorsForUnstoredObject(IcingaObject $object)
|
|
{
|
|
$this->requireTree();
|
|
$ancestors = [];
|
|
foreach ($object->imports() as $import) {
|
|
$name = $import->getObjectName();
|
|
if ($import->hasBeenLoadedFromDb()) {
|
|
$pid = $import->get('id');
|
|
} else {
|
|
if (! array_key_exists($name, $this->templateNameToId)) {
|
|
continue;
|
|
}
|
|
$pid = $this->templateNameToId[$name];
|
|
}
|
|
|
|
$this->getAncestorsById($pid, $ancestors);
|
|
|
|
// Hint: inheritance order matters
|
|
if (false !== ($key = array_search($name, $ancestors))) {
|
|
// Note: this used to be just unset($ancestors[$key]), and that
|
|
// broke Apply Rules inheriting from Templates with the same name
|
|
// in a way that related fields no longer showed up (#1602)
|
|
// This new if relaxes this and doesn't unset in case the name
|
|
// matches the original object name. However, I'm still unsure why
|
|
// this was required at all.
|
|
if ($name !== $object->getObjectName()) {
|
|
unset($ancestors[$key]);
|
|
}
|
|
}
|
|
$ancestors[$pid] = $name;
|
|
}
|
|
|
|
return $ancestors;
|
|
}
|
|
|
|
protected function requireObjectMaps()
|
|
{
|
|
if ($this->objectMaps === null) {
|
|
$this->loadObjectMaps();
|
|
}
|
|
}
|
|
|
|
public function getParentsById($id)
|
|
{
|
|
$this->requireTree();
|
|
|
|
if (array_key_exists($id, $this->parents)) {
|
|
return $this->parents[$id];
|
|
}
|
|
|
|
$this->requireObjectMaps();
|
|
if (array_key_exists($id, $this->objectMaps)) {
|
|
$parents = [];
|
|
foreach ($this->objectMaps[$id] as $pid) {
|
|
$parents[$pid] = $this->names[$pid];
|
|
}
|
|
|
|
return $parents;
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
public function getAncestorsById($id, & $ancestors = [])
|
|
{
|
|
foreach ($this->getParentsById($id) as $pid => $name) {
|
|
$this->getAncestorsById($pid, $ancestors);
|
|
|
|
// Hint: inheritance order matters
|
|
if (false !== ($key = array_search($name, $ancestors))) {
|
|
unset($ancestors[$key]);
|
|
}
|
|
$ancestors[$pid] = $name;
|
|
}
|
|
|
|
return $ancestors;
|
|
}
|
|
|
|
public function getChildrenFor(IcingaObject $object)
|
|
{
|
|
// can not use hasBeenLoadedFromDb() when in onStore()
|
|
$id = $object->getProperty('id');
|
|
if ($id !== null) {
|
|
return $this->getChildrenById($id);
|
|
} else {
|
|
throw new RuntimeException(
|
|
'Loading children for unstored objects has not been implemented yet'
|
|
);
|
|
// return $this->getChildrenForUnstoredObject($object);
|
|
}
|
|
}
|
|
|
|
public function getChildrenById($id)
|
|
{
|
|
$this->requireTree();
|
|
|
|
if (array_key_exists($id, $this->children)) {
|
|
return $this->children[$id];
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
public function getDescendantsFor(IcingaObject $object)
|
|
{
|
|
// can not use hasBeenLoadedFromDb() when in onStore()
|
|
$id = $object->getProperty('id');
|
|
if ($id !== null) {
|
|
return $this->getDescendantsById($id);
|
|
} else {
|
|
throw new RuntimeException(
|
|
'Loading descendants for unstored objects has not been implemented yet'
|
|
);
|
|
// return $this->getDescendantsForUnstoredObject($object);
|
|
}
|
|
}
|
|
|
|
public function getDescendantsById($id, & $children = [])
|
|
{
|
|
foreach ($this->getChildrenById($id) as $pid => $name) {
|
|
$this->getDescendantsById($pid, $children);
|
|
$children[$pid] = $name;
|
|
}
|
|
|
|
return $children;
|
|
}
|
|
|
|
public function getTree($parentId = null)
|
|
{
|
|
if ($this->tree === null) {
|
|
$this->prepareTree();
|
|
}
|
|
|
|
if ($parentId === null) {
|
|
return $this->returnFullTree();
|
|
} else {
|
|
throw new RuntimeException(
|
|
'Partial tree fetching has not been implemented yet'
|
|
);
|
|
// return $this->partialTree($parentId);
|
|
}
|
|
}
|
|
|
|
protected function returnFullTree()
|
|
{
|
|
$result = $this->rootNodes;
|
|
foreach ($result as $id => & $node) {
|
|
$this->addChildrenById($id, $node);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
protected function addChildrenById($pid, array & $base)
|
|
{
|
|
foreach ($this->getChildrenById($pid) as $id => $name) {
|
|
$base['children'][$id] = [
|
|
'name' => $name,
|
|
'children' => []
|
|
];
|
|
$this->addChildrenById($id, $base['children'][$id]);
|
|
}
|
|
}
|
|
|
|
protected function prepareTree()
|
|
{
|
|
Benchmark::measure(sprintf('Prepare "%s" Template Tree', $this->type));
|
|
$templates = $this->fetchTemplates();
|
|
$parents = [];
|
|
$rootNodes = [];
|
|
$children = [];
|
|
$names = [];
|
|
foreach ($templates as $row) {
|
|
$id = (int) $row->id;
|
|
$pid = (int) $row->parent_id;
|
|
$names[$id] = $row->name;
|
|
if (! array_key_exists($id, $parents)) {
|
|
$parents[$id] = [];
|
|
}
|
|
|
|
if ($row->parent_id === null) {
|
|
$rootNodes[$id] = [
|
|
'name' => $row->name,
|
|
'children' => []
|
|
];
|
|
continue;
|
|
}
|
|
|
|
$names[$pid] = $row->parent_name;
|
|
$parents[$id][$pid] = $row->parent_name;
|
|
|
|
if (! array_key_exists($pid, $children)) {
|
|
$children[$pid] = [];
|
|
}
|
|
|
|
$children[$pid][$id] = $row->name;
|
|
}
|
|
|
|
$this->parents = $parents;
|
|
$this->children = $children;
|
|
$this->rootNodes = $rootNodes;
|
|
$this->names = $names;
|
|
$this->templateNameToId = array_flip($names);
|
|
Benchmark::measure(sprintf('"%s" Template Tree ready', $this->type));
|
|
}
|
|
|
|
public function fetchObjects()
|
|
{
|
|
//??
|
|
}
|
|
|
|
protected function requireTree()
|
|
{
|
|
if ($this->parents === null) {
|
|
$this->prepareTree();
|
|
}
|
|
}
|
|
|
|
public function fetchTemplates()
|
|
{
|
|
$db = $this->db;
|
|
$type = $this->type;
|
|
$table = "icinga_$type";
|
|
|
|
if ($type === 'command') {
|
|
$joinCondition = $db->quoteInto(
|
|
"p.id = i.parent_${type}_id",
|
|
'template'
|
|
);
|
|
} else {
|
|
$joinCondition = $db->quoteInto(
|
|
"p.id = i.parent_${type}_id AND p.object_type = ?",
|
|
'template'
|
|
);
|
|
}
|
|
|
|
$query = $db->select()->from(
|
|
['o' => $table],
|
|
[
|
|
'id' => 'o.id',
|
|
'name' => 'o.object_name',
|
|
'object_type' => 'o.object_type',
|
|
'parent_id' => 'p.id',
|
|
'parent_name' => 'p.object_name',
|
|
]
|
|
)->joinLeft(
|
|
['i' => $table . '_inheritance'],
|
|
'o.id = i.' . $type . '_id',
|
|
[]
|
|
)->joinLeft(
|
|
['p' => $table],
|
|
$joinCondition,
|
|
[]
|
|
)->order('o.id')->order('i.weight');
|
|
|
|
if ($type !== 'command') {
|
|
$query->where(
|
|
'o.object_type = ?',
|
|
'template'
|
|
);
|
|
}
|
|
// echo '<pre style="padding-top: 6em">' . $query . '</pre>';
|
|
|
|
return $db->fetchAll($query);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
SELECT o.id, o.object_name AS name, o.object_type, p.id AS parent_id,
|
|
p.object_name AS parent_name FROM icinga_service AS o
|
|
RIGHT JOIN icinga_service_inheritance AS i ON o.id = i.service_id
|
|
RIGHT JOIN icinga_service AS p ON p.id = i.parent_service_id
|
|
WHERE (p.object_type = 'template') AND (o.object_type = 'template')
|
|
ORDER BY o.id ASC, i.weight ASC
|
|
|
|
*/
|