2015-06-11 22:48:41 +02:00
< ? php
namespace Icinga\Module\Director\IcingaConfig ;
2015-10-15 20:35:21 +02:00
use Icinga\Exception\ProgrammingError ;
2015-12-15 16:46:19 +01:00
use Icinga\Module\Director\Db ;
2015-06-23 14:37:23 +02:00
use Icinga\Module\Director\Util ;
2015-06-11 22:48:41 +02:00
use Icinga\Module\Director\Objects\IcingaHost ;
2015-12-18 10:53:27 +01:00
use Icinga\Module\Director\Objects\IcingaZone ;
2015-12-03 19:43:08 +01:00
use Icinga\Module\Director\Objects\IcingaEndpoint ;
2016-01-19 16:45:20 +01:00
use Icinga\Application\Benchmark ;
2015-10-15 20:35:21 +02:00
use Icinga\Web\Hook ;
2015-06-23 16:03:56 +02:00
use Exception ;
2015-06-11 22:48:41 +02:00
class IcingaConfig
{
protected $files = array ();
2015-06-17 19:03:23 +02:00
protected $checksum ;
2015-07-28 15:22:48 +02:00
protected $zoneMap = array ();
2015-06-17 19:03:23 +02:00
protected $lastActivityChecksum ;
/**
* @ var \Zend_Db_Adapter_Abstract
*/
protected $db ;
protected $connection ;
protected $generationTime ;
public static $table = 'director_generated_config' ;
2015-12-15 16:46:19 +01:00
protected function __construct ( Db $connection )
2015-06-17 19:03:23 +02:00
{
$this -> connection = $connection ;
$this -> db = $connection -> getDbAdapter ();
}
2015-12-16 16:19:58 +01:00
public function getSize ()
{
$size = 0 ;
foreach ( $this -> getFiles () as $file ) {
$size += $file -> getSize ();
}
return $size ;
}
public function getDuration ()
{
return $this -> duration ;
}
public function getFileCount ()
{
return count ( $this -> files );
}
public function getObjectCount ()
{
$cnt = 0 ;
foreach ( $this -> getFiles () as $file ) {
$cnt += $file -> getObjectCount ();
}
return $cnt ;
}
public function getTemplateCount ()
{
$cnt = 0 ;
foreach ( $this -> getFiles () as $file ) {
$cnt += $file -> getTemplateCount ();
}
return $cnt ;
}
2015-06-17 19:03:23 +02:00
public function getChecksum ()
{
return $this -> checksum ;
}
public function getHexChecksum ()
2015-06-11 22:48:41 +02:00
{
2015-06-23 14:37:23 +02:00
return Util :: binary2hex ( $this -> checksum );
2015-06-11 22:48:41 +02:00
}
public function getFiles ()
{
return $this -> files ;
}
2015-07-23 15:36:08 +02:00
public function getFileContents ()
{
$result = array ();
foreach ( $this -> files as $name => $file ) {
$result [ $name ] = $file -> getContent ();
}
return $result ;
}
2015-06-17 19:03:23 +02:00
public function getFileNames ()
{
return array_keys ( $this -> files );
}
2015-12-17 10:54:38 +01:00
public function getFile ( $name )
{
return $this -> files [ $name ];
}
2015-06-17 19:03:23 +02:00
public function getMissingFiles ( $missing )
{
$files = array ();
foreach ( $this -> files as $name => $file ) {
$files [] = $name . '=' . $file -> getChecksum ();
}
return $files ;
}
2015-12-15 16:46:19 +01:00
public static function load ( $checksum , Db $connection )
2015-06-18 10:54:44 +02:00
{
$config = new static ( $connection );
$config -> loadFromDb ( $checksum );
return $config ;
}
2015-12-15 16:46:19 +01:00
public static function generate ( Db $connection )
2015-06-17 19:03:23 +02:00
{
$config = new static ( $connection );
return $config -> storeIfModified ();
}
2015-12-15 16:46:19 +01:00
public static function wouldChange ( Db $connection )
2015-11-06 09:47:36 +01:00
{
$config = new static ( $connection );
return $config -> hasBeenModified ();
}
2015-11-03 09:10:34 +01:00
protected function hasBeenModified ()
2015-06-17 19:03:23 +02:00
{
$this -> generateFromDb ();
2015-10-15 20:35:21 +02:00
$this -> collectExtraFiles ();
2015-06-17 19:03:23 +02:00
$checksum = $this -> calculateChecksum ();
$exists = $this -> db -> fetchOne (
2015-11-03 09:10:34 +01:00
$this -> db -> select () -> from (
self :: $table ,
'COUNT(*)'
) -> where (
'checksum = ?' ,
$this -> dbBin ( $checksum )
)
2015-06-17 19:03:23 +02:00
);
2015-11-03 09:10:34 +01:00
return ( int ) $exists === 0 ;
}
protected function storeIfModified ()
{
if ( $this -> hasBeenModified ()) {
2015-06-17 19:03:23 +02:00
$this -> store ();
}
2015-11-03 09:10:34 +01:00
2015-06-17 19:03:23 +02:00
return $this ;
}
2015-06-23 15:13:34 +02:00
protected function dbBin ( $binary )
{
if ( $this -> connection -> getDbType () === 'pgsql' ) {
return Util :: pgBinEscape ( $binary );
} else {
return $binary ;
}
}
2015-06-17 19:03:23 +02:00
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 ()
2015-06-11 22:48:41 +02:00
{
2015-06-17 19:03:23 +02:00
$checksums = array ();
/** @var IcingaConfigFile $file */
foreach ( $this -> files as $name => $file ) {
$checksums [] = $file -> getChecksum ();
}
return $checksums ;
}
2015-07-28 15:22:48 +02:00
protected function getZoneName ( $id )
{
return $this -> zoneMap [ $id ];
}
2015-06-17 19:03:23 +02:00
protected function store ()
{
$fileTable = IcingaConfigFile :: $table ;
$fileKey = IcingaConfigFile :: $keyName ;
$this -> db -> beginTransaction ();
try {
$existingQuery = $this -> db -> select ()
-> from ( $fileTable , 'checksum' )
2015-06-23 16:45:25 +02:00
-> where ( 'checksum IN (?)' , array_map ( array ( $this , 'dbBin' ), $this -> getFilesChecksums ()));
2015-06-17 19:03:23 +02:00
$existing = $this -> db -> fetchCol ( $existingQuery );
2015-11-30 15:31:54 +01:00
2015-06-23 16:45:25 +02:00
foreach ( $existing as $key => $val ) {
if ( is_resource ( $val )) {
$existing [ $key ] = stream_get_contents ( $val );
}
}
2015-06-17 19:03:23 +02:00
$missing = array_diff ( $this -> getFilesChecksums (), $existing );
/** @var IcingaConfigFile $file */
foreach ( $this -> files as $name => $file ) {
$checksum = $file -> getChecksum ();
if ( ! in_array ( $checksum , $missing )) {
continue ;
}
2015-11-30 15:31:54 +01:00
2015-06-17 19:03:23 +02:00
$this -> db -> insert (
$fileTable ,
array (
2015-12-18 17:32:17 +01:00
$fileKey => $this -> dbBin ( $checksum ),
'content' => $file -> getContent (),
'cnt_object' => $file -> getObjectCount (),
'cnt_template' => $file -> getTemplateCount ()
2015-06-17 19:03:23 +02:00
)
);
}
$this -> db -> insert (
self :: $table ,
array (
2015-07-23 15:38:17 +02:00
'duration' => $this -> generationTime ,
'last_activity_checksum' => $this -> dbBin ( $this -> getLastActivityChecksum ()),
'checksum' => $this -> dbBin ( $this -> getChecksum ()),
2015-06-17 19:03:23 +02:00
)
);
/** @var IcingaConfigFile $file */
foreach ( $this -> files as $name => $file ) {
$this -> db -> insert (
'director_generated_config_file' ,
array (
2015-06-23 16:03:56 +02:00
'config_checksum' => $this -> dbBin ( $this -> getChecksum ()),
'file_checksum' => $this -> dbBin ( $file -> getChecksum ()),
'file_path' => $name ,
2015-06-17 19:03:23 +02:00
)
);
}
$this -> db -> commit ();
2015-06-23 16:03:56 +02:00
} catch ( Exception $e ) {
2015-06-17 19:03:23 +02:00
$this -> db -> rollBack ();
2015-08-28 17:52:19 +02:00
throw $e ;
2015-06-17 19:03:23 +02:00
var_dump ( $e -> getMessage ());
}
return $this ;
2015-06-11 22:48:41 +02:00
}
2015-06-17 19:03:23 +02:00
protected function generateFromDb ()
2015-06-11 22:48:41 +02:00
{
2015-06-17 19:03:23 +02:00
$start = microtime ( true );
2015-12-12 23:17:03 +01:00
$this -> configFile ( 'conf.d/001-director-basics' ) -> prepend (
2015-11-06 09:38:28 +01:00
" \n const DirectorStageDir = dirname(dirname(current_filename)) \n "
);
2015-06-11 22:48:41 +02:00
$this
-> createFileFromDb ( 'zone' )
2015-06-17 10:53:41 +02:00
-> createFileFromDb ( 'endpoint' )
2015-06-11 22:48:41 +02:00
-> createFileFromDb ( 'command' )
2015-06-17 10:53:41 +02:00
-> createFileFromDb ( 'hostGroup' )
2015-06-11 22:48:41 +02:00
-> createFileFromDb ( 'host' )
2015-12-18 10:53:27 +01:00
-> autogenerateAgents ()
2015-06-17 10:53:41 +02:00
-> createFileFromDb ( 'serviceGroup' )
2015-06-11 22:48:41 +02:00
-> createFileFromDb ( 'service' )
2015-06-17 10:53:41 +02:00
-> createFileFromDb ( 'userGroup' )
2015-06-12 13:41:38 +02:00
-> createFileFromDb ( 'user' )
2015-06-11 22:48:41 +02:00
;
2015-06-12 13:38:49 +02:00
2015-06-23 16:07:34 +02:00
$this -> generationTime = ( int ) (( microtime ( true ) - $start ) * 1000 );
2015-06-17 19:03:23 +02:00
2015-06-11 22:48:41 +02:00
return $this ;
}
2015-06-18 10:54:44 +02:00
protected function loadFromDb ( $checksum )
{
$query = $this -> db -> select () -> from (
self :: $table ,
2015-12-16 16:19:58 +01:00
array ( 'checksum' , 'last_activity_checksum' , 'duration' )
2015-06-23 15:13:34 +02:00
) -> where ( 'checksum = ?' , $this -> dbBin ( $checksum ));
2015-06-18 10:54:44 +02:00
$result = $this -> db -> fetchRow ( $query );
2015-07-24 10:58:02 +02:00
if ( empty ( $result )) {
throw new Exception ( sprintf ( 'Got no config for %s' , Util :: binary2hex ( $checksum )));
}
2015-06-18 10:54:44 +02:00
$this -> checksum = $result -> checksum ;
2015-12-16 16:19:58 +01:00
$this -> duration = $result -> duration ;
2015-06-18 11:01:45 +02:00
$this -> lastActivityChecksum = $result -> last_activity_checksum ;
2015-06-23 16:10:57 +02:00
if ( is_resource ( $this -> checksum )) {
$this -> checksum = stream_get_contents ( $this -> checksum );
}
if ( is_resource ( $this -> lastActivityChecksum )) {
$this -> lastActivityChecksum = stream_get_contents ( $this -> lastActivityChecksum );
}
2015-06-18 10:54:44 +02:00
$query = $this -> db -> select () -> from (
array ( 'cf' => 'director_generated_config_file' ),
array (
2015-12-15 17:17:32 +01:00
'file_path' => 'cf.file_path' ,
'checksum' => 'f.checksum' ,
'content' => 'f.content' ,
'cnt_object' => 'f.cnt_object' ,
'cnt_template' => 'f.cnt_template' ,
2015-06-18 10:54:44 +02:00
)
) -> join (
array ( 'f' => 'director_generated_file' ),
'cf.file_checksum = f.checksum' ,
array ()
2015-06-23 15:13:34 +02:00
) -> where ( 'cf.config_checksum = ?' , $this -> dbBin ( $checksum ));
2015-06-18 10:54:44 +02:00
foreach ( $this -> db -> fetchAll ( $query ) as $row ) {
$file = new IcingaConfigFile ();
2015-12-15 17:17:32 +01:00
$this -> files [ $row -> file_path ] = $file
-> setContent ( $row -> content )
-> setObjectCount ( $row -> cnt_object )
-> setTemplateCount ( $row -> cnt_template );
2015-06-18 10:54:44 +02:00
}
return $this ;
}
2015-12-18 10:53:27 +01:00
protected function autogenerateAgents ()
{
$zones = array ();
$endpoints = array ();
foreach ( IcingaHost :: prefetchAll ( $this -> connection ) as $host ) {
2015-12-18 11:28:01 +01:00
if ( $host -> object_type !== 'object' ) continue ;
if ( $host -> getResolvedProperty ( 'has_agent' ) !== 'y' ) continue ;
2015-12-18 10:53:27 +01:00
$name = $host -> object_name ;
2015-12-18 11:37:11 +01:00
if ( IcingaEndpoint :: exists ( $name , $this -> connection )) continue ;
2015-12-18 10:53:27 +01:00
2015-12-18 11:28:01 +01:00
$props = array (
2016-01-19 16:30:42 +01:00
'object_name' => $name ,
'object_type' => 'object' ,
'log_duration' => 0
2015-12-18 11:28:01 +01:00
);
if ( $host -> getResolvedProperty ( 'master_should_connect' ) === 'y' ) {
$props [ 'host' ] = $host -> getResolvedProperty ( 'address' );
$props [ 'zone_id' ] = $host -> getResolvedProperty ( 'zone_id' );
2015-12-18 10:53:27 +01:00
}
2015-12-18 11:28:01 +01:00
$endpoints [] = IcingaEndpoint :: create ( $props );
$zones [] = IcingaZone :: create ( array (
2015-12-18 15:42:22 +01:00
'object_name' => $name ,
'parent' => $this -> getMasterZoneName ()
), $this -> connection ) -> setEndpointList ( array ( $name ));
2015-12-18 10:53:27 +01:00
}
$this -> createFileForObjects ( 'endpoint' , $endpoints );
$this -> createFileForObjects ( 'zone' , $zones );
return $this ;
}
2015-06-11 22:48:41 +02:00
protected function createFileFromDb ( $type )
{
$class = 'Icinga\\Module\\Director\\Objects\\Icinga' . ucfirst ( $type );
2016-01-19 16:45:20 +01:00
Benchmark :: measure ( sprintf ( 'Prefetching %s' , $type ));
2015-12-18 10:53:27 +01:00
$objects = $class :: prefetchAll ( $this -> connection );
return $this -> createFileForObjects ( $type , $objects );
}
protected function createFileForObjects ( $type , $objects )
{
2016-01-19 16:45:20 +01:00
Benchmark :: measure ( sprintf ( 'Generating %d %s' , count ( $objects ), $type ));
2015-11-30 15:31:54 +01:00
if ( empty ( $objects )) return $this ;
2015-12-17 20:33:16 +01:00
$masterZone = $this -> getMasterZoneName ();
$globalZone = $this -> getGlobalZoneName ();
2015-08-28 17:52:19 +02:00
$file = null ;
2015-06-11 22:48:41 +02:00
2015-07-28 15:24:05 +02:00
foreach ( $objects as $object ) {
2015-12-01 14:47:10 +01:00
if ( $object -> isExternal ()) {
2015-12-02 16:10:20 +01:00
if ( $type === 'zone' ) {
$this -> zoneMap [ $object -> id ] = $object -> object_name ;
}
2015-12-01 14:47:10 +01:00
continue ;
} elseif ( $object -> isTemplate ()) {
2015-07-28 15:24:05 +02:00
$filename = strtolower ( $type ) . '_templates' ;
} else {
$filename = strtolower ( $type ) . 's' ;
}
2015-12-17 20:33:16 +01:00
// Zones get special handling
2015-07-28 15:24:05 +02:00
if ( $type === 'zone' ) {
$this -> zoneMap [ $object -> id ] = $object -> object_name ;
2015-12-17 20:33:16 +01:00
// If the zone has a parent zone...
if ( $object -> parent_id ) {
// ...we render the zone object to the parent zone
$zone = $object -> parent ;
} elseif ( $object -> is_global === 'y' ) {
// ...additional global zones are rendered to our global zone...
$zone = $globalZone ;
} else {
// ...and all the other zones are rendered to our master zone
$zone = $masterZone ;
}
// Zone definitions for all other objects are respected...
2015-07-28 15:24:05 +02:00
} elseif ( $object -> hasProperty ( 'zone_id' ) && ( $zone_id = $object -> zone_id )) {
$zone = $this -> getZoneName ( $zone_id );
2015-12-17 20:33:16 +01:00
// ...and if there is no zone defined, special rules take place
2015-07-28 15:24:05 +02:00
} else {
2015-12-17 20:33:16 +01:00
if ( $this -> typeWantsMasterZone ( $type )) {
$zone = $masterZone ;
} elseif ( $this -> typeWantsGlobalZone ( $type )) {
$zone = $globalZone ;
} else {
throw new ProgrammingError (
'I have no idea of how to deploy a "%s" object' ,
$type
);
}
2015-07-28 15:24:05 +02:00
}
2015-12-17 20:33:16 +01:00
$filename = 'zones.d/' . $zone . '/' . $filename ;
2015-08-28 17:52:19 +02:00
$file = $this -> configFile ( $filename );
2015-07-28 15:24:05 +02:00
$file -> addObject ( $object );
2015-06-11 22:48:41 +02:00
}
2015-08-28 17:52:19 +02:00
if ( $file && $type === 'command' ) {
$file -> prepend ( " library \" methods \" \n \n " );
}
2015-06-11 22:48:41 +02:00
return $this ;
}
2015-06-17 19:03:23 +02:00
2015-12-17 20:33:16 +01:00
protected function typeWantsGlobalZone ( $type )
{
$types = array (
'command' ,
);
return in_array ( $type , $types );
}
protected function typeWantsMasterZone ( $type )
{
$types = array (
'host' ,
'hostGroup' ,
'service' ,
'serviceGroup' ,
'endpoint' ,
'user' ,
'userGroup' ,
'timeperiod' ,
'notification'
);
return in_array ( $type , $types );
}
protected function getMasterZoneName ()
{
return 'master' ;
}
protected function getGlobalZoneName ()
{
return 'director-global' ;
}
2015-10-15 20:02:44 +02:00
protected function configFile ( $name , $suffix = '.conf' )
2015-07-28 15:22:20 +02:00
{
2015-10-15 20:02:44 +02:00
$filename = $name . $suffix ;
2015-07-28 15:22:20 +02:00
if ( ! array_key_exists ( $filename , $this -> files )) {
$this -> files [ $filename ] = new IcingaConfigFile ();
}
return $this -> files [ $filename ];
}
2015-10-15 20:35:21 +02:00
protected function collectExtraFiles ()
{
foreach ( Hook :: all ( 'Director\\ShipConfigFiles' ) as $hook ) {
foreach ( $hook -> fetchFiles () as $filename => $file ) {
if ( array_key_exists ( $filename , $this -> files )) {
throw new ProgrammingError (
'Cannot ship one file twice: %s' ,
$filename
);
}
if ( $file instanceof IcingaConfigFile ) {
$this -> files [ $filename ] = $file ;
} else {
$this -> configFile ( $filename , '' ) -> setContent (( string ) $file );
}
}
}
return $this ;
}
2015-06-17 19:03:23 +02:00
public function getLastActivityHexChecksum ()
{
2015-06-23 14:37:23 +02:00
return Util :: binary2hex ( $this -> getLastActivityChecksum ());
2015-06-17 19:03:23 +02:00
}
/**
* @ 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 );
2015-06-23 14:12:39 +02:00
// PgSQL workaround:
if ( is_resource ( $this -> lastActivityChecksum )) {
$this -> lastActivityChecksum = stream_get_contents ( $this -> lastActivityChecksum );
}
2015-06-17 19:03:23 +02:00
}
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;
2015-06-11 22:48:41 +02:00
}