From a80c295314e5ff3b9f469fe82be8f61f7450214d Mon Sep 17 00:00:00 2001 From: Alexander Fuhr Date: Wed, 17 Jun 2015 19:03:23 +0200 Subject: [PATCH] IcingaConfig: generate and store if modified resolves #9434 --- .../Director/IcingaConfig/IcingaConfig.php | 187 +++++++++++++++++- .../IcingaConfig/IcingaConfigFile.php | 10 +- 2 files changed, 186 insertions(+), 11 deletions(-) diff --git a/library/Director/IcingaConfig/IcingaConfig.php b/library/Director/IcingaConfig/IcingaConfig.php index 28db654b..a51c0707 100644 --- a/library/Director/IcingaConfig/IcingaConfig.php +++ b/library/Director/IcingaConfig/IcingaConfig.php @@ -10,8 +10,35 @@ class IcingaConfig { protected $files = array(); - protected function __construct() + protected $checksum; + + protected $lastActivityChecksum; + + /** + * @var \Zend_Db_Adapter_Abstract + */ + protected $db; + + protected $connection; + + protected $generationTime; + + public static $table = 'director_generated_config'; + + protected function __construct(DbConnection $connection) { + $this->connection = $connection; + $this->db = $connection->getDbAdapter(); + } + + public function getChecksum() + { + return $this->checksum; + } + + public function getHexChecksum() + { + return current(unpack('H*', $this->checksum)); } public function getFiles() @@ -19,15 +46,130 @@ class IcingaConfig return $this->files; } - public static function generate(DbConnection $db) + public function getFileNames() { - $config = new static; - return $config->loadFromDb($db); + return array_keys($this->files); } - protected function loadFromDb(DbConnection $db) + public function getMissingFiles($missing) { - $this->db = $db; + $files = array(); + foreach ($this->files as $name => $file) { + $files[] = $name . '=' . $file->getChecksum(); + } + return $files; + } + + public static function generate(DbConnection $connection) + { + $config = new static($connection); + return $config->storeIfModified(); + } + + protected function storeIfModified() + { + $this->generateFromDb(); + $checksum = $this->calculateChecksum(); + $exists = $this->db->fetchOne( + $this->db->select()->from(self::$table, 'COUNT(*)')->where('checksum = ?', $checksum) + ); + if ((int) $exists === 0) { + $this->store(); + } + return $this; + } + + protected function calculateChecksum() + { + $files = array($this->getLastActivityHexChecksum()); + $sortedFiles = $this->files; + ksort($sortedFiles); + /** @var IcingaConfigFile $file */ + foreach ($sortedFiles as $name => $file) { + $files[] = $name . '=' . $file->getHexChecksum(); + } + + $this->checksum = sha1(implode(';', $files), true); + return $this->checksum; + } + + public function getFilesChecksums() + { + $checksums = array(); + + /** @var IcingaConfigFile $file */ + foreach ($this->files as $name => $file) { + $checksums[] = $file->getChecksum(); + } + + return $checksums; + } + + protected function store() + { + + $fileTable = IcingaConfigFile::$table; + $fileKey = IcingaConfigFile::$keyName; + + $this->db->beginTransaction(); + try { + $existingQuery = $this->db->select() + ->from($fileTable, 'checksum') + ->where('checksum IN (?)', $this->getFilesChecksums()); + + $existing = $this->db->fetchCol($existingQuery); + + $missing = array_diff($this->getFilesChecksums(), $existing); + + /** @var IcingaConfigFile $file */ + foreach ($this->files as $name => $file) { + $checksum = $file->getChecksum(); + if (! in_array($checksum, $missing)) { + continue; + } + $this->db->insert( + $fileTable, + array( + $fileKey => $checksum, + 'content' => $file->getContent() + ) + ); + } + + $this->db->insert( + self::$table, + array( + 'duration' => $this->generationTime, + 'last_activity_checksum' => $this->getLastActivityChecksum(), + 'checksum' => $this->getChecksum(), + ) + ); + + /** @var IcingaConfigFile $file */ + foreach ($this->files as $name => $file) { + $this->db->insert( + 'director_generated_config_file', + array( + 'config_checksum' => $this->getChecksum(), + 'file_checksum' => $file->getChecksum(), + 'file_path' => $name, + ) + ); + } + + $this->db->commit(); + } catch (\Exception $e) { + $this->db->rollBack(); + var_dump($e->getMessage()); + } + + return $this; + } + + protected function generateFromDb() + { + $start = microtime(true); + $this ->createFileFromDb('zone') ->createFileFromDb('endpoint') @@ -40,18 +182,17 @@ class IcingaConfig ->createFileFromDb('user') ; + $this->generationTime = (microtime(true) - $start) * 1000; + return $this; } protected function createFileFromDb($type) { $class = 'Icinga\\Module\\Director\\Objects\\Icinga' . ucfirst($type); - $objects = $class::loadAll($this->db); + $objects = $class::loadAll($this->connection); if (! empty($objects)) { - $class = 'Icinga\\Module\\Director\\IcingaConfig\\Icinga' - . ucfirst($type) - . 'sConfigFile'; $file = new IcingaConfigFile(); if ($type === 'command') { $file->prepend("library \"methods\"\n\n"); @@ -62,4 +203,30 @@ class IcingaConfig return $this; } + + public function getLastActivityHexChecksum() + { + return current(unpack('H*', $this->getLastActivityChecksum())); + } + + /** + * @return mixed + */ + public function getLastActivityChecksum() + { + if ($this->lastActivityChecksum === null) { + $query = $this->db->select() + ->from('director_activity_log', 'checksum') + ->order('change_time DESC') + ->limit(1); + + $this->lastActivityChecksum = $this->db->fetchOne($query); + } + + return $this->lastActivityChecksum; + } + + // TODO: wipe unused files + // DELETE f FROM director_generated_file f left join director_generated_config_file cf ON f.checksum = cf.file_checksum WHERE cf.file_checksum IS NULL; + } diff --git a/library/Director/IcingaConfig/IcingaConfigFile.php b/library/Director/IcingaConfig/IcingaConfigFile.php index e8c80df3..cb36a572 100644 --- a/library/Director/IcingaConfig/IcingaConfigFile.php +++ b/library/Director/IcingaConfig/IcingaConfigFile.php @@ -6,6 +6,9 @@ use Icinga\Module\Director\Objects\IcingaObject; class IcingaConfigFile { + public static $table = 'director_generated_file'; + public static $keyName = 'checksum'; + protected $content; public function prepend($content) @@ -19,11 +22,16 @@ class IcingaConfigFile return $this->content; } - public function getChecksum() + public function getHexChecksum() { return sha1($this->content); } + public function getChecksum() + { + return sha1($this->content, true); + } + public function addObjects($objects) { foreach ($objects as $object) {