baseTable . ' warming up'); $this->db = $db; // Fetch: ignore disabled? $this->prepareNameMaps(); $this->templates = []; foreach ($this->fetchPlainObjects($this->baseTable, 'template') as $template) { $id = $template->id; $this->stripIgnoredProperties($template); $this->stripNullProperties($template); $this->templates[$id] = (array) $template; } $this->templateVars = $this->fetchTemplateVars(); $this->inheritancePaths = $this->fetchInheritancePaths($this->baseTable, 'host_id'); foreach ($this->inheritancePaths as $path) { $this->getResolvedImports($path); } // Using already resolved data, so this is unused right now: // $this->groupMemberShips = $this->fetchAllGroups(); $this->resolvedGroupMemberShips = $this->fetchAllResolvedGroups(); foreach ($this->inheritancePaths as $path) { if (! isset($this->resolvedTemplateProperties[$path])) { $properties = (object) $this->getResolvedProperties($path); $this->replaceRelatedNames($properties); $this->convertBooleans($properties); $this->resolvedTemplateProperties[$path] = $properties; $this->resolvedTemplateVars[$path] = $this->getResolvedVars($path); } } Benchmark::measure('Object Resolver for ' . $this->baseTable . ' is ready'); // Notes: // default != null: // most icinga objects: disabled => n // Icinga(ScheduledDowntime|TimePeriod)Range: range_type => include, merge_behaviour => set // IcingaTemplateChoice: min_required => 0, max_allowed => 1 // IcingaZone: is_global => n // ImportSource: import_state => unknown // SyncRule: sync_state => unknown } protected static function addUniqueMembers(&$list, $newMembers) { foreach (\array_reverse($newMembers) as $member) { $pos = \array_search($member, $list); if ($pos !== false) { unset($list[$pos]); } \array_unshift($list, $member); } } public function fetchResolvedObjects() { $objects = []; $allVars = $this->fetchNonTemplateVars(); foreach ($this->fetchPlainObjects($this->baseTable, 'object') as $object) { $id = $object->id; // id will be stripped $objects[$id] = $this->enrichObject($object, $allVars); } return $objects; } public function fetchObjectsMatchingFilter(Filter $filter) { $filter = clone($filter); DynamicApplyMatches::setType($this->getType()); DynamicApplyMatches::fixFilterColumns($filter); $helper = new AssignFilterHelper($filter); $objects = []; $allVars = $this->fetchNonTemplateVars(); foreach ($this->fetchPlainObjects($this->baseTable, 'object') as $object) { $id = $object->id; // id will be stripped $object = $this->enrichObject($object, $allVars); if ($helper->matches($object)) { $objects[$id] = $object; } } return $objects; } protected function enrichObject($object, $allVars) { $id = $object->id; $this->stripIgnoredProperties($object); if (isset($allVars[$id])) { $vars = $allVars[$id]; } else { $vars = []; } $vars += $this->getInheritedVarsById($id); // There is no merge, +/-, not yet. Unused, as we use resolved groups: // if (isset($this->groupMemberShips[$id])) { // $groups = $this->groupMemberShips[$id]; // } else { // $groups = $this->getInheritedGroupsById($id); // } if (isset($this->resolvedGroupMemberShips[$id])) { $groups = $this->resolvedGroupMemberShips[$id]; } else { $groups = []; } foreach ($this->getInheritedPropertiesById($id) as $property => $value) { if (! isset($object->$property)) { $object->$property = $value; } } $this->replaceRelatedNames($object); $this->convertBooleans($object); $this->stripNullProperties($object); if (! empty($vars)) { $object->vars = (object) $vars; static::flattenVars($object); } if (! empty($groups)) { $object->groups = $groups; } $templates = $this->getTemplateNamesByID($id); if (! empty($templates)) { $object->templates = \array_reverse($templates); } return $object; } /** * @param string $baseTable e.g. icinga_host * @param string $relColumn e.g. host_id * @return array */ protected function fetchInheritancePaths($baseTable, $relColumn) { if ($this->db instanceof \Zend_Db_Adapter_Pdo_Pgsql) { $groupColumn = "ARRAY_TO_STRING(ARRAY_AGG(parent_$relColumn ORDER BY weight), ',')"; } else { $groupColumn = "GROUP_CONCAT(parent_$relColumn ORDER BY weight SEPARATOR ',')"; } $query = $this->db->select() ->from([ 'oi' => "{$baseTable}_inheritance" ], [ $relColumn, $groupColumn ]) ->group($relColumn) // Ordering by length increases the possibility to have less cycles afterwards ->order("LENGTH($groupColumn)"); return $this->db->fetchPairs($query); } protected function getInheritedPropertiesById($objectId) { if (isset($this->inheritancePaths[$objectId])) { return $this->getResolvedProperties($this->inheritancePaths[$objectId]); } else { return []; } } protected function getInheritedVarsById($objectId) { if (isset($this->inheritancePaths[$objectId])) { return $this->getResolvedVars($this->inheritancePaths[$objectId]); } else { return []; } } protected function getInheritedGroupsById($objectId) { if (isset($this->inheritancePaths[$objectId])) { return $this->getResolvedGroups($this->inheritancePaths[$objectId]); } else { return []; } } protected function getTemplateNamesByID($objectId) { if (isset($this->inheritancePaths[$objectId])) { return $this->translateTemplateIdsToNames( $this->getResolvedImports($this->inheritancePaths[$objectId]) ); } else { return []; } } /** * @param $path * @return array[] */ protected function getResolvedProperties($path) { $result = []; // + adds only non existing members, so let's reverse our templates foreach ($this->getResolvedImports($path) as $templateId) { $result += $this->templates[$templateId]; } unset($result['object_name']); return $result; } protected function getResolvedVars($path) { $result = []; foreach ($this->getResolvedImports($path) as $templateId) { $result += $this->getTemplateVars($templateId); } return $result; } protected function getTemplateVars($templateId) { if (isset($this->templateVars[$templateId])) { return $this->templateVars[$templateId]; } else { return []; } } protected function getResolvedGroups($path) { $pos = \strpos($path, ','); if ($pos === false) { if (isset($this->groupMemberShips[$path])) { return $this->groupMemberShips[$path]; } else { return []; } } else { $first = \substr($path, 0, $pos); $parentPath = \substr($path, $pos + 1); $currentGroups = $this->getResolvedVars($first); // There is no merging +/-, not yet if (empty($currentGroups)) { return $this->getResolvedVars($parentPath); } else { return $currentGroups; } } } /** * Hint: this ships most important (last) imports first * * @param $path * @return array */ protected function getResolvedImports($path) { if (! isset($this->flatImports[$path])) { $this->flatImports[$path] = $this->calculateFlatImports($path); } return $this->flatImports[$path]; } protected function calculateFlatImports($path) { $imports = \preg_split('/,/', $path); $ancestors = []; foreach ($imports as $template) { if (isset($this->inheritancePaths[$template])) { $this->addUniqueMembers( $ancestors, $this->calculateFlatImports($this->inheritancePaths[$template]) ); } $this->addUniqueMembers($ancestors, [$template]); } return $ancestors; } protected function fetchPlainObjects($table, $objectType = null) { $query = $this->db->select() ->from(['o' => $table]) ->order('o.object_name'); if ($objectType !== null) { $query->where('o.object_type = ?', $objectType); } return $this->db->fetchAll($query); } /** * @param \stdClass $object */ protected function replaceRelatedNames($object) { foreach ($this->nameMaps as $property => $map) { if (\property_exists($object, $property)) { // Hint: substr strips _id if ($object->$property === null) { $object->{\substr($property, 0, -3)} = null; } else { $object->{\substr($property, 0, -3)} = $map[$object->$property]; } unset($object->$property); } } } protected function translateTemplateIdsToNames($ids) { $names = []; foreach ($ids as $id) { if (isset($this->templates[$id])) { $names[] = $this->templates[$id]['object_name']; } else { throw new \RuntimeException("There is no template with ID $id"); } } return $names; } protected function stripIgnoredProperties($object) { foreach ($this->ignoredProperties as $key) { unset($object->$key); } } public function prepareNameMaps() { // TODO: fetch from dummy Object? How to ignore irrelevant ones like choices? $relatedNames = []; foreach ($this->relatedTables as $key => $relatedTable) { $relatedNames[$key] = $this->fetchRelationMap($this->baseTable, $relatedTable, $key); } $this->nameMaps = $relatedNames; } protected function convertBooleans($object) { foreach ($this->booleans as $property) { if (\property_exists($object, $property) && $object->$property !== null) { // Hint: substr strips _id $object->$property = $object->$property === 'y'; } } } protected function stripNullProperties($object) { foreach (\array_keys((array) $object) as $property) { if ($object->$property === null) { unset($object->$property); } } } protected function fetchRelationMap($sourceTable, $destinationTable, $property) { $query = $this->db->select() ->from(['d' => $destinationTable], ['d.id', 'd.object_name']) ->join(['o' => $sourceTable], "d.id = o.$property", []) ->order('d.object_name'); return $this->db->fetchPairs($query); } protected function fetchTemplateVars() { $query = $this->prepareVarsQuery()->where('o.object_type = ?', 'template'); return $this->fetchAndCombineVars($query); } protected function fetchNonTemplateVars() { $query = $this->prepareVarsQuery()->where('o.object_type != ?', 'template'); return $this->fetchAndCombineVars($query); } protected function fetchAndCombineVars($query) { $vars = []; foreach ($this->db->fetchAll($query) as $var) { $id = $var->object_id; if (! isset($vars[$id])) { $vars[$id] = []; } if ($var->format === 'json') { $vars[$id][$var->varname] = \json_decode($var->varvalue); } else { $vars[$id][$var->varname] = $var->varvalue; } } return $vars; } protected function fetchAllGroups() { $query = $this->prepareGroupsQuery(); return $this->fetchAndCombineGroups($query); } protected function fetchAllResolvedGroups() { $query = $this->prepareGroupsQuery(true); return $this->fetchAndCombineGroups($query); } protected function fetchAndCombineGroups($query) { $groups = []; foreach ($this->db->fetchAll($query) as $group) { $id = $group->object_id; if (isset($groups[$id])) { $groups[$id][$group->group_id] = $group->group_name; } else { $groups[$id] = [ $group->group_id => $group->group_name ]; } } return $groups; } protected function prepareGroupsQuery($resolved = false) { $type = $this->getType(); $groupsTable = $this->baseTable . 'group'; $groupMembershipTable = "{$groupsTable}_$type"; if ($resolved) { $groupMembershipTable .= '_resolved'; } $oRef = "{$type}_id"; $gRef = "{$type}group_id"; return $this->db->select() ->from(['gm' => $groupMembershipTable], [ 'object_id' => $oRef, 'group_id' => $gRef, 'group_name' => 'g.object_name' ]) ->join(['g' => $groupsTable], "g.id = gm.$gRef", []) ->order("gm.$oRef") ->order('g.object_name'); } protected function prepareVarsQuery() { $table = $this->baseTable . '_var'; $ref = $this->getType() . '_id'; return $this->db->select() ->from(['v' => $table], [ 'object_id' => $ref, 'v.varname', 'v.varvalue', 'v.format', // 'v.checksum', ]) ->join(['o' => $this->baseTable], "o.id = v.$ref", []) ->order('o.id') ->order('v.varname'); } protected function getType() { return \preg_replace('/^icinga_/', '', $this->baseTable); } /** * Helper, flattens all vars of a given object * * The object itself will be modified, and the 'vars' property will be * replaced with corresponding 'vars.whatever' properties * * @param $object * @param string $key */ protected static function flattenVars(\stdClass $object, $key = 'vars') { if (\property_exists($object, $key)) { foreach ($object->vars as $k => $v) { if (\is_object($v)) { static::flattenVars($v, $k); } $object->{$key . '.' . $k} = $v; } unset($object->$key); } } }