diff --git a/library/Director/Objects/GroupMembershipResolver.php b/library/Director/Objects/GroupMembershipResolver.php new file mode 100644 index 00000000..2507813e --- /dev/null +++ b/library/Director/Objects/GroupMembershipResolver.php @@ -0,0 +1,509 @@ +connection = $connection; + $this->db = $connection->getDbAdapter(); + } + + /** + * @return $this + */ + public function refreshAllMappings() + { + return $this->clearGroups()->clearObjects()->refreshDb(true); + } + + /** + * @param bool $force + * @return $this + */ + public function refreshDb($force = false) + { + if ($force || ! $this->isDeferred()) { + Benchmark::measure('Going to refresh all group mappings'); + $this->fetchStoredMappings(); + Benchmark::measure('Got stored HG mappings, rechecking all objects'); + $this->recheckAllObjects($this->getAppliedGroups()); + Benchmark::measure('Ready, going to store new mappings'); + $this->storeNewMappings(); + $this->removeOutdatedMappings(); + } + + return $this; + } + + /** + * @param bool $defer + * @return $this + */ + public function defer($defer = true) + { + $this->deferred = $defer; + return $this; + } + + /** + * @param $use + * @return $this + */ + public function setUseTransactions($use) + { + $this->useTransactions = $use; + return $this; + } + + public function getType() + { + if ($this->type === null) { + throw new ProgrammingError( + '"type" is required when extending %s, got none in %s', + __CLASS__, + get_class($this) + ); + } + + return $this->type; + } + + /** + * @return bool + */ + public function isDeferred() + { + return $this->deferred; + } + + /** + * @param IcingaObject $object + * @return $this + */ + public function addObject(IcingaObject $object) + { + $this->assertBeenLoadedFromDb($object); + if ($this->objects === null) { + $this->objects = array(); + } + + $this->objects[$object->get('id')] = $object; + return $this; + } + + /** + * @param IcingaObject[] $objects + * @return $this + */ + public function addObjects(array $objects) + { + foreach ($objects as $object) { + $this->addObject($object); + } + + return $this; + } + + /** + * @param IcingaObject $object + * @return $this + */ + public function setObject(IcingaObject $object) + { + $this->clearObjects(); + return $this->addObject($object); + } + + /** + * @param IcingaObject[] $objects + * @return $this + */ + public function setObjects(array $objects) + { + $this->clearObjects(); + return $this->addObjects($objects); + } + + /** + * @return $this + */ + public function clearObjects() + { + $this->objects = array(); + return $this; + } + + /** + * @param IcingaObjectGroup $group + * @return $this + */ + public function addGroup(IcingaObjectGroup $group) + { + $this->assertBeenLoadedFromDb($group); + + if ($group->get('assign_filter') !== null) { + $this->groups[$group->get('id')] = $group; + } + + return $this; + } + + /** + * @param IcingaObjectGroup[] $groups + * @return $this + */ + public function addGroups(array $groups) + { + foreach ($groups as $group) { + $this->addGroup($group); + } + + return $this; + } + + /** + * @param IcingaObjectGroup $group + * @return $this + */ + public function setGroup(IcingaObjectGroup $group) + { + $this->clearGroups(); + return $this->addGroup($group); + } + + /** + * @param array $groups + * @return $this + */ + public function setGroups(array $groups) + { + $this->clearGroups(); + return $this->addGroups($groups); + } + + /** + * @return $this + */ + public function clearGroups() + { + $this->objects = array(); + return $this; + } + + protected function storeNewMappings() + { + $diff = $this->getDifference($this->newMappings, $this->existingMappings); + $count = count($diff); + if ($count === 0) { + return; + } + + $db = $this->db; + $this->beginTransaction(); + foreach ($diff as $row) { + $db->insert( + $this->getResolvedTableName(), + $row + ); + } + + $this->commit(); + Benchmark::measure( + sprintf( + 'Stored %d new resolved group memberships', + $count + ) + ); + } + + protected function removeOutdatedMappings() + { + $diff = $this->getDifference($this->existingMappings, $this->newMappings); + $count = count($diff); + if ($count === 0) { + return; + } + + $type = $this->getType(); + $db = $this->db; + $this->beginTransaction(); + foreach ($diff as $row) { + $db->delete( + $this->getResolvedTableName(), + sprintf( + "(${type}group_id = %d AND ${type}_id = %d)", + $row["${type}group_id"], + $row["${type}_id"] + ) + ); + } + + $this->commit(); + Benchmark::measure( + sprintf( + 'Removed %d outdated group memberships', + $count + ) + ); + } + + protected function getDifference(& $left, & $right) + { + $diff = array(); + + $type = $this->getType(); + foreach ($left as $groupId => $objectIds) { + if (array_key_exists($groupId, $right)) { + foreach ($objectIds as $objectId) { + if (! array_key_exists($objectId, $right[$groupId])) { + $diff[] = array( + "${type}group_id" => $groupId, + "${type}_id" => $objectId, + ); + } + } + } else { + foreach ($objectIds as $objectId) { + $diff[] = array( + "${type}group_id" => $groupId, + "${type}_id" => $objectId, + ); + } + } + } + + return $diff; + } + + protected function fetchStoredMappings() + { + $mappings = array(); + + $type = $this->getType(); + $query = $this->db->select()->from( + array('hgh' => $this->getResolvedTableName()), + array( + 'group_id' => "${type}group_id", + 'object_id' => "${type}_id", + ) + ); + $this->addMembershipWhere($query, "${type}_id", $this->objects); + $this->addMembershipWhere($query, "${type}group_id", $this->groups); + + foreach ($this->db->fetchAll($query) as $row) { + $groupId = $row->group_id; + $objectId = $row->object_id; + if (! array_key_exists($groupId, $mappings)) { + $mappings[$groupId] = array(); + } + + $mappings[$groupId][$objectId] = $objectId; + } + + $this->existingMappings = $mappings; + } + + /** + * @param ZfSelect $query + * @param string $column + * @param IcingaObject[] $objects + * @return ZfSelect + */ + protected function addMembershipWhere(ZfSelect $query, $column, & $objects) + { + if (empty($objects)) { + return $query; + } + + $ids = array(); + foreach ($objects as $object) { + $ids[] = (int) $object->get('id'); + } + + if (count($ids) === 1) { + $query->orWhere($column . ' = ?', $ids[0]); + } else { + $query->orWhere($column . ' IN (?)', $ids); + } + + return $query; + } + + protected function recheckAllObjects($groups) + { + $mappings = array(); + + foreach ($this->getObjects() as $object) { + // TODO: fix this last hard host dependency + $resolver = HostApplyMatches::prepare($object); + foreach ($groups as $groupId => $filter) { + if ($resolver->matchesFilter(Filter::fromQueryString($filter))) { + if (! array_key_exists($groupId, $mappings)) { + $mappings[$groupId] = array(); + } + + $id = $object->get('id'); + $mappings[$groupId][$id] = $id; + } + } + } + + foreach ($this->fetchMissingSingleAssignments() as $row) { + $mappings[$row->group_id][$row->object_id] = $row->object_id; + } + + $this->newMappings = $mappings; + } + + protected function getAppliedGroups() + { + if (empty($this->groups)) { + return $this->fetchAppliedGroups(); + } else { + return $this->buildAppliedGroups(); + } + } + + protected function buildAppliedGroups() + { + $list = array(); + foreach ($this->groups as $id => $group) { + $list[$id] = $group->get('assign_filter'); + } + + return $list; + } + + protected function fetchAppliedGroups() + { + $type = $this->getType(); + $query = $this->db->select()->from( + array('hg' => "icinga_${type}group"), + array( + 'id', + 'assign_filter', + ) + )->where('assign_filter IS NOT NULL'); + + return $this->db->fetchPairs($query); + } + + protected function fetchMissingSingleAssignments() + { + $type = $this->getType(); + $query = $this->db->select()->from( + array("go" => $this->getTableName()), + array( + 'object_id' => "${type}_id", + 'group_id' => "${type}group_id", + ) + )->joinLeft( + array("gor" => $this->getResolvedTableName()), + "go.${type}_id = gor.${type}_id AND go.${type}group_id = gor.${type}group_id", + array() + )->where("hghr.${type}_id IS NULL"); + + $this->addMembershipWhere($query, "go.${type}_id", $this->objects); + $this->addMembershipWhere($query, "go.${type}group_id", $this->groups); + + return $this->db->fetchAll($query); + } + + protected function getTableName() + { + $type = $this->getType(); + return "icinga_${type}group_${type}"; + } + + protected function getResolvedTableName() + { + return $this->getTableName() . '_resolved'; + } + + /** + * @return $this + */ + protected function beginTransaction() + { + if ($this->useTransactions) { + $this->db->beginTransaction(); + } + + return $this; + } + + /** + * @return $this + */ + protected function commit() + { + if ($this->useTransactions) { + $this->db->commit(); + } + + return $this; + } + + /** + * @return IcingaObject[] + */ + protected function getObjects() + { + if ($this->objects === null) { + $this->objects = IcingaObject::loadAllByType($this->getType(), $this->connection); + } + + return $this->objects; + } + + protected function assertBeenLoadedFromDb(IcingaObject $object) + { + if (! ctype_digit($object->get('id'))) { + throw new ProgrammingError( + 'Group resolver does not support unstored objects' + ); + } + } +} diff --git a/library/Director/Objects/HostGroupMembershipResolver.php b/library/Director/Objects/HostGroupMembershipResolver.php index 13f6293b..b5970178 100644 --- a/library/Director/Objects/HostGroupMembershipResolver.php +++ b/library/Director/Objects/HostGroupMembershipResolver.php @@ -2,477 +2,7 @@ namespace Icinga\Module\Director\Objects; -use Icinga\Application\Benchmark; -use Icinga\Data\Filter\Filter; -use Icinga\Exception\ProgrammingError; -use Icinga\Module\Director\Db; -use Zend_Db_Select as ZfSelect; - -/** - * Class HostGroupMembershipResolver - * - * - Fetches all involved assignments - * - Fetch all (or one) host - * - Fetch all (or one) group - */ -class HostGroupMembershipResolver +class HostGroupMembershipResolver extends GroupMembershipResolver { - /** @var array */ - protected $existingMappings; - - /** @var array */ - protected $newMappings; - - /** @var Db */ - protected $connection; - - /** @var \Zend_Db_Adapter_Abstract */ - protected $db; - - /** @var IcingaHost[] */ - protected $hosts; - - /** @var IcingaHostGroup[] */ - protected $hostgroups = array(); - - protected $table = 'icinga_hostgroup_host_resolved'; - - /** @var bool */ - protected $deferred = false; - - /** @var bool */ - protected $useTransactions = false; - - public function __construct(Db $connection) - { - $this->connection = $connection; - $this->db = $connection->getDbAdapter(); - } - - /** - * @return $this - */ - public function refreshAllMappings() - { - return $this->clearHostgroups()->clearHosts()->refreshDb(true); - } - - /** - * @param bool $force - * @return $this - */ - public function refreshDb($force = false) - { - if ($force || ! $this->isDeferred()) { - Benchmark::measure('Going to refresh all hostgroup mappings'); - $this->fetchStoredMappings(); - Benchmark::measure('Got stored HG mappings, rechecking all hosts'); - $this->recheckAllHosts($this->getAppliedHostgroups()); - Benchmark::measure('Ready, going to store new mappings'); - $this->storeNewMappings(); - $this->removeOutdatedMappings(); - } - - return $this; - } - - /** - * @param bool $defer - * @return $this - */ - public function defer($defer = true) - { - $this->deferred = $defer; - return $this; - } - - /** - * @param $use - * @return $this - */ - public function setUseTransactions($use) - { - $this->useTransactions = $use; - return $this; - } - - /** - * @return bool - */ - public function isDeferred() - { - return $this->deferred; - } - - /** - * @param IcingaHost $host - * @return $this - */ - public function addHost(IcingaHost $host) - { - $this->assertBeenLoadedFromDb($host); - if ($this->hosts === null) { - $this->hosts = array(); - } - - $this->hosts[$host->get('id')] = $host; - return $this; - } - - /** - * @param IcingaHost[] $hosts - * @return $this - */ - public function addHosts(array $hosts) - { - foreach ($hosts as $host) { - $this->addHost($host); - } - - return $this; - } - - /** - * @param IcingaHost $host - * @return $this - */ - public function setHost(IcingaHost $host) - { - $this->clearHosts(); - return $this->addHost($host); - } - - /** - * @param IcingaHost[] $hosts - * @return $this - */ - public function setHosts(array $hosts) - { - $this->clearHosts(); - return $this->addHosts($hosts); - } - - /** - * @return $this - */ - public function clearHosts() - { - $this->hosts = array(); - return $this; - } - - /** - * @param IcingaHostGroup $group - * @return $this - */ - public function addHostgroup(IcingaHostGroup $group) - { - $this->assertBeenLoadedFromDb($group); - - if ($group->get('assign_filter') !== null) { - $this->hostgroups[$group->get('id')] = $group; - } - - return $this; - } - - /** - * @param IcingaHostGroup[] $groups - * @return $this - */ - public function addHostgroups(array $groups) - { - foreach ($groups as $group) { - $this->addHostgroup($group); - } - - return $this; - } - - /** - * @param IcingaHostGroup $group - * @return $this - */ - public function setHostgroup(IcingaHostGroup $group) - { - $this->clearHostgroups(); - return $this->addHostgroup($group); - } - - /** - * @param array $groups - * @return $this - */ - public function setHostgroups(array $groups) - { - $this->clearHostgroups(); - return $this->addHostgroups($groups); - } - - /** - * @return $this - */ - public function clearHostgroups() - { - $this->hosts = array(); - return $this; - } - - protected function storeNewMappings() - { - $diff = $this->getDifference($this->newMappings, $this->existingMappings); - $count = count($diff); - if ($count === 0) { - return; - } - - $db = $this->db; - $this->beginTransaction(); - foreach ($diff as $row) { - $db->insert( - $this->table, - $row - ); - } - - $this->commit(); - Benchmark::measure( - sprintf( - 'Stored %d new resolved hostgroup memberships', - $count - ) - ); - } - - protected function removeOutdatedMappings() - { - $diff = $this->getDifference($this->existingMappings, $this->newMappings); - $count = count($diff); - if ($count === 0) { - return; - } - - $db = $this->db; - $this->beginTransaction(); - foreach ($diff as $row) { - $db->delete( - $this->table, - sprintf( - '(hostgroup_id = %d AND host_id = %d)', - $row['hostgroup_id'], - $row['host_id'] - ) - ); - } - - $this->commit(); - Benchmark::measure( - sprintf( - 'Removed %d outdated hostgroup memberships', - $count - ) - ); - } - - protected function getDifference(& $left, & $right) - { - $diff = array(); - - foreach ($left as $groupId => $hostIds) { - if (array_key_exists($groupId, $right)) { - foreach ($hostIds as $hostId) { - if (! array_key_exists($hostId, $right[$groupId])) { - $diff[] = array( - 'hostgroup_id' => $groupId, - 'host_id' => $hostId, - ); - } - } - } else { - foreach ($hostIds as $hostId) { - $diff[] = array( - 'hostgroup_id' => $groupId, - 'host_id' => $hostId, - ); - } - } - } - - return $diff; - } - - protected function fetchStoredMappings() - { - $mappings = array(); - - $query = $this->db->select()->from( - array('hgh' => $this->table), - array( - 'hostgroup_id', - 'host_id', - ) - ); - $this->addMembershipWhere($query, 'host_id', $this->hosts); - $this->addMembershipWhere($query, 'hostgroup_id', $this->hostgroups); - - foreach ($this->db->fetchAll($query) as $row) { - $groupId = $row->hostgroup_id; - $hostId = $row->host_id; - if (! array_key_exists($groupId, $mappings)) { - $mappings[$groupId] = array(); - } - - $mappings[$groupId][$hostId] = $hostId; - } - - $this->existingMappings = $mappings; - } - - /** - * @param ZfSelect $query - * @param string $column - * @param IcingaObject[] $objects - * @return ZfSelect - */ - protected function addMembershipWhere(ZfSelect $query, $column, & $objects) - { - if (empty($objects)) { - return $query; - } - - $ids = array(); - foreach ($objects as $object) { - $ids[] = (int) $object->get('id'); - } - - if (count($ids) === 1) { - $query->orWhere($column . ' = ?', $ids[0]); - } else { - $query->orWhere($column . ' IN (?)', $ids); - } - - return $query; - } - - protected function recheckAllHosts($groups) - { - $mappings = array(); - - foreach ($this->getHosts() as $host) { - $resolver = HostApplyMatches::prepare($host); - foreach ($groups as $groupId => $filter) { - if ($resolver->matchesFilter(Filter::fromQueryString($filter))) { - if (! array_key_exists($groupId, $mappings)) { - $mappings[$groupId] = array(); - } - - $id = $host->get('id'); - $mappings[$groupId][$id] = $id; - } - } - } - - foreach ($this->fetchMissingSingleAssignments() as $row) { - $mappings[$row->hostgroup_id][$row->host_id] = $row->host_id; - } - - $this->newMappings = $mappings; - } - - protected function getAppliedHostgroups() - { - if (empty($this->hostgroups)) { - return $this->fetchAppliedHostgroups(); - } else { - return $this->buildAppliedHostgroups(); - } - } - - protected function buildAppliedHostgroups() - { - $list = array(); - foreach ($this->hostgroups as $id => $group) { - $list[$id] = $group->get('assign_filter'); - } - - return $list; - } - - protected function fetchAppliedHostgroups() - { - $query = $this->db->select()->from( - array('hg' => 'icinga_hostgroup'), - array( - 'id', - 'assign_filter', - ) - )->where('assign_filter IS NOT NULL'); - - return $this->db->fetchPairs($query); - } - - protected function fetchMissingSingleAssignments() - { - $query = $this->db->select()->from( - array('hgh' => 'icinga_hostgroup_host'), - array( - 'host_id', - 'hostgroup_id', - ) - )->joinLeft( - array('hghr' => 'icinga_hostgroup_host_resolved'), - 'hgh.host_id = hghr.host_id AND hgh.hostgroup_id = hghr.hostgroup_id', - array() - )->where('hghr.host_id IS NULL'); - - $this->addMembershipWhere($query, 'hgh.host_id', $this->hosts); - $this->addMembershipWhere($query, 'hgh.hostgroup_id', $this->hostgroups); - - return $this->db->fetchAll($query); - } - - /** - * @return $this - */ - protected function beginTransaction() - { - if ($this->useTransactions) { - $this->db->beginTransaction(); - } - - return $this; - } - - /** - * @return $this - */ - protected function commit() - { - if ($this->useTransactions) { - $this->db->commit(); - } - - return $this; - } - - /** - * @return IcingaHost[] - */ - protected function getHosts() - { - if ($this->hosts === null) { - $this->hosts = IcingaHost::loadAll($this->connection); - } - - return $this->hosts; - } - - protected function assertBeenLoadedFromDb(IcingaObject $object) - { - if (! ctype_digit($object->get('id'))) { - throw new ProgrammingError( - 'Hostgroup resolver does not support unstored objects' - ); - } - } + protected $type = 'host'; } diff --git a/library/Director/Objects/IcingaHost.php b/library/Director/Objects/IcingaHost.php index 2e43ab20..65879aa4 100644 --- a/library/Director/Objects/IcingaHost.php +++ b/library/Director/Objects/IcingaHost.php @@ -298,7 +298,7 @@ class IcingaHost extends IcingaObject protected function notifyResolvers() { $resolver = $this->getHostGroupMembershipResolver(); - $resolver->addHost($this); + $resolver->addObject($this); $resolver->refreshDb(); return $this; diff --git a/library/Director/Objects/IcingaHostGroup.php b/library/Director/Objects/IcingaHostGroup.php index 0d1ec0e0..b5cffafa 100644 --- a/library/Director/Objects/IcingaHostGroup.php +++ b/library/Director/Objects/IcingaHostGroup.php @@ -48,7 +48,7 @@ class IcingaHostGroup extends IcingaObjectGroup protected function notifyResolvers() { $resolver = $this->getHostGroupMembershipResolver(); - $resolver->addHostgroup($this); + $resolver->addGroup($this); $resolver->refreshDb(); return $this;