null, 'host_id' => null, 'object_name' => null, 'object_type' => null, 'description' => null, 'assign_filter' => null, ); protected $keyName = array('host_id', 'object_name'); protected $supportsImports = true; protected $supportsCustomVars = true; protected $supportsApplyRules = true; protected $supportedInLegacy = true; protected $relations = array( 'host' => 'IcingaHost', ); public function isDisabled() { return false; } public function supportsAssignments() { return true; } protected function setKey($key) { if (is_int($key)) { $this->set('id', $key); } elseif (is_string($key)) { $keyComponents = preg_split('~!~', $key); if (count($keyComponents) === 1) { $this->set('object_name', $keyComponents[0]); $this->set('object_type', 'template'); } else { throw new InvalidArgumentException(sprintf( 'Can not parse key: %s', $key )); } } else { return parent::setKey($key); } return $this; } /** * @return IcingaService[] * @throws \Icinga\Exception\NotFoundError */ public function getServiceObjects() { if ($this->get('host_id')) { $imports = $this->imports()->getObjects(); if (empty($imports)) { return array(); } return $this->getServiceObjectsForSet(array_shift($imports)); } else { return $this->getServiceObjectsForSet($this); } } /** * @param IcingaServiceSet $set * @return array * @throws \Icinga\Exception\NotFoundError */ protected function getServiceObjectsForSet(IcingaServiceSet $set) { if ($set->get('id') === null) { return array(); } $connection = $this->getConnection(); $db = $this->getDb(); $ids = $db->fetchCol( $db->select()->from('icinga_service', 'id') ->where('service_set_id = ?', $set->get('id')) ); $services = array(); foreach ($ids as $id) { $service = IcingaService::load(array( 'id' => $id, 'object_type' => 'template' ), $connection); $service->set('service_set', null); $services[$service->getObjectName()] = $service; } return $services; } public function getUniqueIdentifier() { return $this->getObjectName(); } /** * @return object * @throws \Icinga\Exception\NotFoundError */ public function export() { if ($this->get('host_id')) { return $this->exportSetOnHost(); } else { return $this->exportTemplate(); } } protected function exportSetOnHost() { // TODO. throw new RuntimeException('Not yet'); } /** * @return object * @throws \Icinga\Exception\NotFoundError */ protected function exportTemplate() { $props = $this->getProperties(); unset($props['id'], $props['host_id']); $props['services'] = []; foreach ($this->getServiceObjects() as $serviceObject) { $props['services'][$serviceObject->getObjectName()] = $serviceObject->export(); } ksort($props); return (object) $props; } /** * @param $plain * @param Db $db * @param bool $replace * @return IcingaServiceSet * @throws DuplicateKeyException * @throws \Icinga\Exception\NotFoundError */ public static function import($plain, Db $db, $replace = false) { $properties = (array) $plain; $name = $properties['object_name']; if (isset($properties['services'])) { $services = $properties['services']; unset($properties['services']); } else { $services = []; } if ($properties['object_type'] !== 'template') { throw new InvalidArgumentException(sprintf( 'Can import only Templates, got "%s" for "%s"', $properties['object_type'], $name )); } if ($replace && static::exists($name, $db)) { $object = static::load($name, $db); } elseif (static::exists($name, $db)) { throw new DuplicateKeyException( 'Service Set "%s" already exists', $name ); } else { $object = static::create([], $db); } $object->setProperties($properties); // This is not how other imports work, but here we need an ID if (! $object->hasBeenLoadedFromDb()) { $object->store(); } $setId = $object->get('id'); $sQuery = $db->getDbAdapter()->select()->from( ['s' => 'icinga_service'], 's.*' )->where('service_set_id = ?', $setId); $existingServices = IcingaService::loadAll($db, $sQuery, 'object_name'); foreach ($services as $service) { if (isset($service->fields)) { unset($service->fields); } $name = $service->object_name; if (isset($existingServices[$name])) { $existing = $existingServices[$name]; $existing->setProperties((array) $service); $existing->set('service_set_id', $setId); if ($existing->hasBeenModified()) { $existing->store(); } } else { $new = IcingaService::create((array) $service, $db); $new->set('service_set_id', $setId); $new->store(); } } return $object; } /** * @throws \Icinga\Exception\NotFoundError */ public function onDelete() { $hostId = $this->get('host_id'); if ($hostId) { $deleteIds = []; foreach ($this->getServiceObjects() as $service) { $deleteIds[] = (int) $service->get('id'); } if (! empty($deleteIds)) { $db = $this->getDb(); $db->delete( 'icinga_host_service_blacklist', $db->quoteInto( sprintf('host_id = %s AND service_id IN (?)', $hostId), $deleteIds ) ); } } parent::onDelete(); } /** * @param IcingaConfig $config * @throws \Icinga\Exception\NotFoundError */ public function renderToConfig(IcingaConfig $config) { if ($this->get('assign_filter') === null && $this->isTemplate()) { return; } if ($config->isLegacy()) { $this->renderToLegacyConfig($config); return; } $services = $this->getServiceObjects(); if (empty($services)) { return; } $file = $this->getConfigFileWithHeader($config); // Loop over all services belonging to this set // add our assign rules and then add the service to the config // eventually clone them beforehand to not get into trouble with caches // figure out whether we might need a zone property foreach ($services as $service) { if ($filter = $this->get('assign_filter')) { $service->set('object_type', 'apply'); $service->set('assign_filter', $filter); } elseif ($hostId = $this->get('host_id')) { $host = $this->getRelatedObject('host', $hostId)->getObjectName(); if (in_array($host, $this->getBlacklistedHostnames($service))) { continue; } $service->set('object_type', 'object'); $service->set('use_var_overrides', 'y'); $service->set('host_id', $hostId); } else { // Service set template without assign filter or host continue; } $this->copyVarsToService($service); $file->addObject($service); } } /** * @return array */ public function getBlacklistedHostnames($service) { // Hint: if ($this->isApplyRule()) would be nice, but apply rules are // not enough, one might want to blacklist single services from Sets // assigned to single Hosts. if (PrefetchCache::shouldBeUsed()) { $lookup = PrefetchCache::instance()->hostServiceBlacklist(); } else { $lookup = new HostServiceBlacklist($this->getConnection()); } return $lookup->getBlacklistedHostnamesForService($service); } protected function getConfigFileWithHeader(IcingaConfig $config) { $file = $config->configFile( 'zones.d/' . $this->getRenderingZone($config) . '/servicesets' ); $file->addContent($this->getConfigHeaderComment($config)); return $file; } protected function getConfigHeaderComment(IcingaConfig $config) { if ($config->isLegacy()) { if ($this->get('assign_filter')) { $comment = "## applied Service Set '%s'\n\n"; } else { $comment = "## Service Set '%s' on this host\n\n"; } } else { $comment = "/** Service Set '%s' **/\n\n"; } return sprintf($comment, $this->getObjectName()); } public function copyVarsToService(IcingaService $service) { $serviceVars = $service->vars(); foreach ($this->vars() as $k => $var) { $serviceVars->$k = $var; } return $this; } /** * @param IcingaConfig $config * @throws \Icinga\Exception\NotFoundError */ public function renderToLegacyConfig(IcingaConfig $config) { if ($this->get('assign_filter') === null && $this->isTemplate()) { return; } // evaluate my assign rules once, get related hosts // Loop over all services belonging to this set // generate every service with host_name host1,host2... -> not yet. And Zones? $conn = $this->getConnection(); // Delegating this to the service would look, but this way it's faster if ($filter = $this->get('assign_filter')) { $filter = Filter::fromQueryString($filter); $hostnames = HostApplyMatches::forFilter($filter, $conn); } else { $hostnames = array($this->getRelated('host')->getObjectName()); } $blacklists = []; foreach ($this->mapHostsToZones($hostnames) as $zone => $names) { $file = $config->configFile('director/' . $zone . '/servicesets', '.cfg'); $file->addContent($this->getConfigHeaderComment($config)); foreach ($this->getServiceObjects() as $service) { $object_name = $service->getObjectName(); if (! array_key_exists($object_name, $blacklists)) { $blacklists[$object_name] = $service->getBlacklistedHostnames(); } // check if all hosts in the zone ignore this service $zoneNames = array_diff($names, $blacklists[$object_name]); if (empty($zoneNames)) { continue; } $service->set('object_type', 'object'); $service->set('host_id', $names); $this->copyVarsToService($service); $file->addLegacyObject($service); } } } public function getRenderingZone(IcingaConfig $config = null) { if ($this->get('host_id') === null) { return $this->connection->getDefaultGlobalZoneName(); } else { $host = $this->getRelatedObject('host', $this->get('host_id')); return $host->getRenderingZone($config); } } public function createWhere() { $where = parent::createWhere(); if (! $this->hasBeenLoadedFromDb()) { if (null === $this->get('host_id') && null === $this->get('id')) { $where .= " AND object_type = 'template'"; } } return $where; } /** * @return IcingaService[] */ public function fetchServices() { $connection = $this->getConnection(); $db = $connection->getDbAdapter(); /** @var IcingaService[] $services */ $services = IcingaService::loadAll( $connection, $db->select()->from('icinga_service') ->where('service_set_id = ?', $this->get('id')) ); return $services; } /** * Fetch IcingaServiceSet that are based on this set and added to hosts directly * * @return IcingaServiceSet[] */ public function fetchHostSets() { $id = $this->get('id'); if ($id === null) { return []; } $query = $this->db->select() ->from( ['o' => $this->table] )->join( ['ssi' => $this->table . '_inheritance'], 'ssi.service_set_id = o.id', [] )->where( 'ssi.parent_service_set_id = ?', $id ); return static::loadAll($this->connection, $query); } /** * @throws DuplicateKeyException * @throws \Icinga\Exception\NotFoundError */ protected function beforeStore() { parent::beforeStore(); $name = $this->getObjectName(); if ($this->isObject() && $this->get('host_id') === null) { throw new InvalidArgumentException( 'A Service Set cannot be an object with no related host' ); } // checking if template object_name is unique // TODO: Move to IcingaObject if (! $this->hasBeenLoadedFromDb() && $this->isTemplate() && static::exists($name, $this->connection)) { throw new DuplicateKeyException( '%s template "%s" already existing in database!', $this->getType(), $name ); } } public function toSingleIcingaConfig() { $config = parent::toSingleIcingaConfig(); try { foreach ($this->fetchHostSets() as $set) { $set->renderToConfig($config); } } catch (Exception $e) { $config->configFile( 'failed-to-render' )->prepend( "/** Failed to render this object **/\n" . '/* ' . $e->getMessage() . ' */' ); } return $config; } }