2015-06-11 22:48:41 +02:00
< ? php
namespace Icinga\Module\Director\IcingaConfig ;
use Icinga\Data\Db\DbConnection ;
2015-10-15 20:35:21 +02:00
use Icinga\Exception\ProgrammingError ;
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\IcingaCommand ;
use Icinga\Module\Director\Objects\IcingaHost ;
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' ;
protected function __construct ( DbConnection $connection )
{
$this -> connection = $connection ;
$this -> db = $connection -> getDbAdapter ();
}
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 );
}
public function getMissingFiles ( $missing )
{
$files = array ();
foreach ( $this -> files as $name => $file ) {
$files [] = $name . '=' . $file -> getChecksum ();
}
return $files ;
}
2015-06-18 10:54:44 +02:00
public static function fromDb ( $checksum , DbConnection $connection )
{
$config = new static ( $connection );
$config -> loadFromDb ( $checksum );
return $config ;
}
2015-06-17 19:03:23 +02:00
public static function generate ( DbConnection $connection )
{
$config = new static ( $connection );
return $config -> storeIfModified ();
}
2015-11-06 09:47:36 +01:00
public static function wouldChange ( DbConnection $connection )
{
$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-06-23 16:05:34 +02:00
$fileKey => $this -> dbBin ( $checksum ),
2015-06-17 19:03:23 +02:00
'content' => $file -> getContent ()
)
);
}
$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-11-06 09:38:28 +01:00
$this -> configFile ( 'conf.d/001-director-basics.conf' ) -> prepend (
" \n const DirectorStageDir = dirname(dirname(current_filename)) \n "
2015-11-30 15:31:54 +01:00
. " \n object Zone \" director-global \" { \n global = true \n } \n "
2015-11-06 09:38:28 +01:00
);
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-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 ,
array ( 'checksum' , 'last_activity_checksum' )
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-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 (
'file_path' => 'cf.file_path' ,
'checksum' => 'f.checksum' ,
'content' => 'f.content' ,
)
) -> 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 ();
$this -> files [ $row -> file_path ] = $file -> setContent ( $row -> content );
}
return $this ;
}
2015-06-11 22:48:41 +02:00
protected function createFileFromDb ( $type )
{
$class = 'Icinga\\Module\\Director\\Objects\\Icinga' . ucfirst ( $type );
2015-06-17 19:03:23 +02:00
$objects = $class :: loadAll ( $this -> connection );
2015-11-30 15:31:54 +01:00
if ( empty ( $objects )) return $this ;
2015-11-14 14:49:45 +01:00
$ourGlobalZone = 'director-global' ;
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' ;
$zone = 'master' ;
} else {
$filename = strtolower ( $type ) . 's' ;
$zone = 'global' ;
}
if ( $type === 'zone' ) {
$this -> zoneMap [ $object -> id ] = $object -> object_name ;
} elseif ( $object -> hasProperty ( 'zone_id' ) && ( $zone_id = $object -> zone_id )) {
$zone = $this -> getZoneName ( $zone_id );
} else {
$zone = 'master' ;
}
2015-11-14 14:49:45 +01:00
if ( in_array ( $type , array ( 'command' , 'zone' ))) {
$filename = 'zones.d/' . $ourGlobalZone . '/' . $filename ;
2015-08-28 17:52:19 +02:00
} elseif ( $type === 'endpoint' ) {
$filename = 'conf.d/endpoints' ;
continue ;
} elseif ( $zone === 'master' ) {
$filename = 'conf.d/' . $filename ;
} else {
$filename = 'zones.d/' . $zone . '/' . $filename ;
2015-06-17 10:03:31 +02:00
}
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-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
}