2015-07-24 10:51:55 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Icinga\Module\Director\Import;
|
|
|
|
|
2016-06-28 12:56:53 +02:00
|
|
|
use Exception;
|
2016-03-15 17:28:49 +01:00
|
|
|
use Icinga\Data\Filter\Filter;
|
2017-08-21 21:53:19 +02:00
|
|
|
use Icinga\Module\Director\Data\Db\DbObject;
|
2016-10-27 15:43:26 +02:00
|
|
|
use Icinga\Module\Director\Db;
|
2016-10-31 17:20:17 +01:00
|
|
|
use Icinga\Module\Director\Db\Cache\PrefetchCache;
|
2017-03-14 14:21:15 +01:00
|
|
|
use Icinga\Module\Director\Objects\HostGroupMembershipResolver;
|
|
|
|
use Icinga\Module\Director\Objects\IcingaHost;
|
|
|
|
use Icinga\Module\Director\Objects\IcingaHostGroup;
|
2015-08-04 19:52:02 +02:00
|
|
|
use Icinga\Module\Director\Objects\IcingaObject;
|
2015-07-24 10:51:55 +02:00
|
|
|
use Icinga\Module\Director\Objects\ImportSource;
|
2016-02-24 15:33:08 +01:00
|
|
|
use Icinga\Module\Director\Objects\IcingaService;
|
2015-07-24 10:51:55 +02:00
|
|
|
use Icinga\Module\Director\Objects\SyncRule;
|
2016-02-24 12:24:19 +01:00
|
|
|
use Icinga\Module\Director\Objects\SyncRun;
|
|
|
|
use Icinga\Module\Director\Util;
|
2015-10-20 22:21:48 +02:00
|
|
|
use Icinga\Exception\IcingaException;
|
2015-07-24 10:51:55 +02:00
|
|
|
|
|
|
|
class Sync
|
|
|
|
{
|
2016-02-23 17:35:47 +01:00
|
|
|
/**
|
|
|
|
* @var SyncRule
|
|
|
|
*/
|
2016-02-23 11:10:37 +01:00
|
|
|
protected $rule;
|
|
|
|
|
2016-10-27 15:43:26 +02:00
|
|
|
/**
|
|
|
|
* @var Db
|
|
|
|
*/
|
|
|
|
protected $db;
|
|
|
|
|
2016-02-23 17:35:47 +01:00
|
|
|
/**
|
|
|
|
* Related ImportSource objects
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
2016-02-23 17:03:52 +01:00
|
|
|
protected $sources;
|
|
|
|
|
2016-02-23 21:05:09 +01:00
|
|
|
/**
|
|
|
|
* Source columns we want to fetch from our sources
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $sourceColumns;
|
|
|
|
|
2016-02-23 17:35:47 +01:00
|
|
|
/**
|
|
|
|
* Imported data
|
|
|
|
*/
|
|
|
|
protected $imported;
|
|
|
|
|
2016-02-23 17:47:18 +01:00
|
|
|
/**
|
|
|
|
* Objects to work with
|
|
|
|
*
|
2017-08-03 10:51:13 +02:00
|
|
|
* @var IcingaObject[]
|
2016-02-23 17:47:18 +01:00
|
|
|
*/
|
|
|
|
protected $objects;
|
|
|
|
|
2016-02-24 12:24:19 +01:00
|
|
|
/**
|
|
|
|
* Whether we already prepared your sync
|
|
|
|
*
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
protected $isPrepared = false;
|
|
|
|
|
2015-12-23 15:10:37 +01:00
|
|
|
protected $modify = array();
|
|
|
|
|
|
|
|
protected $remove = array();
|
|
|
|
|
|
|
|
protected $create = array();
|
|
|
|
|
|
|
|
protected $errors = array();
|
|
|
|
|
2016-02-23 13:03:47 +01:00
|
|
|
protected $syncProperties;
|
|
|
|
|
2017-08-03 10:51:13 +02:00
|
|
|
protected $replaceVars = false;
|
|
|
|
|
2016-10-27 15:43:26 +02:00
|
|
|
/**
|
|
|
|
* @var SyncRun
|
|
|
|
*/
|
2016-02-24 12:24:19 +01:00
|
|
|
protected $run;
|
|
|
|
|
|
|
|
protected $runStartTime;
|
|
|
|
|
2017-03-28 18:03:53 +02:00
|
|
|
/** @var Filter[] */
|
2016-03-15 17:28:49 +01:00
|
|
|
protected $columnFilters = array();
|
|
|
|
|
2017-03-14 14:21:15 +01:00
|
|
|
/** @var HostGroupMembershipResolver|bool */
|
2017-03-14 16:08:37 +01:00
|
|
|
protected $hostGroupMembershipResolver;
|
2017-03-14 14:21:15 +01:00
|
|
|
|
2015-12-08 11:51:05 +01:00
|
|
|
/**
|
|
|
|
* Constructor. No direct initialization allowed right now. Please use one
|
|
|
|
* of the available static factory methods
|
2016-10-27 15:43:26 +02:00
|
|
|
*
|
|
|
|
* @param SyncRule $rule
|
2015-12-08 11:51:05 +01:00
|
|
|
*/
|
2016-02-24 10:50:57 +01:00
|
|
|
public function __construct(SyncRule $rule)
|
2015-07-24 10:51:55 +02:00
|
|
|
{
|
2016-02-23 11:10:37 +01:00
|
|
|
$this->rule = $rule;
|
2016-02-23 11:22:25 +01:00
|
|
|
$this->db = $rule->getConnection();
|
2015-07-24 10:51:55 +02:00
|
|
|
}
|
|
|
|
|
2015-12-08 11:51:05 +01:00
|
|
|
/**
|
|
|
|
* Whether the given sync rule would apply modifications
|
|
|
|
*
|
|
|
|
* @return boolean
|
|
|
|
*/
|
2016-02-24 10:50:57 +01:00
|
|
|
public function hasModifications()
|
2015-10-30 23:34:27 +01:00
|
|
|
{
|
2016-02-24 10:50:57 +01:00
|
|
|
return count($this->getExpectedModifications()) > 0;
|
2015-10-30 23:34:27 +01:00
|
|
|
}
|
|
|
|
|
2015-12-08 11:51:05 +01:00
|
|
|
/**
|
|
|
|
* Retrieve modifications a given SyncRule would apply
|
|
|
|
*
|
|
|
|
* @return array Array of IcingaObject elements
|
|
|
|
*/
|
2016-02-24 10:50:57 +01:00
|
|
|
public function getExpectedModifications()
|
2015-10-30 23:34:27 +01:00
|
|
|
{
|
|
|
|
$modified = array();
|
2016-02-24 10:50:57 +01:00
|
|
|
$objects = $this->prepare();
|
2015-10-30 23:34:27 +01:00
|
|
|
foreach ($objects as $object) {
|
|
|
|
if ($object->hasBeenModified()) {
|
|
|
|
$modified[] = $object;
|
2016-10-05 19:45:25 +02:00
|
|
|
} elseif ($object->shouldBeRemoved()) {
|
2016-04-22 14:47:49 +02:00
|
|
|
$modified[] = $object;
|
2015-10-30 23:34:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $modified;
|
|
|
|
}
|
|
|
|
|
2015-12-08 11:51:05 +01:00
|
|
|
/**
|
|
|
|
* Transform the given value to an array
|
|
|
|
*
|
|
|
|
* @param array|string|null $value
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
2015-08-28 23:56:54 +02:00
|
|
|
protected function wantArray($value)
|
|
|
|
{
|
|
|
|
if (is_array($value)) {
|
|
|
|
return $value;
|
|
|
|
} elseif ($value === null) {
|
|
|
|
return array();
|
|
|
|
} else {
|
|
|
|
return array($value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-24 10:50:57 +01:00
|
|
|
/**
|
|
|
|
* Raise PHP resource limits
|
|
|
|
*
|
|
|
|
* TODO: do this in a failsafe way, and only if necessary
|
|
|
|
*
|
|
|
|
* @return self;
|
|
|
|
*/
|
|
|
|
protected function raiseLimits()
|
|
|
|
{
|
2017-08-04 11:31:43 +02:00
|
|
|
if ((string) ini_get('memory_limit') !== '-1') {
|
|
|
|
ini_set('memory_limit', '1024M');
|
|
|
|
}
|
2016-02-24 10:50:57 +01:00
|
|
|
ini_set('max_execution_time', 0);
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2016-02-24 12:24:19 +01:00
|
|
|
/**
|
|
|
|
* Initialize run summary measurements
|
|
|
|
*
|
|
|
|
* @return self;
|
|
|
|
*/
|
|
|
|
protected function startMeasurements()
|
|
|
|
{
|
|
|
|
$this->run = SyncRun::start($this->rule);
|
|
|
|
$this->runStartTime = microtime(true);
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fetch the configured properties involved in this sync
|
|
|
|
*
|
|
|
|
* @return self
|
|
|
|
*/
|
|
|
|
protected function fetchSyncProperties()
|
|
|
|
{
|
|
|
|
$this->syncProperties = $this->rule->fetchSyncProperties();
|
2016-03-15 17:28:49 +01:00
|
|
|
foreach ($this->syncProperties as $key => $prop) {
|
2017-08-03 10:51:13 +02:00
|
|
|
if ($prop->destination_field === 'vars' && $prop->merge_policy === 'override') {
|
|
|
|
$this->replaceVars = true;
|
|
|
|
}
|
|
|
|
|
2016-03-15 17:28:49 +01:00
|
|
|
if (! strlen($prop->filter_expression)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->columnFilters[$key] = Filter::fromQueryString(
|
|
|
|
$prop->filter_expression
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-02-24 12:24:19 +01:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2016-03-15 17:28:49 +01:00
|
|
|
protected function rowMatchesPropertyFilter($row, $key)
|
|
|
|
{
|
|
|
|
if (!array_key_exists($key, $this->columnFilters)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->columnFilters[$key]->matches($row);
|
|
|
|
}
|
|
|
|
|
2016-02-23 17:03:52 +01:00
|
|
|
/**
|
|
|
|
* Instantiates all related ImportSource objects
|
|
|
|
*
|
2016-02-23 17:35:47 +01:00
|
|
|
* @return self
|
2016-02-23 17:03:52 +01:00
|
|
|
*/
|
|
|
|
protected function prepareRelatedImportSources()
|
2015-07-24 10:51:55 +02:00
|
|
|
{
|
2016-02-23 17:03:52 +01:00
|
|
|
$this->sources = array();
|
2016-02-23 13:03:47 +01:00
|
|
|
foreach ($this->syncProperties as $p) {
|
2016-02-23 17:03:52 +01:00
|
|
|
$id = $p->source_id;
|
2016-02-23 17:47:18 +01:00
|
|
|
if (! array_key_exists($id, $this->sources)) {
|
2016-02-23 17:03:52 +01:00
|
|
|
$this->sources[$id] = ImportSource::load($id, $this->db);
|
2015-12-08 16:40:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-23 17:35:47 +01:00
|
|
|
return $this;
|
2015-12-08 16:40:47 +01:00
|
|
|
}
|
2015-12-08 11:51:05 +01:00
|
|
|
|
2016-02-23 21:05:09 +01:00
|
|
|
/**
|
|
|
|
* Prepare the source columns we want to fetch
|
|
|
|
*
|
|
|
|
* @return self
|
|
|
|
*/
|
2016-02-23 13:03:47 +01:00
|
|
|
protected function prepareSourceColumns()
|
2015-12-08 16:40:47 +01:00
|
|
|
{
|
2015-07-24 10:51:55 +02:00
|
|
|
// $fieldMap = array();
|
2016-02-23 21:05:09 +01:00
|
|
|
$this->sourceColumns = array();
|
2015-07-24 10:51:55 +02:00
|
|
|
|
2016-02-23 13:03:47 +01:00
|
|
|
foreach ($this->syncProperties as $p) {
|
2015-07-24 10:51:55 +02:00
|
|
|
$sourceId = $p->source_id;
|
2016-02-23 21:05:09 +01:00
|
|
|
if (! array_key_exists($sourceId, $this->sourceColumns)) {
|
|
|
|
$this->sourceColumns[$sourceId] = array();
|
2015-07-24 10:51:55 +02:00
|
|
|
}
|
|
|
|
|
2016-07-13 13:52:15 +02:00
|
|
|
foreach (SyncUtils::extractVariableNames($p->source_expression) as $varname) {
|
2016-02-23 21:05:09 +01:00
|
|
|
$this->sourceColumns[$sourceId][$varname] = $varname;
|
2015-07-24 10:51:55 +02:00
|
|
|
// -> ? $fieldMap[
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-23 21:05:09 +01:00
|
|
|
return $this;
|
2015-12-08 16:40:47 +01:00
|
|
|
}
|
|
|
|
|
2016-02-23 17:35:47 +01:00
|
|
|
/**
|
|
|
|
* Fetch latest imported data rows from all involved import sources
|
2017-08-03 10:51:13 +02:00
|
|
|
* @return Sync
|
|
|
|
* @throws IcingaException
|
2016-02-23 17:35:47 +01:00
|
|
|
*/
|
|
|
|
protected function fetchImportedData()
|
2016-02-23 11:10:37 +01:00
|
|
|
{
|
2016-02-23 17:35:47 +01:00
|
|
|
$this->imported = array();
|
2016-02-23 11:10:37 +01:00
|
|
|
|
2016-07-13 19:59:04 +02:00
|
|
|
$sourceKeyPattern = $this->rule->getSourceKeyPattern();
|
|
|
|
$combinedKey = $this->rule->hasCombinedKey();
|
|
|
|
|
2016-02-23 17:35:47 +01:00
|
|
|
foreach ($this->sources as $source) {
|
2016-10-27 15:43:26 +02:00
|
|
|
/** @var ImportSource $source */
|
2015-07-24 10:51:55 +02:00
|
|
|
$sourceId = $source->id;
|
2016-02-23 21:05:09 +01:00
|
|
|
|
|
|
|
// Provide an alias column for our key. TODO: double-check this!
|
2015-07-24 10:51:55 +02:00
|
|
|
$key = $source->key_column;
|
2016-02-23 21:05:09 +01:00
|
|
|
$this->sourceColumns[$sourceId][$key] = $key;
|
2016-07-13 10:19:03 +02:00
|
|
|
$run = $source->fetchLastRun(true);
|
2017-03-28 18:03:53 +02:00
|
|
|
|
|
|
|
$usedColumns = SyncUtils::getRootVariables($this->sourceColumns[$sourceId]);
|
|
|
|
|
|
|
|
$filterColumns = array();
|
|
|
|
foreach ($this->columnFilters as $filter) {
|
|
|
|
foreach ($filter->listFilteredColumns() as $column) {
|
|
|
|
$filterColumns[$column] = $column;
|
|
|
|
}
|
|
|
|
}
|
2017-04-26 15:31:24 +02:00
|
|
|
if (! empty($filterColumns)) {
|
|
|
|
foreach (SyncUtils::getRootVariables($filterColumns) as $column) {
|
|
|
|
$usedColumns[$column] = $column;
|
|
|
|
}
|
2017-03-28 18:03:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$rows = $run->fetchRows($usedColumns);
|
2015-07-26 15:46:25 +02:00
|
|
|
|
2016-02-23 17:35:47 +01:00
|
|
|
$this->imported[$sourceId] = array();
|
2015-07-24 10:51:55 +02:00
|
|
|
foreach ($rows as $row) {
|
2016-07-13 19:59:04 +02:00
|
|
|
if ($combinedKey) {
|
|
|
|
$key = SyncUtils::fillVariables($sourceKeyPattern, $row);
|
2016-07-13 13:52:15 +02:00
|
|
|
|
2016-02-23 17:35:47 +01:00
|
|
|
if (array_key_exists($key, $this->imported[$sourceId])) {
|
2016-02-23 08:58:22 +01:00
|
|
|
throw new IcingaException(
|
|
|
|
'Trying to import row "%s" (%s) twice: %s VS %s',
|
|
|
|
$key,
|
2016-07-13 19:59:04 +02:00
|
|
|
$sourceKeyPattern,
|
2016-02-23 17:35:47 +01:00
|
|
|
json_encode($this->imported[$sourceId][$key]),
|
2016-02-23 08:58:22 +01:00
|
|
|
json_encode($row)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (! property_exists($row, $key)) {
|
|
|
|
throw new IcingaException(
|
|
|
|
'There is no key column "%s" in this row from "%s": %s',
|
|
|
|
$key,
|
|
|
|
$source->source_name,
|
|
|
|
json_encode($row)
|
|
|
|
);
|
|
|
|
}
|
2015-07-24 15:27:37 +02:00
|
|
|
}
|
2016-02-23 08:58:22 +01:00
|
|
|
|
2016-02-23 21:05:09 +01:00
|
|
|
if (! $this->rule->matches($row)) {
|
2015-12-04 10:59:25 +01:00
|
|
|
continue;
|
|
|
|
}
|
2016-02-23 08:58:22 +01:00
|
|
|
|
2016-07-13 19:59:04 +02:00
|
|
|
if ($combinedKey) {
|
2016-02-23 17:35:47 +01:00
|
|
|
$this->imported[$sourceId][$key] = $row;
|
2016-02-23 08:58:22 +01:00
|
|
|
} else {
|
2016-02-23 17:35:47 +01:00
|
|
|
$this->imported[$sourceId][$row->$key] = $row;
|
2016-02-23 08:58:22 +01:00
|
|
|
}
|
2015-07-24 10:51:55 +02:00
|
|
|
}
|
2016-07-13 10:19:03 +02:00
|
|
|
|
|
|
|
unset($rows);
|
2015-07-24 10:51:55 +02:00
|
|
|
}
|
|
|
|
|
2016-02-23 17:35:47 +01:00
|
|
|
return $this;
|
2015-12-08 16:40:47 +01:00
|
|
|
}
|
|
|
|
|
2016-02-19 12:42:02 +01:00
|
|
|
// TODO: This is rubbish, we need to filter at fetch time
|
2016-02-23 17:47:18 +01:00
|
|
|
protected function removeForeignListEntries()
|
2016-02-19 12:42:02 +01:00
|
|
|
{
|
|
|
|
$listId = null;
|
2016-02-23 13:03:47 +01:00
|
|
|
foreach ($this->syncProperties as $prop) {
|
2016-02-19 12:42:02 +01:00
|
|
|
if ($prop->destination_field === 'list_id') {
|
|
|
|
$listId = (int) $prop->source_expression;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($listId === null) {
|
|
|
|
throw new IcingaException(
|
|
|
|
'Cannot sync datalist entry without list_ist'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$no = array();
|
2016-02-23 17:47:18 +01:00
|
|
|
foreach ($this->objects as $k => $o) {
|
2016-10-05 19:45:25 +02:00
|
|
|
if ((int) $o->list_id !== (int) $listId) {
|
2016-02-19 12:42:02 +01:00
|
|
|
$no[] = $k;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($no as $k) {
|
2016-02-23 17:47:18 +01:00
|
|
|
unset($this->objects[$k]);
|
2016-02-19 12:42:02 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-23 17:47:18 +01:00
|
|
|
protected function loadExistingObjects()
|
|
|
|
{
|
|
|
|
// TODO: Make object_type (template, object...) and object_name mandatory?
|
2016-07-13 19:59:04 +02:00
|
|
|
if ($this->rule->hasCombinedKey()) {
|
2016-02-25 00:10:40 +01:00
|
|
|
$this->objects = array();
|
2016-07-13 19:59:04 +02:00
|
|
|
$destinationKeyPattern = $this->rule->getDestinationKeyPattern();
|
2016-02-25 00:10:40 +01:00
|
|
|
|
2016-02-24 15:33:08 +01:00
|
|
|
foreach (IcingaObject::loadAllByType(
|
|
|
|
$this->rule->object_type,
|
|
|
|
$this->db
|
|
|
|
) as $object) {
|
|
|
|
if ($object instanceof IcingaService) {
|
2016-11-10 14:05:18 +01:00
|
|
|
if (strstr($destinationKeyPattern, '${host}') && $object->host_id === null) {
|
|
|
|
continue;
|
2017-01-13 19:47:54 +01:00
|
|
|
} elseif (strstr($destinationKeyPattern, '${service_set}') && $object->service_set_id === null) {
|
2016-02-26 11:58:37 +01:00
|
|
|
continue;
|
|
|
|
}
|
2016-02-24 15:33:08 +01:00
|
|
|
}
|
|
|
|
|
2016-07-13 13:52:15 +02:00
|
|
|
$key = SyncUtils::fillVariables(
|
2016-07-13 19:59:04 +02:00
|
|
|
$destinationKeyPattern,
|
2016-02-24 15:33:08 +01:00
|
|
|
$object
|
|
|
|
);
|
|
|
|
|
|
|
|
if (array_key_exists($key, $this->objects)) {
|
|
|
|
throw new IcingaException(
|
|
|
|
'Combined destination key "%s" is not unique, got "%s" twice',
|
2016-07-13 19:59:04 +02:00
|
|
|
$destinationKeyPattern,
|
2016-02-24 15:33:08 +01:00
|
|
|
$key
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->objects[$key] = $object;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$this->objects = IcingaObject::loadAllByType(
|
|
|
|
$this->rule->object_type,
|
|
|
|
$this->db
|
|
|
|
);
|
|
|
|
}
|
2016-02-23 17:47:18 +01:00
|
|
|
|
|
|
|
// TODO: should be obsoleted by a better "loadFiltered" method
|
|
|
|
if ($this->rule->object_type === 'datalistEntry') {
|
2017-08-03 10:51:13 +02:00
|
|
|
$this->removeForeignListEntries();
|
2016-02-23 17:47:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2016-02-23 17:35:47 +01:00
|
|
|
protected function prepareNewObjects()
|
2015-12-08 16:40:47 +01:00
|
|
|
{
|
2016-02-19 15:23:44 +01:00
|
|
|
$newObjects = array();
|
2015-07-24 10:51:55 +02:00
|
|
|
|
2016-02-23 17:35:47 +01:00
|
|
|
foreach ($this->sources as $source) {
|
2015-07-26 15:46:25 +02:00
|
|
|
$sourceId = $source->id;
|
2015-07-24 10:51:55 +02:00
|
|
|
|
2016-02-23 17:35:47 +01:00
|
|
|
foreach ($this->imported[$sourceId] as $key => $row) {
|
2015-10-30 23:34:27 +01:00
|
|
|
$newProps = array();
|
2015-08-04 19:52:02 +02:00
|
|
|
|
2015-07-26 15:46:25 +02:00
|
|
|
$newVars = array();
|
2015-08-03 13:39:55 +02:00
|
|
|
$imports = array();
|
2015-07-24 10:51:55 +02:00
|
|
|
|
2016-03-15 17:28:49 +01:00
|
|
|
foreach ($this->syncProperties as $propertyKey => $p) {
|
2016-02-26 11:58:37 +01:00
|
|
|
if ($p->source_id !== $sourceId) {
|
|
|
|
continue;
|
|
|
|
}
|
2015-07-24 10:51:55 +02:00
|
|
|
|
2016-03-15 17:28:49 +01:00
|
|
|
if (! $this->rowMatchesPropertyFilter($row, $propertyKey)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-07-26 15:46:25 +02:00
|
|
|
$prop = $p->destination_field;
|
2016-03-15 17:28:49 +01:00
|
|
|
|
2016-07-13 13:52:15 +02:00
|
|
|
$val = SyncUtils::fillVariables($p->source_expression, $row);
|
2015-07-26 15:46:25 +02:00
|
|
|
|
|
|
|
if (substr($prop, 0, 5) === 'vars.') {
|
2015-08-28 23:56:54 +02:00
|
|
|
$varName = substr($prop, 5);
|
|
|
|
if (substr($varName, -2) === '[]') {
|
|
|
|
$varName = substr($varName, 0, -2);
|
|
|
|
$val = $this->wantArray($val);
|
|
|
|
}
|
|
|
|
$newVars[$varName] = $val;
|
2015-07-26 15:46:25 +02:00
|
|
|
} else {
|
2015-08-03 13:39:55 +02:00
|
|
|
if ($prop === 'import') {
|
2017-05-22 14:34:58 +02:00
|
|
|
if (is_array($val)) {
|
|
|
|
$imports = array_merge($imports, $val);
|
|
|
|
} elseif (!is_null($val)) {
|
|
|
|
$imports[] = $val;
|
|
|
|
}
|
2015-08-03 13:39:55 +02:00
|
|
|
} else {
|
|
|
|
$newProps[$prop] = $val;
|
|
|
|
}
|
2015-07-26 15:46:25 +02:00
|
|
|
}
|
2015-07-24 10:51:55 +02:00
|
|
|
}
|
2016-02-19 15:23:44 +01:00
|
|
|
if (! array_key_exists($key, $newObjects)) {
|
|
|
|
$newObjects[$key] = IcingaObject::createByType(
|
2016-02-23 11:55:16 +01:00
|
|
|
$this->rule->object_type,
|
2016-02-19 15:23:44 +01:00
|
|
|
array(),
|
2016-02-23 11:55:16 +01:00
|
|
|
$this->db
|
2015-12-10 12:57:11 +01:00
|
|
|
);
|
2016-02-19 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$object = $newObjects[$key];
|
2015-12-10 12:57:11 +01:00
|
|
|
|
2016-02-19 15:23:44 +01:00
|
|
|
// Safe default values for object_type and object_name
|
2016-02-23 11:55:16 +01:00
|
|
|
if ($this->rule->object_type !== 'datalistEntry') {
|
2016-02-19 15:23:44 +01:00
|
|
|
if (! array_key_exists('object_type', $newProps)
|
|
|
|
|| $newProps['object_type'] === null
|
|
|
|
) {
|
|
|
|
$newProps['object_type'] = 'object';
|
2015-07-26 15:46:25 +02:00
|
|
|
}
|
2015-08-03 15:12:46 +02:00
|
|
|
|
2016-02-19 15:23:44 +01:00
|
|
|
if (! array_key_exists('object_name', $newProps)
|
|
|
|
|| $newProps['object_name'] === null
|
|
|
|
) {
|
|
|
|
$newProps['object_name'] = $key;
|
2015-08-03 15:12:46 +02:00
|
|
|
}
|
2015-07-24 10:51:55 +02:00
|
|
|
}
|
2016-02-19 15:23:44 +01:00
|
|
|
|
|
|
|
foreach ($newProps as $prop => $value) {
|
2017-01-13 19:47:54 +01:00
|
|
|
// TODO: data type?
|
2016-02-19 15:23:44 +01:00
|
|
|
$object->set($prop, $value);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($newVars as $prop => $var) {
|
|
|
|
$object->vars()->$prop = $var;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! empty($imports)) {
|
|
|
|
// TODO: merge imports!!!
|
|
|
|
$object->imports()->set($imports);
|
|
|
|
}
|
2015-07-24 10:51:55 +02:00
|
|
|
}
|
2015-07-26 15:46:25 +02:00
|
|
|
}
|
|
|
|
|
2016-02-19 15:23:44 +01:00
|
|
|
return $newObjects;
|
|
|
|
}
|
2015-07-24 15:30:09 +02:00
|
|
|
|
2017-03-14 14:21:15 +01:00
|
|
|
protected function deferResolvers()
|
|
|
|
{
|
|
|
|
if (in_array($this->rule->get('object_type'), array('host', 'hostgroup'))) {
|
|
|
|
$resolver = new HostGroupMembershipResolver($this->db);
|
|
|
|
$resolver->defer()->setUseTransactions(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2017-08-03 10:51:13 +02:00
|
|
|
/**
|
2017-08-21 21:53:19 +02:00
|
|
|
* @param DbObject $object
|
2017-08-03 10:51:13 +02:00
|
|
|
* @return $this
|
|
|
|
*/
|
2017-03-14 16:08:37 +01:00
|
|
|
protected function setResolver($object)
|
2017-03-14 14:21:15 +01:00
|
|
|
{
|
2017-08-03 10:51:13 +02:00
|
|
|
if (! ($object instanceof IcingaHost || $object instanceof IcingaHostGroup)) {
|
2017-03-14 16:08:37 +01:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
if ($resolver = $this->gethostGroupMembershipResolver()) {
|
2017-03-14 14:21:15 +01:00
|
|
|
$object->setHostGroupMembershipResolver($resolver);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function notifyResolvers()
|
|
|
|
{
|
2017-03-14 16:08:37 +01:00
|
|
|
if ($resolver = $this->gethostGroupMembershipResolver()) {
|
2017-03-14 14:21:15 +01:00
|
|
|
$resolver->defer();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2017-08-03 10:51:13 +02:00
|
|
|
/**
|
|
|
|
* @return bool|HostGroupMembershipResolver
|
|
|
|
*/
|
2017-03-14 16:08:37 +01:00
|
|
|
protected function gethostGroupMembershipResolver()
|
2017-03-14 14:21:15 +01:00
|
|
|
{
|
2017-03-14 16:08:37 +01:00
|
|
|
if ($this->hostGroupMembershipResolver === null) {
|
2017-03-14 14:21:15 +01:00
|
|
|
if (in_array(
|
|
|
|
$this->rule->get('object_type'),
|
|
|
|
array('host', 'hostgroup')
|
|
|
|
)) {
|
2017-03-14 16:08:37 +01:00
|
|
|
$this->hostGroupMembershipResolver = new HostGroupMembershipResolver(
|
2017-03-14 14:21:15 +01:00
|
|
|
$this->db
|
|
|
|
);
|
|
|
|
} else {
|
2017-03-14 16:08:37 +01:00
|
|
|
$this->hostGroupMembershipResolver = false;
|
2017-03-14 14:21:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-14 16:08:37 +01:00
|
|
|
return $this->hostGroupMembershipResolver;
|
2017-03-14 14:21:15 +01:00
|
|
|
}
|
|
|
|
|
2016-02-19 15:23:44 +01:00
|
|
|
/**
|
|
|
|
* Evaluates a SyncRule and returns a list of modified objects
|
|
|
|
*
|
|
|
|
* TODO: This needs to be splitted into smaller methods
|
|
|
|
*
|
2017-08-21 21:53:19 +02:00
|
|
|
* @return DbObject[] List of modified IcingaObjects
|
2016-02-19 15:23:44 +01:00
|
|
|
*/
|
2016-02-23 11:22:25 +01:00
|
|
|
protected function prepare()
|
2016-02-19 15:23:44 +01:00
|
|
|
{
|
2016-02-24 12:24:19 +01:00
|
|
|
if ($this->isPrepared) {
|
|
|
|
return $this->objects;
|
|
|
|
}
|
|
|
|
|
2016-10-31 17:20:17 +01:00
|
|
|
PrefetchCache::initialize($this->db);
|
|
|
|
|
2016-02-24 10:50:57 +01:00
|
|
|
$this->raiseLimits()
|
2016-02-24 12:24:19 +01:00
|
|
|
->startMeasurements()
|
|
|
|
->fetchSyncProperties()
|
2016-02-24 10:50:57 +01:00
|
|
|
->prepareRelatedImportSources()
|
2016-02-23 21:05:09 +01:00
|
|
|
->prepareSourceColumns()
|
2016-02-24 15:33:08 +01:00
|
|
|
->loadExistingObjects()
|
2017-03-14 14:21:15 +01:00
|
|
|
->fetchImportedData()
|
|
|
|
->deferResolvers();
|
2016-02-19 15:23:44 +01:00
|
|
|
|
2016-02-23 08:58:22 +01:00
|
|
|
// TODO: directly work on existing objects, remember imported keys, then purge
|
2016-02-23 17:35:47 +01:00
|
|
|
$newObjects = $this->prepareNewObjects();
|
2016-02-19 15:23:44 +01:00
|
|
|
|
|
|
|
foreach ($newObjects as $key => $object) {
|
2016-02-23 17:47:18 +01:00
|
|
|
if (array_key_exists($key, $this->objects)) {
|
2016-02-24 10:50:57 +01:00
|
|
|
switch ($this->rule->update_policy) {
|
2016-02-19 15:23:44 +01:00
|
|
|
case 'override':
|
2016-02-23 17:47:18 +01:00
|
|
|
$this->objects[$key]->replaceWith($object);
|
2015-10-20 22:22:58 +02:00
|
|
|
break;
|
|
|
|
|
2016-02-19 15:23:44 +01:00
|
|
|
case 'merge':
|
2016-02-23 09:08:14 +01:00
|
|
|
// TODO: re-evaluate merge settings. vars.x instead of
|
|
|
|
// just "vars" might suffice.
|
2017-08-03 10:51:13 +02:00
|
|
|
$this->objects[$key]->merge($object, $this->replaceVars);
|
2016-02-19 15:23:44 +01:00
|
|
|
break;
|
2015-07-24 15:29:17 +02:00
|
|
|
|
2016-02-19 15:23:44 +01:00
|
|
|
default:
|
|
|
|
// policy 'ignore', no action
|
|
|
|
}
|
|
|
|
} else {
|
2016-02-23 17:47:18 +01:00
|
|
|
$this->objects[$key] = $object;
|
2015-10-20 23:29:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-22 11:01:37 +01:00
|
|
|
$noAction = array();
|
2016-06-26 14:00:37 +02:00
|
|
|
foreach ($this->rule->purgeStrategy()->listObjectsToPurge() as $key) {
|
2016-02-22 11:01:37 +01:00
|
|
|
if (array_key_exists($key, $newObjects)) {
|
2016-06-26 14:00:37 +02:00
|
|
|
// Object has been touched, do not delete
|
|
|
|
continue;
|
|
|
|
}
|
2016-02-19 15:23:44 +01:00
|
|
|
|
2016-06-26 14:00:37 +02:00
|
|
|
if (array_key_exists($key, $this->objects)) {
|
|
|
|
$object = $this->objects[$key];
|
|
|
|
if (! $object->hasBeenModified()) {
|
|
|
|
$object->markForRemoval();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-02-22 11:01:37 +01:00
|
|
|
|
2016-06-26 14:00:37 +02:00
|
|
|
foreach ($this->objects as $key => $object) {
|
|
|
|
if (! $object->hasBeenModified() && ! $object->shouldBeRemoved()) {
|
2016-02-22 11:01:37 +01:00
|
|
|
$noAction[] = $key;
|
2016-02-19 15:23:44 +01:00
|
|
|
}
|
2015-10-20 23:29:29 +02:00
|
|
|
}
|
|
|
|
|
2016-02-22 11:01:37 +01:00
|
|
|
foreach ($noAction as $key) {
|
2016-02-23 17:47:18 +01:00
|
|
|
unset($this->objects[$key]);
|
2016-02-22 11:01:37 +01:00
|
|
|
}
|
|
|
|
|
2016-02-24 12:24:19 +01:00
|
|
|
$this->isPrepared = true;
|
|
|
|
|
2016-02-23 17:47:18 +01:00
|
|
|
return $this->objects;
|
2015-10-20 23:29:29 +02:00
|
|
|
}
|
|
|
|
|
2015-12-08 11:51:05 +01:00
|
|
|
/**
|
|
|
|
* Runs a SyncRule and applies all resulting changes
|
|
|
|
* @return int
|
2017-08-03 10:51:13 +02:00
|
|
|
* @throws Exception
|
|
|
|
* @throws IcingaException
|
2015-12-08 11:51:05 +01:00
|
|
|
*/
|
2016-02-24 10:50:57 +01:00
|
|
|
public function apply()
|
2015-10-20 23:29:29 +02:00
|
|
|
{
|
2016-02-23 13:41:19 +01:00
|
|
|
$objects = $this->prepare();
|
2016-02-24 12:24:19 +01:00
|
|
|
$db = $this->db;
|
|
|
|
$dba = $db->getDbAdapter();
|
2015-10-20 23:29:29 +02:00
|
|
|
$dba->beginTransaction();
|
2016-06-28 12:56:53 +02:00
|
|
|
|
2016-10-27 15:43:26 +02:00
|
|
|
$object = null;
|
|
|
|
|
2016-06-28 12:56:53 +02:00
|
|
|
try {
|
|
|
|
$formerActivityChecksum = Util::hex2binary(
|
|
|
|
$db->getLastActivityChecksum()
|
|
|
|
);
|
|
|
|
$created = 0;
|
|
|
|
$modified = 0;
|
|
|
|
$deleted = 0;
|
|
|
|
foreach ($objects as $object) {
|
2017-03-14 14:21:15 +01:00
|
|
|
$this->setResolver($object);
|
2016-10-05 19:45:25 +02:00
|
|
|
if ($object->shouldBeRemoved()) {
|
2017-08-21 21:53:19 +02:00
|
|
|
$object->delete();
|
2016-06-28 12:56:53 +02:00
|
|
|
$deleted++;
|
|
|
|
continue;
|
|
|
|
}
|
2015-12-04 10:24:54 +01:00
|
|
|
|
2016-06-28 12:56:53 +02:00
|
|
|
if ($object->hasBeenModified()) {
|
|
|
|
if ($object->hasBeenLoadedFromDb()) {
|
|
|
|
$modified++;
|
|
|
|
} else {
|
|
|
|
$created++;
|
|
|
|
}
|
|
|
|
$object->store($db);
|
2016-02-24 12:24:19 +01:00
|
|
|
}
|
2015-11-02 09:29:03 +01:00
|
|
|
}
|
2015-10-20 22:22:58 +02:00
|
|
|
|
2016-06-28 12:56:53 +02:00
|
|
|
$runProperties = array(
|
|
|
|
'objects_created' => $created,
|
|
|
|
'objects_deleted' => $deleted,
|
|
|
|
'objects_modified' => $modified,
|
|
|
|
);
|
2016-02-24 12:24:19 +01:00
|
|
|
|
2016-06-28 12:56:53 +02:00
|
|
|
if ($created + $deleted + $modified > 0) {
|
|
|
|
// TODO: What if this has been the very first activity?
|
|
|
|
$runProperties['last_former_activity'] = $db->quoteBinary($formerActivityChecksum);
|
|
|
|
$runProperties['last_related_activity'] = $db->quoteBinary(Util::hex2binary(
|
|
|
|
$db->getLastActivityChecksum()
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->run->setProperties($runProperties)->store();
|
2016-02-24 12:24:19 +01:00
|
|
|
|
2016-06-28 12:56:53 +02:00
|
|
|
$dba->commit();
|
2016-02-24 12:24:19 +01:00
|
|
|
|
2016-06-28 12:56:53 +02:00
|
|
|
// Store duration after commit, as the commit might take some time
|
|
|
|
$this->run->set('duration_ms', (int) round(
|
|
|
|
(microtime(true) - $this->runStartTime) * 1000
|
|
|
|
))->store();
|
|
|
|
} catch (Exception $e) {
|
|
|
|
$dba->rollBack();
|
2017-01-13 20:48:50 +01:00
|
|
|
|
2016-10-27 15:43:26 +02:00
|
|
|
if ($object !== null && $object instanceof IcingaObject) {
|
|
|
|
throw new IcingaException(
|
|
|
|
'Exception while syncing %s %s: %s',
|
2017-01-13 19:47:54 +01:00
|
|
|
get_class($object),
|
|
|
|
$object->get('object_name'),
|
|
|
|
$e->getMessage(),
|
|
|
|
$e
|
2016-10-27 15:43:26 +02:00
|
|
|
);
|
2017-01-13 19:47:54 +01:00
|
|
|
} else {
|
2016-10-27 15:43:26 +02:00
|
|
|
throw $e;
|
|
|
|
}
|
2016-06-28 12:56:53 +02:00
|
|
|
}
|
2016-02-24 12:24:19 +01:00
|
|
|
|
|
|
|
return $this->run->id;
|
2015-07-24 10:51:55 +02:00
|
|
|
}
|
|
|
|
}
|